fix(build): derive sw.js cache tag from bundle content hash
The service worker uses staleWhileRevalidate on /dist/*, so after a release it would serve old bundles from cache indefinitely (cache is only wiped when the cache *name* changes, which was hardcoded to 'dashcaddy-shell-v10'). Result: dashboard appears unchanged after a self-update until the user manually unregisters the SW. build.js now hashes the concatenated dist bundles and writes `dashcaddy-shell-<10-hex-chars>` into sw.js. Any change in dist/ produces a fresh cache name; on the next page load the SW's activate handler deletes all older caches and the new bundles are fetched.
This commit is contained in:
+25
-1
@@ -6,6 +6,7 @@ const esbuild = require('esbuild');
|
|||||||
const JS = (...parts) => path.join(__dirname, 'js', ...parts);
|
const JS = (...parts) => path.join(__dirname, 'js', ...parts);
|
||||||
const DIST = path.join(__dirname, 'dist');
|
const DIST = path.join(__dirname, 'dist');
|
||||||
const INDEX_HTML = path.join(__dirname, 'index.html');
|
const INDEX_HTML = path.join(__dirname, 'index.html');
|
||||||
|
const SW_JS = path.join(__dirname, 'sw.js');
|
||||||
|
|
||||||
// Bundle definitions — files are concatenated in order, then minified
|
// Bundle definitions — files are concatenated in order, then minified
|
||||||
const bundles = {
|
const bundles = {
|
||||||
@@ -129,6 +130,7 @@ async function build() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cspHash = updateInlineScriptCspHash();
|
const cspHash = updateInlineScriptCspHash();
|
||||||
|
const cacheTag = updateServiceWorkerCache();
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
console.log('\n DashCaddy Frontend Build\n');
|
console.log('\n DashCaddy Frontend Build\n');
|
||||||
@@ -143,7 +145,29 @@ async function build() {
|
|||||||
console.log(' ─────────────────────────────────────────');
|
console.log(' ─────────────────────────────────────────');
|
||||||
console.log(` ${'Total'.padEnd(18)} ${totalRaw.toFixed(1).padStart(6)} KB ${totalMin.toFixed(1).padStart(6)} KB`);
|
console.log(` ${'Total'.padEnd(18)} ${totalRaw.toFixed(1).padStart(6)} KB ${totalMin.toFixed(1).padStart(6)} KB`);
|
||||||
console.log(`\n Output: ${DIST}`);
|
console.log(`\n Output: ${DIST}`);
|
||||||
console.log(` CSP Hash: sha256-${cspHash}\n`);
|
console.log(` CSP Hash: sha256-${cspHash}`);
|
||||||
|
console.log(` SW Cache: dashcaddy-shell-${cacheTag}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrites the CACHE constant in sw.js to a tag derived from the bundle
|
||||||
|
// contents. Every change in dist/ produces a new cache name; on next load
|
||||||
|
// the SW's activate handler wipes all older caches, so users never get
|
||||||
|
// stuck on stale precached bundles after a release.
|
||||||
|
function updateServiceWorkerCache() {
|
||||||
|
const sw = fs.readFileSync(SW_JS, 'utf8');
|
||||||
|
const hash = crypto.createHash('sha256');
|
||||||
|
for (const name of Object.keys(bundles)) {
|
||||||
|
hash.update(fs.readFileSync(path.join(DIST, name)));
|
||||||
|
}
|
||||||
|
const tag = hash.digest('hex').slice(0, 10);
|
||||||
|
const updated = sw.replace(
|
||||||
|
/const CACHE = 'dashcaddy-shell-[^']+';/,
|
||||||
|
`const CACHE = 'dashcaddy-shell-${tag}';`
|
||||||
|
);
|
||||||
|
if (updated !== sw) {
|
||||||
|
fs.writeFileSync(SW_JS, updated);
|
||||||
|
}
|
||||||
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch mode
|
// Watch mode
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'dashcaddy-shell-v10';
|
const CACHE = 'dashcaddy-shell-c6600af35d';
|
||||||
const PRECACHE = [
|
const PRECACHE = [
|
||||||
'/',
|
'/',
|
||||||
'/index.html',
|
'/index.html',
|
||||||
|
|||||||
Reference in New Issue
Block a user