How to generate predefined manifest with versioning using workbox-build for Service Worker - service-worker

I'm trying to configure workbox-build to create a service worker (generateSW) or inject a manifest (injectManifest) to an existing service worker based on a list of predefined URLs instead of a pattern match in order to preCache specific resources on app load.
not like this:
const { generateSW } = require('workbox-build');
const swDest = 'app/cache-sw.js';
generateSW({
globDirectory: "app/",
globPatterns: [
"**/*.{html,js,png,css,json,txt,ico,config}"
]
swDest
}).then(({ count, size }) => {
console.log(`Generated ${swDest}, which will precache ${count} files, totaling ${size} bytes.`);
});
but something like this:
const { generateSW } = require('workbox-build');
const swDest = 'app/cache-sw.js';
generateSW({
globDirectory: "app/",
manifestURLs: [
"/index.html",
"/favicon.ico",
"/info.txt",
...
],
swDest
}).then(({ count, size }) => {
console.log(`Generated ${swDest}, which will precache ${count} files, totaling ${size} bytes.`);
});
So that the auto-generated service worker contains a manifest similar to something like this:
[
{
"url": "/index.html",
"revision": "487659b6e9c542e7cd8227e0e9d0bed1"
},
{
"url": "/favicon.ico",
"revision": "29459c5d9c542e7cd8227e0e9d0if83"
},
{
"url": "/info.txt",
"revision": "73932c5d9c542e7cd8227e0e9z7el19"
},
...
]
And the revision gets updated during build when a resource changes so that the cache is invalidated in the browser on next load.
Thanks in advance!

A glob pattern without any wildcards should match literal filenames. You should be able to accomplish what you describe by using those filenames as the values passed to globPatterns:
const { generateSW } = require('workbox-build');
const swDest = 'app/cache-sw.js';
generateSW({
globDirectory: "app/",
globPatterns: [
"index.html",
"favicon.ico",
"info.txt",
...
]
swDest
}).then(({ count, size }) => {
console.log(`Generated ${swDest}, which will precache ${count} files, totaling ${size} bytes.`);
});

Related

How to Cache Additional Items in a Workbox Plugin

