From 1e6af5fa9f6a807930f451adb5ab5078d5cda451 Mon Sep 17 00:00:00 2001 From: shadowwalker Date: Mon, 22 Aug 2022 21:58:01 -0700 Subject: [PATCH] BREAKING: Update to next.js recommended plugin signature See comments: https://github.com/vercel/next.js/pull/38498#issuecomment-1197282975 Reference implementation: https://github.com/vercel/next.js/blob/canary/packages/next-mdx/index.js#L2 --- README.md | 152 ++--- .../cache-on-front-end-nav/next.config.js | 10 +- examples/cookie/next.config.js | 14 +- examples/custom-ts-worker/README.md | 33 +- examples/custom-ts-worker/next.config.js | 10 +- examples/custom-worker/README.md | 31 +- examples/custom-worker/next.config.js | 10 +- examples/lifecycle/next.config.js | 14 +- examples/minimal/next.config.js | 18 +- examples/next-9/next.config.js | 10 +- examples/next-i18next/next.config.js | 4 +- examples/next-image/README.md | 7 +- examples/next-image/next.config.js | 10 +- examples/offline-fallback-v2/next.config.js | 21 +- examples/offline-fallback/next.config.js | 9 +- examples/web-push/next.config.js | 10 +- index.js | 576 +++++++++--------- package.json | 2 +- 18 files changed, 452 insertions(+), 489 deletions(-) diff --git a/README.md b/README.md index 019c3059..5fa5c72c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ This plugin is powered by [workbox](https://developer.chrome.com/docs/workbox/) > > **NOTE 2** - If you encounter error `TypeError: Cannot read property **'javascript' of undefined**` during build, [please consider upgrade to webpack5 in `next.config.js`](https://github.com/shadowwalker/next-pwa/issues/198#issuecomment-817205700). ----- +--- [![Open in Gitpod](https://img.shields.io/badge/Open%20In-Gitpod.io-%231966D2?style=for-the-badge&logo=gitpod)](https://gitpod.io/#https://github.com/shadowwalker/next-pwa/) @@ -39,7 +39,7 @@ This plugin is powered by [workbox](https://developer.chrome.com/docs/workbox/) > If you are new to `next.js` or `react.js` at all, you may want to first checkout [learn next.js](https://nextjs.org/learn/basics/create-nextjs-app) or [next.js document](https://nextjs.org/docs/getting-started). Then start from [a simple example](https://github.com/shadowwalker/next-pwa/tree/master/examples/next-9) or [progressive-web-app example in next.js repository](https://github.com/vercel/next.js/tree/canary/examples/progressive-web-app). -``` bash +```bash yarn add next-pwa ``` @@ -49,13 +49,13 @@ yarn add next-pwa Update or create `next.config.js` with -``` javascript -const withPWA = require('next-pwa') +```javascript +const withPWA = require('next-pwa')({ + dest: 'public' +}) module.exports = withPWA({ - pwa: { - dest: 'public' - } + // next.js config }) ``` @@ -65,7 +65,6 @@ If you are using Next.js version 9 or newer, then skip the options below and mov If you are using Next.js older than version 9, you'll need to pick an option below before continuing to Step 2. - ### Option 1: Host Static Files Copy files to your static file hosting server, so that they are accessible from the following paths: `https://yourdomain.com/sw.js` and `https://yourdomain.com/workbox-*.js`. @@ -89,23 +88,21 @@ const next = require('next') const app = next({ dev: process.env.NODE_ENV !== 'production' }) const handle = app.getRequestHandler() -app.prepare() - .then(() => { - createServer((req, res) => { - const parsedUrl = parse(req.url, true) - const { pathname } = parsedUrl - - if (pathname === '/sw.js' || /^\/(workbox|worker|fallback)-\w+\.js$/.test(pathname)) { - const filePath = join(__dirname, '.next', pathname) - app.serveStatic(req, res, filePath) - } else { - handle(req, res, parsedUrl) - } - }) - .listen(3000, () => { - console.log(`> Ready on http://localhost:${3000}`) - }) +app.prepare().then(() => { + createServer((req, res) => { + const parsedUrl = parse(req.url, true) + const { pathname } = parsedUrl + + if (pathname === '/sw.js' || /^\/(workbox|worker|fallback)-\w+\.js$/.test(pathname)) { + const filePath = join(__dirname, '.next', pathname) + app.serveStatic(req, res, filePath) + } else { + handle(req, res, parsedUrl) + } + }).listen(3000, () => { + console.log(`> Ready on http://localhost:${3000}`) }) +}) ``` > The following setup has nothing to do with `next-pwa` plugin, and you probably have already set them up. If not, go ahead and set them up. @@ -148,43 +145,43 @@ Create a `manifest.json` file in your `public` folder: Add the following into `_document.jsx` or `_app.tsx`, in ``: -``` html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +```html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ``` -> Tip: Put the `viewport` head meta tag into `_app.js` rather than in `_document.js` if you need it. +> Tip: Put the `viewport` head meta tag into `_app.js` rather than in `_document.js` if you need it. -``` typescript - +```typescript + ``` ## Offline Fallbacks Offline fallbacks are useful when the fetch failed from both cache and network, a precached resource is served instead of present an error from browser. -To get started simply add a `/_offline` page such as `pages/_offline.js` or `pages/_offline.jsx` or `pages/_offline.ts` or `pages/_offline.tsx`. Then you are all set! When the user is offline, all pages which are not cached will fallback to '/_offline'. +To get started simply add a `/_offline` page such as `pages/_offline.js` or `pages/_offline.jsx` or `pages/_offline.ts` or `pages/_offline.tsx`. Then you are all set! When the user is offline, all pages which are not cached will fallback to '/\_offline'. **[Use this example to see it in action](https://github.com/shadowwalker/next-pwa/tree/master/examples/offline-fallback-v2)** @@ -221,17 +221,17 @@ You can also setup `precacheFallback.fallbackURL` in your [runtimeCaching config There are options you can use to customize the behavior of this plugin by adding `pwa` object in the next config in `next.config.js`: ```javascript -const withPWA = require('next-pwa') +const withPWA = require('next-pwa')({ + dest: 'public' + // disable: process.env.NODE_ENV === 'development', + // register: true, + // scope: '/app', + // sw: 'service-worker.js', + //... +}) module.exports = withPWA({ - pwa: { - dest: 'public', - // disable: process.env.NODE_ENV === 'development', - // register: true, - // scope: '/app', - // sw: 'service-worker.js', - //... - } + // next.js config }) ``` @@ -260,7 +260,7 @@ module.exports = withPWA({ - example: `['!img/super-large-image.jpg', '!fonts/not-used-fonts.otf']` - buildExcludes - an array of extra pattern or function to exclude files from being precached in `.next/static` (or your custom build) folder - default: `[]` - - example: `[/chunks\/images\/.*$/]` - Don't precache files under `.next/static/chunks/images` (Highly recommend this to work with `next-optimized-images` plugin) + - example: `[/chunks\/images\/.*$/]` - Don't precache files under `.next/static/chunks/images` (Highly recommend this to work with `next-optimized-images` plugin) - doc: Array of (string, RegExp, or function()). One or more specifiers used to exclude assets from the precache manifest. This is interpreted following the same rules as Webpack's standard exclude option. - cacheStartUrl - whether to cache start url - default: `true` @@ -311,7 +311,7 @@ Here is the [document on how to write runtime caching configurations](https://de 3. When you are debugging service worker, constantly `clean application cache` to reduce some flaky errors. 4. If you are redirecting the user to another route, please note [workbox by default only cache response with 200 HTTP status](https://developer.chrome.com/docs/workbox/modules/workbox-cacheable-response#what_are_the_defaults), if you really want to cache redirected page for the route, you can specify it in `runtimeCaching` such as `options.cacheableResponse.statuses=[200,302]`. 5. When debugging issues, you may want to format your generated `sw.js` file to figure out what's really going on. -6. Force `next-pwa` to generate worker box production build by specify the option `mode: 'production'` in your `pwa` section of `next.config.js`. Though `next-pwa` automatically generate the worker box development build during development (by running `next`) and worker box production build during production (by running `next build` and `next start`). You may still want to force it to production build even during development of your web app for following reason: +6. Force `next-pwa` to generate worker box production build by specify the option `mode: 'production'` in your `pwa` section of `next.config.js`. Though `next-pwa` automatically generate the worker box development build during development (by running `next`) and worker box production build during production (by running `next build` and `next start`). You may still want to force it to production build even during development of your web app for following reason: 1. Reduce logging noise due to production build doesn't include logging. 2. Improve performance a bit due to production build is optimized and minified. 7. If you just want to disable worker box logging while keeping development build during development, [simply put `self.__WB_DISABLE_DEV_LOGS = true` in your `worker/index.js` (create one if you don't have one)](https://github.com/shadowwalker/next-pwa/blob/c48ef110360d0138ad2dacd82ab96964e3da2daf/examples/custom-worker/worker/index.js#L6). diff --git a/examples/cache-on-front-end-nav/next.config.js b/examples/cache-on-front-end-nav/next.config.js index c9f0fbec..5d6a902e 100644 --- a/examples/cache-on-front-end-nav/next.config.js +++ b/examples/cache-on-front-end-nav/next.config.js @@ -1,7 +1,5 @@ -const withPWA = require('next-pwa') - -module.exports = withPWA({ - pwa: { - dest: 'public' - } +const withPWA = require('next-pwa')({ + dest: 'public' }) + +module.exports = withPWA() diff --git a/examples/cookie/next.config.js b/examples/cookie/next.config.js index 996a600d..3723323a 100644 --- a/examples/cookie/next.config.js +++ b/examples/cookie/next.config.js @@ -1,9 +1,7 @@ -const withPWA = require('next-pwa') - -module.exports = withPWA({ - pwa: { - dest: 'public', - dynamicStartUrl: true, // this is same as default value - dynamicStartUrlRedirect: '/login' // recommend to config this for best user experience if your start-url redirects on first load - } +const withPWA = require('next-pwa')({ + dest: 'public', + dynamicStartUrl: true, // this is same as default value + dynamicStartUrlRedirect: '/login' // recommend to config this for best user experience if your start-url redirects on first load }) + +module.exports = withPWA() diff --git a/examples/custom-ts-worker/README.md b/examples/custom-ts-worker/README.md index 5fbf435e..2f382cb0 100644 --- a/examples/custom-ts-worker/README.md +++ b/examples/custom-ts-worker/README.md @@ -10,39 +10,37 @@ Simply create a `worker/index.ts` and start implementing your service worker. `n In this way, you get benefit of code splitting and size minimization automatically. Yes! `require` modules works! Yes! you can share codes between web app and the service worker! -> - In dev mode, `worker/index.ts` is not watch, so it will not hot reload. +> - In dev mode, `worker/index.ts` is not watched, so it will not hot reload. ### Custom Worker Directory You can customize the directory of your custom worker file by setting the `customWorkerDir` relative to the `basedir` in the `pwa` section of your `next.config.js`: - -``` javascript -const withPWA = require('next-pwa') +```javascript +const withPWA = require('next-pwa')({ + customWorkerDir: 'serviceworker' + // ... +}) module.exports = withPWA({ - pwa: { - customWorkerDir: 'serviceworker' - ... - } + // next.js config }) ``` In this example, `next-pwa` would look for `serviceworker/index.ts`. - ## Old Method (Still Works) Basically you need to create a file such as `worker.js` in `public` folder, then add an option `importScripts` to `pwa` object in `next.config.js`: -``` javascript -const withPWA = require('next-pwa') +```javascript +const withPWA = require('next-pwa')({ + dest: 'public', + importScripts: ['/worker.js'] +}) module.exports = withPWA({ - pwa: { - dest: 'public', - importScripts: ['/worker.js'] - } + // next.js config }) ``` @@ -52,7 +50,7 @@ Then service worker generated will automatically import your code and run it bef [![Open in Gitpod](https://img.shields.io/badge/Open%20In-Gitpod.io-%231966D2?style=for-the-badge&logo=gitpod)](https://gitpod.io/#https://github.com/shadowwalker/next-pwa/) -``` bash +```bash cd examples/custom-ts-server yarn install yarn build @@ -66,6 +64,3 @@ yarn start **/public/sw.js **/public/worker-*.js ``` - - - diff --git a/examples/custom-ts-worker/next.config.js b/examples/custom-ts-worker/next.config.js index c9f0fbec..5d6a902e 100644 --- a/examples/custom-ts-worker/next.config.js +++ b/examples/custom-ts-worker/next.config.js @@ -1,7 +1,5 @@ -const withPWA = require('next-pwa') - -module.exports = withPWA({ - pwa: { - dest: 'public' - } +const withPWA = require('next-pwa')({ + dest: 'public' }) + +module.exports = withPWA() diff --git a/examples/custom-worker/README.md b/examples/custom-worker/README.md index 923209ac..d52dea2e 100644 --- a/examples/custom-worker/README.md +++ b/examples/custom-worker/README.md @@ -18,33 +18,31 @@ In this way, you get benefit of code splitting and size minimization automatical You can customize the directory of your custom worker file by setting the `customWorkerDir` relative to the `basedir` in the `pwa` section of your `next.config.js`: - -``` javascript -const withPWA = require('next-pwa') +```javascript +const withPWA = require('next-pwa')({ + customWorkerDir: 'serviceworker' + ... +}) module.exports = withPWA({ - pwa: { - customWorkerDir: 'serviceworker' - ... - } + // next.js config }) ``` In this example, `next-pwa` would look for `serviceworker/index.js`. - ## Old Method (Still Works) Basically you need to create a file such as `worker.js` in `public` folder, then add an option `importScripts` to `pwa` object in `next.config.js`: -``` javascript -const withPWA = require('next-pwa') +```javascript +const withPWA = require('next-pwa')({ + dest: 'public', + importScripts: ['/worker.js'] +}) module.exports = withPWA({ - pwa: { - dest: 'public', - importScripts: ['/worker.js'] - } + // next.js config }) ``` @@ -54,7 +52,7 @@ Then service worker generated will automatically import your code and run it bef [![Open in Gitpod](https://img.shields.io/badge/Open%20In-Gitpod.io-%231966D2?style=for-the-badge&logo=gitpod)](https://gitpod.io/#https://github.com/shadowwalker/next-pwa/) -``` bash +```bash cd examples/custom-server yarn install yarn build @@ -68,6 +66,3 @@ yarn start **/public/sw.js **/public/worker-*.js ``` - - - diff --git a/examples/custom-worker/next.config.js b/examples/custom-worker/next.config.js index c9f0fbec..5d6a902e 100644 --- a/examples/custom-worker/next.config.js +++ b/examples/custom-worker/next.config.js @@ -1,7 +1,5 @@ -const withPWA = require('next-pwa') - -module.exports = withPWA({ - pwa: { - dest: 'public' - } +const withPWA = require('next-pwa')({ + dest: 'public' }) + +module.exports = withPWA() diff --git a/examples/lifecycle/next.config.js b/examples/lifecycle/next.config.js index 2fe937d3..d0264a39 100644 --- a/examples/lifecycle/next.config.js +++ b/examples/lifecycle/next.config.js @@ -1,9 +1,7 @@ -const withPWA = require('next-pwa') - -module.exports = withPWA({ - pwa: { - dest: 'public', - register: false, - skipWaiting: false - } +const withPWA = require('next-pwa')({ + dest: 'public', + register: false, + skipWaiting: false }) + +module.exports = withPWA() diff --git a/examples/minimal/next.config.js b/examples/minimal/next.config.js index 4f92c25c..27b8be85 100644 --- a/examples/minimal/next.config.js +++ b/examples/minimal/next.config.js @@ -1,11 +1,9 @@ -const withPWA = require('next-pwa') - -module.exports = withPWA({ - pwa: { - // pwa output folder - // dest: '.next/pwa' - // - // Other configurations: - // ... - } +const withPWA = require('next-pwa')({ + // pwa output folder + // dest: '.next/pwa' + // + // Other configurations: + // ... }) + +module.exports = withPWA() diff --git a/examples/next-9/next.config.js b/examples/next-9/next.config.js index c9f0fbec..5d6a902e 100644 --- a/examples/next-9/next.config.js +++ b/examples/next-9/next.config.js @@ -1,7 +1,5 @@ -const withPWA = require('next-pwa') - -module.exports = withPWA({ - pwa: { - dest: 'public' - } +const withPWA = require('next-pwa')({ + dest: 'public' }) + +module.exports = withPWA() diff --git a/examples/next-i18next/next.config.js b/examples/next-i18next/next.config.js index 3d204dd3..2d102a3e 100644 --- a/examples/next-i18next/next.config.js +++ b/examples/next-i18next/next.config.js @@ -1,3 +1,3 @@ -const withPWA = require('next-pwa') +const withPWA = require('next-pwa')() -module.exports = withPWA({}) +module.exports = withPWA() diff --git a/examples/next-image/README.md b/examples/next-image/README.md index e47e65d0..96f2de5e 100644 --- a/examples/next-image/README.md +++ b/examples/next-image/README.md @@ -4,13 +4,13 @@ This example demonstrates best practices to serve your images through `next.js` built-in image serving feature. -For best performance, put images in it's own folder other than `public`. This is prevent duplicate precaching entries in the `sw.js` service worker script. Then `import Image from 'next/image'` to use the `Image` component provided from `next.js` in your app. +For best performance, put images in it's own folder other than `public`. This will prevent duplicate precaching entries in the `sw.js` service worker script. Then `import Image from 'next/image'` to use the `Image` component provided from `next.js` in your app. ## Usage [![Open in Gitpod](https://img.shields.io/badge/Open%20In-Gitpod.io-%231966D2?style=for-the-badge&logo=gitpod)](https://gitpod.io/#https://github.com/shadowwalker/next-pwa/) -``` bash +```bash cd examples/next-image yarn install yarn build @@ -23,6 +23,3 @@ yarn start **/public/workbox-*.js **/public/sw.js ``` - - - diff --git a/examples/next-image/next.config.js b/examples/next-image/next.config.js index c9f0fbec..5d6a902e 100644 --- a/examples/next-image/next.config.js +++ b/examples/next-image/next.config.js @@ -1,7 +1,5 @@ -const withPWA = require('next-pwa') - -module.exports = withPWA({ - pwa: { - dest: 'public' - } +const withPWA = require('next-pwa')({ + dest: 'public' }) + +module.exports = withPWA() diff --git a/examples/offline-fallback-v2/next.config.js b/examples/offline-fallback-v2/next.config.js index d2f6b65c..8c726ade 100644 --- a/examples/offline-fallback-v2/next.config.js +++ b/examples/offline-fallback-v2/next.config.js @@ -1,17 +1,16 @@ -const withPWA = require('next-pwa') +const withPWA = require('next-pwa')({ + dest: 'public', + fallbacks: { + image: '/static/images/fallback.png' + // document: '/other-offline', // if you want to fallback to a custom page other than /_offline + // font: '/static/font/fallback.woff2', + // audio: ..., + // video: ..., + } +}) module.exports = withPWA({ images: { domains: ['source.unsplash.com'] - }, - pwa: { - dest: 'public', - fallbacks: { - image: '/static/images/fallback.png' - // document: '/other-offline', // if you want to fallback to a custom page other than /_offline - // font: '/static/font/fallback.woff2', - // audio: ..., - // video: ..., - } } }) diff --git a/examples/offline-fallback/next.config.js b/examples/offline-fallback/next.config.js index ec5d325d..29e494f8 100644 --- a/examples/offline-fallback/next.config.js +++ b/examples/offline-fallback/next.config.js @@ -1,11 +1,10 @@ -const withPWA = require('next-pwa') +const withPWA = require('next-pwa')({ + dest: 'public', + swSrc: 'service-worker.js' +}) module.exports = withPWA({ images: { domains: ['source.unsplash.com'] - }, - pwa: { - dest: 'public', - swSrc: 'service-worker.js' } }) diff --git a/examples/web-push/next.config.js b/examples/web-push/next.config.js index c9f0fbec..5d6a902e 100644 --- a/examples/web-push/next.config.js +++ b/examples/web-push/next.config.js @@ -1,7 +1,5 @@ -const withPWA = require('next-pwa') - -module.exports = withPWA({ - pwa: { - dest: 'public' - } +const withPWA = require('next-pwa')({ + dest: 'public' }) + +module.exports = withPWA() diff --git a/index.js b/index.js index 2b6e4807..cc936682 100644 --- a/index.js +++ b/index.js @@ -12,334 +12,330 @@ const buildFallbackWorker = require('./build-fallback-worker') const getRevision = file => crypto.createHash('md5').update(fs.readFileSync(file)).digest('hex') -module.exports = (nextConfig = {}) => { - const pwaNextConfig = { - ...nextConfig, - webpack(config, options) { - const { - webpack, - buildId, - dev, - config: { - distDir = '.next', - pwa = nextConfig.pwa, - pageExtensions = ['tsx', 'ts', 'jsx', 'js', 'mdx'], - experimental = {} - } - } = options - - let basePath = options.config.basePath - if (!basePath) basePath = '/' - - // For workbox configurations: - // https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-webpack-plugin.GenerateSW - const { - disable = false, - register = true, - dest = distDir, - sw = 'sw.js', - cacheStartUrl = true, - dynamicStartUrl = true, - dynamicStartUrlRedirect, - skipWaiting = true, - clientsClaim = true, - cleanupOutdatedCaches = true, - additionalManifestEntries, - ignoreURLParametersMatching = [], - importScripts = [], - publicExcludes = ['!noprecache/**/*'], - buildExcludes = [], - modifyURLPrefix = {}, - manifestTransforms = [], - fallbacks = {}, - cacheOnFrontEndNav = false, - reloadOnOnline = true, - scope = basePath, - customWorkerDir = 'worker', - subdomainPrefix, // deprecated, use basePath in next.config.js instead - ...workbox - } = pwa - - if (typeof nextConfig.webpack === 'function') { - config = nextConfig.webpack(config, options) - } - - if (disable) { - options.isServer && console.log('> [PWA] PWA support is disabled') - return config - } - - if (subdomainPrefix) { - console.error( - '> [PWA] subdomainPrefix is deprecated, use basePath in next.config.js instead: https://nextjs.org/docs/api-reference/next.config.js/basepath' - ) - } - - console.log(`> [PWA] Compile ${options.isServer ? 'server' : 'client (static)'}`) - - let { runtimeCaching = defaultCache } = pwa - const _scope = path.posix.join(scope, '/') +module.exports = + (pluginOptions = {}) => + (nextConfig = {}) => + Object.assign({}, nextConfig, { + webpack(config, options) { + const { + webpack, + buildId, + dev, + config: { distDir = '.next', pageExtensions = ['tsx', 'ts', 'jsx', 'js', 'mdx'], experimental = {} } + } = options - // inject register script to main.js - const _sw = path.posix.join(basePath, sw.startsWith('/') ? sw : `/${sw}`) - config.plugins.push( - new webpack.DefinePlugin({ - __PWA_SW__: `'${_sw}'`, - __PWA_SCOPE__: `'${_scope}'`, - __PWA_ENABLE_REGISTER__: `${Boolean(register)}`, - __PWA_START_URL__: dynamicStartUrl ? `'${basePath}'` : undefined, - __PWA_CACHE_ON_FRONT_END_NAV__: `${Boolean(cacheOnFrontEndNav)}`, - __PWA_RELOAD_ON_ONLINE__: `${Boolean(reloadOnOnline)}` - }) - ) + let basePath = options.config.basePath + if (!basePath) basePath = '/' - const registerJs = path.join(__dirname, 'register.js') - const entry = config.entry - config.entry = () => - entry().then(entries => { - if (entries['main.js'] && !entries['main.js'].includes(registerJs)) { - entries['main.js'].unshift(registerJs) - } - return entries - }) + // For workbox configurations: + // https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-webpack-plugin.GenerateSW + const { + disable = false, + register = true, + dest = distDir, + sw = 'sw.js', + cacheStartUrl = true, + dynamicStartUrl = true, + dynamicStartUrlRedirect, + skipWaiting = true, + clientsClaim = true, + cleanupOutdatedCaches = true, + additionalManifestEntries, + ignoreURLParametersMatching = [], + importScripts = [], + publicExcludes = ['!noprecache/**/*'], + buildExcludes = [], + modifyURLPrefix = {}, + manifestTransforms = [], + fallbacks = {}, + cacheOnFrontEndNav = false, + reloadOnOnline = true, + scope = basePath, + customWorkerDir = 'worker', + subdomainPrefix, // deprecated, use basePath in next.config.js instead + ...workbox + } = pluginOptions - if (!options.isServer) { - const _dest = path.join(options.dir, dest) - const customWorkerScriptName = buildCustomWorker({ - id: buildId, - basedir: options.dir, - customWorkerDir, - destdir: _dest, - plugins: config.plugins.filter(plugin => plugin instanceof webpack.DefinePlugin), - minify: !dev - }) + if (typeof nextConfig.webpack === 'function') { + config = nextConfig.webpack(config, options) + } - if (!!customWorkerScriptName) { - importScripts.unshift(customWorkerScriptName) + if (disable) { + options.isServer && console.log('> [PWA] PWA support is disabled') + return config } - if (register) { - console.log(`> [PWA] Auto register service worker with: ${path.resolve(registerJs)}`) - } else { - console.log( - `> [PWA] Auto register service worker is disabled, please call following code in componentDidMount callback or useEffect hook` + if (subdomainPrefix) { + console.error( + '> [PWA] subdomainPrefix is deprecated, use basePath in next.config.js instead: https://nextjs.org/docs/api-reference/next.config.js/basepath' ) - console.log(`> [PWA] window.workbox.register()`) } - console.log(`> [PWA] Service worker: ${path.join(_dest, sw)}`) - console.log(`> [PWA] url: ${_sw}`) - console.log(`> [PWA] scope: ${_scope}`) + console.log(`> [PWA] Compile ${options.isServer ? 'server' : 'client (static)'}`) + let { runtimeCaching = defaultCache } = pluginOptions + const _scope = path.posix.join(scope, '/') + + // inject register script to main.js + const _sw = path.posix.join(basePath, sw.startsWith('/') ? sw : `/${sw}`) config.plugins.push( - new CleanWebpackPlugin({ - cleanOnceBeforeBuildPatterns: [ - path.join(_dest, 'workbox-*.js'), - path.join(_dest, 'workbox-*.js.map'), - path.join(_dest, sw), - path.join(_dest, `${sw}.map`) - ] + new webpack.DefinePlugin({ + __PWA_SW__: `'${_sw}'`, + __PWA_SCOPE__: `'${_scope}'`, + __PWA_ENABLE_REGISTER__: `${Boolean(register)}`, + __PWA_START_URL__: dynamicStartUrl ? `'${basePath}'` : undefined, + __PWA_CACHE_ON_FRONT_END_NAV__: `${Boolean(cacheOnFrontEndNav)}`, + __PWA_RELOAD_ON_ONLINE__: `${Boolean(reloadOnOnline)}` }) ) - // precache files in public folder - let manifestEntries = additionalManifestEntries - if (!Array.isArray(manifestEntries)) { - manifestEntries = globby - .sync( - [ - '**/*', - '!workbox-*.js', - '!workbox-*.js.map', - '!worker-*.js', - '!worker-*.js.map', - '!fallback-*.js', - '!fallback-*.js.map', - `!${sw.replace(/^\/+/, '')}`, - `!${sw.replace(/^\/+/, '')}.map`, - ...publicExcludes - ], - { - cwd: 'public' - } - ) - .map(f => ({ - url: path.posix.join(basePath, `/${f}`), - revision: getRevision(`public/${f}`) - })) - } - - if (cacheStartUrl) { - if (!dynamicStartUrl) { - manifestEntries.push({ - url: basePath, - revision: buildId - }) - } else if (typeof dynamicStartUrlRedirect === 'string' && dynamicStartUrlRedirect.length > 0) { - manifestEntries.push({ - url: dynamicStartUrlRedirect, - revision: buildId - }) - } - } + const registerJs = path.join(__dirname, 'register.js') + const entry = config.entry + config.entry = () => + entry().then(entries => { + if (entries['main.js'] && !entries['main.js'].includes(registerJs)) { + entries['main.js'].unshift(registerJs) + } + return entries + }) - let _fallbacks = fallbacks - if (_fallbacks) { - const res = buildFallbackWorker({ + if (!options.isServer) { + const _dest = path.join(options.dir, dest) + const customWorkerScriptName = buildCustomWorker({ id: buildId, - fallbacks, basedir: options.dir, + customWorkerDir, destdir: _dest, - minify: !dev, - pageExtensions + plugins: config.plugins.filter(plugin => plugin instanceof webpack.DefinePlugin), + minify: !dev }) - if (res) { - _fallbacks = res.fallbacks - importScripts.unshift(res.name) - res.precaches.forEach(route => { - if (!manifestEntries.find(entry => entry.url.startsWith(route))) { - manifestEntries.push({ - url: route, - revision: buildId - }) - } - }) + if (!!customWorkerScriptName) { + importScripts.unshift(customWorkerScriptName) + } + + if (register) { + console.log(`> [PWA] Auto register service worker with: ${path.resolve(registerJs)}`) } else { - _fallbacks = undefined + console.log( + `> [PWA] Auto register service worker is disabled, please call following code in componentDidMount callback or useEffect hook` + ) + console.log(`> [PWA] window.workbox.register()`) } - } - const workboxCommon = { - swDest: path.join(_dest, sw), - additionalManifestEntries: dev ? [] : manifestEntries, - exclude: [ - ...buildExcludes, - ({ asset, compilation }) => { - if ( - asset.name.startsWith('server/') || - asset.name.match(/^(build-manifest\.json|react-loadable-manifest\.json)$/) - ) { - return true - } - if (dev && !asset.name.startsWith('static/runtime/')) { - return true - } - if (experimental.modern /* modern */) { - if (asset.name.endsWith('.module.js')) { - return false - } - if (asset.name.endsWith('.js')) { - return true - } - } - return false - } - ], - modifyURLPrefix: { - ...modifyURLPrefix, - '/_next/../public/': '/' - }, - manifestTransforms: [ - ...manifestTransforms, - async (manifestEntries, compilation) => { - const manifest = manifestEntries.map(m => { - m.url = m.url.replace('/_next//static/image', '/_next/static/image') - m.url = m.url.replace('/_next//static/media', '/_next/static/media') - if (m.revision === null) { - let key = m.url - if (key.startsWith(config.output.publicPath)) { - key = m.url.substring(config.output.publicPath.length) - } - const assset = compilation.assetsInfo.get(key) - m.revision = assset ? assset.contenthash || buildId : buildId + console.log(`> [PWA] Service worker: ${path.join(_dest, sw)}`) + console.log(`> [PWA] url: ${_sw}`) + console.log(`> [PWA] scope: ${_scope}`) + + config.plugins.push( + new CleanWebpackPlugin({ + cleanOnceBeforeBuildPatterns: [ + path.join(_dest, 'workbox-*.js'), + path.join(_dest, 'workbox-*.js.map'), + path.join(_dest, sw), + path.join(_dest, `${sw}.map`) + ] + }) + ) + + // precache files in public folder + let manifestEntries = additionalManifestEntries + if (!Array.isArray(manifestEntries)) { + manifestEntries = globby + .sync( + [ + '**/*', + '!workbox-*.js', + '!workbox-*.js.map', + '!worker-*.js', + '!worker-*.js.map', + '!fallback-*.js', + '!fallback-*.js.map', + `!${sw.replace(/^\/+/, '')}`, + `!${sw.replace(/^\/+/, '')}.map`, + ...publicExcludes + ], + { + cwd: 'public' } - m.url = m.url.replace(/\[/g, '%5B').replace(/\]/g, '%5D') - return m + ) + .map(f => ({ + url: path.posix.join(basePath, `/${f}`), + revision: getRevision(`public/${f}`) + })) + } + + if (cacheStartUrl) { + if (!dynamicStartUrl) { + manifestEntries.push({ + url: basePath, + revision: buildId + }) + } else if (typeof dynamicStartUrlRedirect === 'string' && dynamicStartUrlRedirect.length > 0) { + manifestEntries.push({ + url: dynamicStartUrlRedirect, + revision: buildId }) - return { manifest, warnings: [] } } - ] - } + } - if (workbox.swSrc) { - const swSrc = path.join(options.dir, workbox.swSrc) - console.log(`> [PWA] Inject manifest in ${swSrc}`) - config.plugins.push( - new WorkboxPlugin.InjectManifest({ - ...workboxCommon, - ...workbox, - swSrc + let _fallbacks = fallbacks + if (_fallbacks) { + const res = buildFallbackWorker({ + id: buildId, + fallbacks, + basedir: options.dir, + destdir: _dest, + minify: !dev, + pageExtensions }) - ) - } else { - if (dev) { - console.log( - '> [PWA] Build in develop mode, cache and precache are mostly disabled. This means offline support is disabled, but you can continue developing other functions in service worker.' - ) - ignoreURLParametersMatching.push(/ts/) - runtimeCaching = [ - { - urlPattern: /.*/i, - handler: 'NetworkOnly', - options: { - cacheName: 'dev' + if (res) { + _fallbacks = res.fallbacks + importScripts.unshift(res.name) + res.precaches.forEach(route => { + if (!manifestEntries.find(entry => entry.url.startsWith(route))) { + manifestEntries.push({ + url: route, + revision: buildId + }) } - } - ] + }) + } else { + _fallbacks = undefined + } } - if (dynamicStartUrl) { - runtimeCaching.unshift({ - urlPattern: basePath, - handler: 'NetworkFirst', - options: { - cacheName: 'start-url', - plugins: [ - { - cacheWillUpdate: async ({ request, response, event, state }) => { - if (response && response.type === 'opaqueredirect') { - return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) - } - return response + const workboxCommon = { + swDest: path.join(_dest, sw), + additionalManifestEntries: dev ? [] : manifestEntries, + exclude: [ + ...buildExcludes, + ({ asset, compilation }) => { + if ( + asset.name.startsWith('server/') || + asset.name.match(/^(build-manifest\.json|react-loadable-manifest\.json)$/) + ) { + return true + } + if (dev && !asset.name.startsWith('static/runtime/')) { + return true + } + if (experimental.modern /* modern */) { + if (asset.name.endsWith('.module.js')) { + return false + } + if (asset.name.endsWith('.js')) { + return true + } + } + return false + } + ], + modifyURLPrefix: { + ...modifyURLPrefix, + '/_next/../public/': '/' + }, + manifestTransforms: [ + ...manifestTransforms, + async (manifestEntries, compilation) => { + const manifest = manifestEntries.map(m => { + m.url = m.url.replace('/_next//static/image', '/_next/static/image') + m.url = m.url.replace('/_next//static/media', '/_next/static/media') + if (m.revision === null) { + let key = m.url + if (key.startsWith(config.output.publicPath)) { + key = m.url.substring(config.output.publicPath.length) } + const assset = compilation.assetsInfo.get(key) + m.revision = assset ? assset.contenthash || buildId : buildId } - ] + m.url = m.url.replace(/\[/g, '%5B').replace(/\]/g, '%5D') + return m + }) + return { manifest, warnings: [] } } - }) + ] } - if (_fallbacks) { - runtimeCaching.forEach(c => { - if (c.options.precacheFallback) return - if (Array.isArray(c.options.plugins) && c.options.plugins.find(p => 'handlerDidError' in p)) return - if (!c.options.plugins) c.options.plugins = [] - c.options.plugins.push({ - handlerDidError: async ({ request }) => self.fallback(request) + if (workbox.swSrc) { + const swSrc = path.join(options.dir, workbox.swSrc) + console.log(`> [PWA] Inject manifest in ${swSrc}`) + config.plugins.push( + new WorkboxPlugin.InjectManifest({ + ...workboxCommon, + ...workbox, + swSrc }) - }) - } + ) + } else { + if (dev) { + console.log( + '> [PWA] Build in develop mode, cache and precache are mostly disabled. This means offline support is disabled, but you can continue developing other functions in service worker.' + ) - config.plugins.push( - new WorkboxPlugin.GenerateSW({ - ...workboxCommon, - skipWaiting, - clientsClaim, - cleanupOutdatedCaches, - ignoreURLParametersMatching, - importScripts, - ...workbox, - runtimeCaching - }) - ) + ignoreURLParametersMatching.push(/ts/) + runtimeCaching = [ + { + urlPattern: /.*/i, + handler: 'NetworkOnly', + options: { + cacheName: 'dev' + } + } + ] + } + + if (dynamicStartUrl) { + runtimeCaching.unshift({ + urlPattern: basePath, + handler: 'NetworkFirst', + options: { + cacheName: 'start-url', + plugins: [ + { + cacheWillUpdate: async ({ request, response, event, state }) => { + if (response && response.type === 'opaqueredirect') { + return new Response(response.body, { + status: 200, + statusText: 'OK', + headers: response.headers + }) + } + return response + } + } + ] + } + }) + } + + if (_fallbacks) { + runtimeCaching.forEach(c => { + if (c.options.precacheFallback) return + if (Array.isArray(c.options.plugins) && c.options.plugins.find(p => 'handlerDidError' in p)) return + if (!c.options.plugins) c.options.plugins = [] + c.options.plugins.push({ + handlerDidError: async ({ request }) => self.fallback(request) + }) + }) + } + + config.plugins.push( + new WorkboxPlugin.GenerateSW({ + ...workboxCommon, + skipWaiting, + clientsClaim, + cleanupOutdatedCaches, + ignoreURLParametersMatching, + importScripts, + ...workbox, + runtimeCaching + }) + ) + } } - } - return config - } - } - // Next 12.2.3+ logs errors if the `pwa` field is returned since it is not recognized - delete pwaNextConfig.pwa - return pwaNextConfig -} + return config + } + }) diff --git a/package.json b/package.json index fed8d902..3d6f4064 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next-pwa", - "version": "5.5.6", + "version": "5.6.0", "description": "Next.js with PWA, powered by workbox.", "main": "index.js", "repository": "https://github.com/shadowwalker/next-pwa",