Use precache manifest generated by webpack plugin (#2464)
This commit is contained in:
parent
de463e1f99
commit
8b139f08da
@ -1,6 +1,6 @@
|
||||
const { withPlausibleProxy } = require('next-plausible')
|
||||
const { InjectManifest } = require('workbox-webpack-plugin')
|
||||
const { generatePrecacheManifest } = require('./sw/build.js')
|
||||
const CopyPlugin = require('copy-webpack-plugin')
|
||||
const webpack = require('webpack')
|
||||
|
||||
let isProd = process.env.NODE_ENV === 'production'
|
||||
@ -215,15 +215,24 @@ module.exports = withPlausibleProxy()({
|
||||
},
|
||||
webpack: (config, { isServer, dev, defaultLoaders }) => {
|
||||
if (isServer) {
|
||||
generatePrecacheManifest()
|
||||
const workboxPlugin = new InjectManifest({
|
||||
// ignore the precached manifest which includes the webpack assets
|
||||
// since they are not useful to us
|
||||
exclude: [/.*/],
|
||||
// by default, webpack saves service worker at .next/server/
|
||||
include: [/\/(icons|maskable|splash)\//, /\.(webp|ttf|woff|woff2)$/],
|
||||
swDest: '../../public/sw.js',
|
||||
swSrc: './sw/index.js',
|
||||
webpackCompilationPlugins: [
|
||||
// we want to precache these static assets so we copy them to include them in the webpack pipeline
|
||||
// so InjectManifest can inject them into the service worker manifest
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{ from: 'public/icons', to: '../icons' },
|
||||
{ from: 'public/maskable', to: '../maskable' },
|
||||
{ from: 'public/splash', to: '../splash' },
|
||||
{ from: 'public/waiting.webp', to: '../waiting.webp' },
|
||||
{ from: 'public/Lightningvolt-xoqm.ttf', to: '../Lightningvolt-xoqm.ttf' },
|
||||
{ from: 'public/Lightningvolt-xoqm.woff', to: '../Lightningvolt-xoqm.woff' },
|
||||
{ from: 'public/Lightningvolt-xoqm.woff2', to: '../Lightningvolt-xoqm.woff2' }
|
||||
]
|
||||
}),
|
||||
// this is need to allow the service worker to access these environment variables
|
||||
// from lib/constants.js
|
||||
new webpack.DefinePlugin({
|
||||
|
124
package-lock.json
generated
124
package-lock.json
generated
@ -114,6 +114,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/eslint-plugin-next": "^14.2.15",
|
||||
"copy-webpack-plugin": "^13.0.1",
|
||||
"eslint": "^9.12.0",
|
||||
"jest": "^29.7.0",
|
||||
"standard": "^17.1.2"
|
||||
@ -6508,6 +6509,24 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
||||
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ajv": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
@ -8048,6 +8067,63 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/copy-webpack-plugin": {
|
||||
"version": "13.0.1",
|
||||
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz",
|
||||
"integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"glob-parent": "^6.0.1",
|
||||
"normalize-path": "^3.0.0",
|
||||
"schema-utils": "^4.2.0",
|
||||
"serialize-javascript": "^6.0.2",
|
||||
"tinyglobby": "^0.2.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.12.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/copy-webpack-plugin/node_modules/ajv-keywords": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
|
||||
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ajv": "^8.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/copy-webpack-plugin/node_modules/schema-utils": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
|
||||
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"ajv": "^8.9.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"ajv-keywords": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js-compat": {
|
||||
"version": "3.38.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz",
|
||||
@ -10234,6 +10310,24 @@
|
||||
"bser": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||
@ -19677,6 +19771,36 @@
|
||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.14",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
||||
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.4.4",
|
||||
"picomatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "6.1.51",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.51.tgz",
|
||||
|
@ -130,6 +130,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/eslint-plugin-next": "^14.2.15",
|
||||
"copy-webpack-plugin": "^13.0.1",
|
||||
"eslint": "^9.12.0",
|
||||
"jest": "^29.7.0",
|
||||
"standard": "^17.1.2"
|
||||
|
61
sw/build.js
61
sw/build.js
@ -1,61 +0,0 @@
|
||||
const { createHash } = require('crypto')
|
||||
const { readdirSync, readFileSync, statSync, writeFileSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
|
||||
const getRevision = filePath => createHash('md5').update(readFileSync(filePath)).digest('hex')
|
||||
const walkSync = dir => readdirSync(dir, { withFileTypes: true }).flatMap(file =>
|
||||
file.isDirectory() ? walkSync(join(dir, file.name)) : join(dir, file.name))
|
||||
|
||||
function formatBytes (bytes, decimals = 2) {
|
||||
if (bytes === 0) {
|
||||
return '0 B'
|
||||
}
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB']
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
const formattedSize = parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))
|
||||
|
||||
return `${formattedSize} ${sizes[i]}`
|
||||
}
|
||||
|
||||
function generatePrecacheManifest () {
|
||||
const manifest = []
|
||||
let size = 0
|
||||
|
||||
const addToManifest = (filePath, url, s) => {
|
||||
const revision = getRevision(filePath)
|
||||
manifest.push({ url, revision })
|
||||
size += s
|
||||
}
|
||||
|
||||
const staticDir = join(__dirname, '../public')
|
||||
const staticFiles = walkSync(staticDir)
|
||||
const staticMatch = f => [/\.(webp|jpe?g|ico|png|ttf|woff|woff2)$/].some(m => m.test(f))
|
||||
staticFiles.filter(staticMatch).forEach(file => {
|
||||
const stats = statSync(file)
|
||||
addToManifest(file, file.slice(staticDir.length), stats.size)
|
||||
})
|
||||
|
||||
const pagesDir = join(__dirname, '../pages')
|
||||
const precacheURLs = ['/offline']
|
||||
const pagesFiles = walkSync(pagesDir)
|
||||
const fileToUrl = f => f.slice(pagesDir.length).replace(/\.js$/, '')
|
||||
const pageMatch = f => precacheURLs.some(url => fileToUrl(f) === url)
|
||||
pagesFiles.filter(pageMatch).forEach(file => {
|
||||
const stats = statSync(file)
|
||||
// This is not ideal since dependencies of the pages may have changed
|
||||
// but we would still generate the same revision ...
|
||||
// The ideal solution would be to create a revision from the file generated by webpack
|
||||
// in .next/server/pages but the file may not exist yet when we run this script
|
||||
addToManifest(file, fileToUrl(file), stats.size)
|
||||
})
|
||||
|
||||
const output = 'sw/precache-manifest.json'
|
||||
writeFileSync(output, JSON.stringify(manifest, null, 2))
|
||||
|
||||
console.log(`Created precache manifest at ${output}. Cache will include ${manifest.length} URLs with a size of ${formatBytes(size)}.`)
|
||||
}
|
||||
|
||||
module.exports = { generatePrecacheManifest }
|
19
sw/index.js
19
sw/index.js
@ -5,7 +5,6 @@ import { setDefaultHandler } from 'workbox-routing'
|
||||
import { NetworkOnly } from 'workbox-strategies'
|
||||
import { enable } from 'workbox-navigation-preload'
|
||||
|
||||
import manifest from './precache-manifest.json'
|
||||
import ServiceWorkerStorage from 'serviceworker-storage'
|
||||
import { CLEAR_NOTIFICATIONS, DELETE_SUBSCRIPTION, STORE_SUBSCRIPTION } from '@/components/serviceworker'
|
||||
|
||||
@ -19,11 +18,19 @@ self.__WB_DISABLE_DEV_LOGS = true
|
||||
// https://developer.chrome.com/docs/workbox/modules/workbox-navigation-preload/
|
||||
enable()
|
||||
|
||||
// ignore precache manifest generated by InjectManifest
|
||||
// they statically check for the presence of this variable
|
||||
console.log(self.__WB_MANIFEST)
|
||||
// precache the manifest we generated ourselves
|
||||
precacheAndRoute(manifest)
|
||||
precacheAndRoute(self.__WB_MANIFEST,
|
||||
{
|
||||
// this returns url fallbacks, we map CDN urls to the origin because that's how they are cached
|
||||
// source: https://github.com/GoogleChrome/workbox/blob/e26d8d7507f9412ba029922f3d9920e68710f2cf/packages/workbox-precaching/src/utils/generateURLVariations.ts#L54-L59
|
||||
urlManipulation: ({ url }) => {
|
||||
if (url.hostname === 'a.stacker.news') {
|
||||
const newUrl = new URL(url.pathname, 'https://stacker.news')
|
||||
return [newUrl]
|
||||
}
|
||||
return [url]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// immediately replace existing service workers with this one
|
||||
// (no wait until this one becomes active)
|
||||
|
Loading…
x
Reference in New Issue
Block a user