I'd like to write a Workbox plugin that intercepts a particular API call, does some logic (omitted from the code below) that determines other URL's that can be cached, then adds them to the runtime cache.
export class PreCachePlugin implements WorkboxPlugin {
fetchDidSucceed: WorkboxPlugin["fetchDidSucceed"] = async ({
response,
}) => {
if (response.ok) {
const clonedResponse = await response.clone();
const json = await clonedResponse.json();
const urls: string[] = getUrlsToCache(json); // Omitting this logic.
await Promise.all(urls.map(url => {
const response = await fetch(url);
// TODO: How do I fetch these URL's and put them in the cache configured
// below while still respecting the expiration.maxEntries setting below?
}));
}
}
}
I am using GenerateSW with workbox-webpack-plugin and adding my plugin:
new GenerateSW({
include: [/\.js$/, /\.json$/, /\.html$/],
runtimeCaching: [
{
urlPattern: ({ url }) => url.toString().endsWith("/foo"),
handler: "StaleWhileRevalidate",
options: {
cacheName: "Foo",
expiration: { maxEntries: 100 },
plugins: [
new PreCachePlugin(),
],
},
},
],
swDest: "./sw.js",
}
How do I fetch the above new URL's and put them in the configured runtime cache while still respecting the expiration.maxEntries settings?

Vercel and Nextjs: redirect subdomain to path dynamically

I have a nextjs project with a :client param which represents a client, like this:
domain.com/:client
And I have multiple clients... so I need to do this rewrite:
:client.domain.com -> domain.com/:client
For example for clients:
google.domain.com -> domain.com/google
netflix.domain.com -> domain.com/netflix
...
Inside the same project.
Any way to do that?
You can use the redirects option in the vercel.json, as Maxime mentioned.
However, it requires 1 extra key.
For example, if your app is available at company.com:
{
...
redirects: [
{
"source": "/",
"has": [
{
"type": "host",
"value": "app.company.com"
}
],
"destination": "/app"
}
]
}
More info:
Example Guide
vercel.json docs
Create a config in your project root with next.config.js
If this file exists add the following snippet to it, Mind you, we used example.com in place of domain .com as Body cannot contain "http://domain. com" in stackoverflow
// next.config.js
module.exports = {
async redirects() {
return [
{
source: "/:path*",
has: [
{
type: "host",
value: "client.example.com",
},
],
destination: "http://example.com/client/:path*",
permanent: false,
},
];
},
};
To confirm it's also working in development, try with localhost
module.exports = {
reactStrictMode: true,
// async rewrites() {
async redirects() {
return [
{
source: "/:path*",
has: [
{
type: "host",
value: "client.localhost",
},
],
destination: "http://localhost:3000/client/:path*",
permanent: false,
},
];
},
};
Dynamic Redirect
To make it dynamic, we'll create an array of subdomains
const subdomains = ["google", "netflix"];
module.exports = {
async redirects() {
return [
...subdomains.map((subdomain) => ({
source: "/:path*",
has: [
{
type: "host",
value: `${subdomain}.example.com`,
},
],
destination: `https://example.com/${subdomain}/:path*`,
permanent: false,
})),
];
},
}
You can read more from the official next.js doc redirects or rewrite

How should TypeORM in a TypeScript project be configured so it works everywhere?

I'm struggling to find a configuration that works for TypeORM in a TypeScript project in all environments.
For example, starting with this ormconfig.js:
var dotenv = require("dotenv")
dotenv.config()
var connectionOptions = [
{
"name": "default",
"type": "postgres",
"url": process.env.DATABASE_URL,
},
{
"name": "testing",
"type": "postgres",
"url": `${process.env.DATABASE_URL}_test`,
}];
module.exports = connectionOptions
But then, when I try to start the application, I get this error:
No repository for "User" was found. Looks like this entity is not registered in current "default" connection?
So, I add the entities to the config:
var dotenv = require("dotenv")
dotenv.config()
var connectionOptions = [
{
"name": "default",
"type": "postgres",
"url": process.env.DATABASE_URL,
"entities": ["src/entity/**/*"],
},
{
"name": "testing",
"type": "postgres",
"url": `${process.env.DATABASE_URL}_test`, // TODO: fix
"entities": ["src/entity/**/*"],
}];
module.exports = connectionOptions
At this point, running the app in dev (ts-node-dev src/main.ts) works. But when I compile it and try to run the JavaScript, I get this error:
C:\Users\pupeno\Documents\Flexpoint Tech\js\exp7\backend\src\entity\User.ts:1
import {BaseEntity, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn} from "typeorm"
^^^^^^
SyntaxError: Cannot use import statement outside a module
It's trying to load the User.ts source file instead of the compiled User.js.
The way I'm running my app in prod is by running node build/src/main.js, the problem is that ormconfig.js is still at the top level, printing __dirname and __filename shows:
C:\Users\pupeno\Documents\Flexpoint Tech\js\exp7\backend
C:\Users\pupeno\Documents\Flexpoint Tech\js\exp7\backend\ormconfig.js
How are you supposed to configure TypeORM to work in both development and production?
Renaming my entity to User.entity.ts and setting entities this way:
var dotenv = require("dotenv")
dotenv.config()
var connectionOptions = [
{
"name": "default",
"type": "postgres",
"url": process.env.DATABASE_URL,
"entities": [__dirname + '/**/*.entity{.ts,.js}'],
},
{
"name": "testing",
"type": "postgres",
"url": `${process.env.DATABASE_URL}_test`, // TODO: fix
"entities": [__dirname + '/**/*.entity{.ts,.js}'],
}];
console.log(connectionOptions)
module.exports = connectionOptions
cause the same error, as Node tries to load a TypeScript file:
(node:19344) UnhandledPromiseRejectionWarning: C:\Users\pupeno\Documents\Flexpoint Tech\js\exp7\backend\src\entity\User.entity.ts:1
import {BaseEntity, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn} from "typeorm"
^^^^^^
SyntaxError: Cannot use import statement outside a module
My solution to this problem is to import models as classes instead of trying to get the glob to work. I also don't use an ormconfig.js but keep everything in TypeScript with a connection manager class. So your example rewritten for me would look like this:
import {createConnection, Connection} from "typeorm";
import {User} from "./models";
export getConnection = async (): Promise<Connection> => {
const connection = await createConnection({
name: "testing",
type: "postgres",
url: process.env.DATABASE_URL,
entities: [User]
});
return await connection.connect();
}
That can work nicely if you have a small number of models, but it doesn't scale real well. If you have many models, you can export them all from an index and then import them all from that index.
export * from "./User";
export * from "./Foo";
export * from "./Bar";
Now here's the import
import {createConnection, Connection} from "typeorm";
import * as models from "./models";
export getConnection = async (): Promise<Connection> => {
const connection = await createConnection({
name: "testing",
type: "postgres",
url: process.env.DATABASE_URL,
entities: [...Object.values(models)]
});
return await connection.connect();
}
And finally dotenv is a little simplistic for doing this work for my tastes. I recommend you check out https://github.com/lorenwest/node-config and see how you can have base config, env-specific config and envvars all play together well.
I got a similar issue. The solution is to run the app from inside the build directory, not the project directory, i.e. do cd dist and then node app.js instead of node dist/app.js.
My project structure is:
my-app-name/
-- node_modules/
-- dist/ # there go compiled JS files
-- entity/ # entities in TS
-- migration/ # migrations in TS
-- app.ts
-- tsconfig.json
-- ormconfig.json
-- package.json
# ormconfig.json
{
# database specific code is omitted
"entities": [
"./entity/*.js"
],
"migrations": [
"./migration/*.js"
]
}
# tsconfig.json
{
"files": [
"app.ts",
"entity/author.ts",
"entity/photo.ts",
"migration/1618859843962-AddAuthorToPhotos.ts"
],
"compilerOptions": {
"outDir": "dist",
"target": "esnext",
"strict": true,
"lib": [
"ES2020"
],
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node"
}
}

Why Service Worker stalls http/https requests to the server?

I am creating a website using django and nuxtjs. I have installed service worker to improve speed. When I unregister service worker in inspect/Application tab, timing of my website in inspect/Network tab looks like this:
When I use service worker opening website at the first time is fine, But when I reload the page or open another page of the website, the request to the server is queued long time. The timing of my website looks like this:
which is queued 1.91 seconds. here describes reasons of queuing a request and all of them is in browser control.
I installed service worker with pwa module which has workbox in its dependencies. My nuxt.config.js file is here:
import pkg from './package'
import {modify_html} from './services/amp/hook';
export default {
mode: 'universal',
/*
** Headers of the page
*/
head: {
title: 'example',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: pkg.description }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: [
'~/static/css/base.css',
'~/static/css/hooper.css',
'~/static/css/font-awesome/css/all.min.css',
],
/*
** Plugins to load before mounting the App
*/
plugins: [
{ src: '~plugins/ga.js', ssr: false }
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://axios.nuxtjs.org/usage
'#nuxtjs/axios',
// Doc: https://bootstrap-vue.js.org/docs/
'bootstrap-vue/nuxt',
'#nuxtjs/pwa',
'#nuxtjs/device',
],
manifest:{
"short_name": "example",
"name": "example",
"icons": [
{
"src": "/static/icon.png",
"type": "image/png",
},
],
"start_url": "/",
"background_color": "white",
"display": "standalone",
"scope": "/",
"theme_color": "white"
},
workbox:{
},
/*
** Axios module configuration
*/
axios: {
// See https://github.com/nuxt-community/axios-module#options
},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {
}
},
hooks:{
// This hook is called before generatic static html files for SPA mode
'generate:page': (page) => {
if(page.path.includes('/amp/')){
page.html = modify_html(page.html)
}
},
// This hook is called before rendering the html to the browser
'render:route': (url, page, { req, res }) => {
if(url.includes('/amp/')){
page.html = modify_html(page.html)
}
}
}
}
How can I fix service worker to do not stall new requests?

How to you modify the precache manifest file generated by Workbox? Need urls to have preceding '/'

In the generated precache-manifest.*.js file the URLs all reference relative paths when I need absolute since my app will have some sub-directories as well.
Example of generated file:
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
"revision": "1d94d834b7044ec6d4e9",
"url": "js/app.js"
},
{
"revision": "632f09e6ed606bbed1f1",
"url": "css/app.css"
},
...
}
When I need it to look like this:
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
"revision": "1d94d834b7044ec6d4e9",
"url": "/js/app.js"
},
{
"revision": "632f09e6ed606bbed1f1",
"url": "/css/app.css"
},
...
}
I'm using webpack 4.41.0 and workbox-webpack-plugin 4.3.1
Any help would be greatly appreciated! I can add more detail if needed as well.
Here's my webpack config:
let config = {
entry,
stats: {
hash: false,
version: false,
timings: false,
children: false,
errorDetails: false,
entrypoints: false,
performance: inProduction,
chunks: false,
modules: false,
reasons: false,
source: false,
publicPath: false,
builtAt: false
},
performance: { hints: false },
// Valid options: "production" | "development" | "none"
mode: inProduction ? 'production' : 'development',
plugins: [
new CopyPlugin(copyConfig),
new webpack.ProvidePlugin(providers), // Providers, e.g. jQuery
new WebpackNotifierPlugin({ title: 'Webpack' }), // OS notification
new VueLoaderPlugin(), // Vue-loader
new CleanWebpackPlugin(pathsToClean, cleanOptions), // Clean up pre-compile time
new ManifestPlugin(manifestOptions), // Manifest file
new FriendlyErrorsWebpackPlugin({ clearConsole: true }), // Prettify console
new MiniCssExtractPlugin(cssOptions), // Extract CSS files
new WebpackMd5Hash(), // use md5 for hashing
{
/* Laravel Spark RTL support */
apply: (compiler) => {
compiler.hooks.afterEmit.tap('AfterEmitPlugin', (compilation) => {
exec('node_modules/rtlcss/bin/rtlcss.js public/css/app-rtl.css ./public/css/app-rtl.css', (err, stdout, stderr) => {
if (stdout) process.stdout.write(stdout);
if (stderr) process.stderr.write(stderr);
});
});
}
}
],
module: {
rules: [
{
test: /\.vue$/,
use: ['vue-loader']
},
{
test: /\.s?css$/,
use: [
'style-loader',
{
loader: MiniCssExtractPlugin.loader,
options: { hmr: isHot } // set HMR if flagged
},
'css-loader',
'postcss-loader',
'sass-loader'
]
}
]
},
resolve: {
extensions: ['.js', '.json', '.vue'],
modules: ['node_modules'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'~': path.join(__dirname, 'resources/assets/js'),
jquery: "jquery/src/jquery",
}
},
output: {
filename: 'js/[name].js',
// chunkFilename: inProduction ? 'js/[name].[chunkhash].js' : 'js/[name].js',
path: publicPath,
},
optimization: {
...optimization,
concatenateModules: false,
providedExports: false,
usedExports: false,
},
devtool: inDevelopment ? 'eval-source-map' : false,
devServer: {
headers: {
'Access-Control-Allow-Origin': '*'
},
port: port,
contentBase: publicPath,
historyApiFallback: true,
noInfo: false,
compress: true,
quiet: true,
hot: isHot,
}
}
And my GenerateSW:
new GenerateSW({
// The cache ID
cacheId: 'pwa',
// The path and filename of the service worker file that will be created by the build process, relative to the webpack output directory.
swDest: path.join(publicPath, 'sw.js'),
clientsClaim: true,
skipWaiting: true,
// Files to exclude from the precache
exclude: [/\.(?:png|jpg|jpeg|svg)$/, /\.map$/, /manifest\.json$/, /service-worker\.js$/, /sw\.js$/],
// Default fall-back url
navigateFallback: '/',
// An optional array of regular expressions that restricts which URLs the configured navigateFallback behavior applies to.
// This is useful if only a subset of your site's URLs should be treated as being part of a Single Page App.
navigateFallbackWhitelist: [
/^\/media\//,
/^\/settings\//,
],
// Runtime cache
runtimeCaching: [
{
urlPattern: new RegExp(`${process.env.APP_URL}`),
handler: 'NetworkFirst',
options: {
cacheName: `${process.env.APP_NAME}-${process.env.APP_ENV}`
}
},
{
urlPattern: new RegExp('https://fonts.(googleapis|gstatic).com'),
handler: 'CacheFirst',
options: {
cacheName: 'google-fonts'
}
}
]
}
)
And a couple of defined common variables:
...
// Production flag
const inProduction = process.env.NODE_ENV === 'production'
const inDevelopment = process.env.NODE_ENV === 'development'
// HMR
const isHot = process.argv.includes('--hot')
// Public/webroot path
const publicPath = path.resolve(__dirname, 'public')
// Primary webpack entry point(s)
const entry = {
/*
* JS entry point/Vue base component
* Make sure to import your css files here, e.g. `import '../sass/app.scss'`
*/
app: path.resolve(__dirname, 'resources/assets/js/app.js'),
}
...
For anyone visiting this question, you can use the webpack output.publicPath setting to add a prefix to your manifest URLS like so:
plugins: [
new InjectManifest({}) // Left here just for reference
],
output: {
publicPath: '/' // You can add your prefix here
}

Resources