|
| 1 | +// Bundles the service worker (src/sw.ts -> public/sw.js) and stamps it with a |
| 2 | +// per-build id via esbuild `define`. Without a build-varying value the output is |
| 3 | +// byte-identical across builds, so browsers never see a "new" service worker and |
| 4 | +// the in-app "update available" prompt (SwUpdateNotice) never fires. The id is |
| 5 | +// woven into the app-shell cache name in sw.ts, so it must change every deploy. |
| 6 | +// |
| 7 | +// Source of the id, in priority order: |
| 8 | +// 1. SW_BUILD_ID env var (CI passes the git SHA as a Docker build arg) |
| 9 | +// 2. the local git short SHA (dev builds) |
| 10 | +// 3. a timestamp fallback (no env, no git) |
| 11 | +import { execFileSync } from "node:child_process"; |
| 12 | +import { build } from "esbuild"; |
| 13 | + |
| 14 | +export function resolveSwBuildId(env = process.env) { |
| 15 | + const fromEnv = (env.SW_BUILD_ID ?? "").trim(); |
| 16 | + if (fromEnv) return fromEnv; |
| 17 | + try { |
| 18 | + // Fixed argv, no shell — nothing interpolated. |
| 19 | + return execFileSync("git", ["rev-parse", "--short", "HEAD"], { |
| 20 | + stdio: ["ignore", "pipe", "ignore"], |
| 21 | + }) |
| 22 | + .toString() |
| 23 | + .trim(); |
| 24 | + } catch { |
| 25 | + return `dev-${Date.now()}`; |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +if (process.argv[1]?.endsWith("build-sw.mjs")) { |
| 30 | + const buildId = resolveSwBuildId(); |
| 31 | + await build({ |
| 32 | + entryPoints: ["src/sw.ts"], |
| 33 | + bundle: true, |
| 34 | + outfile: "public/sw.js", |
| 35 | + format: "iife", |
| 36 | + platform: "browser", |
| 37 | + define: { |
| 38 | + "process.env.NODE_ENV": '"production"', |
| 39 | + __SW_BUILD_ID__: JSON.stringify(buildId), |
| 40 | + }, |
| 41 | + }); |
| 42 | + console.log(`[build:sw] public/sw.js built (build id: ${buildId})`); |
| 43 | +} |
0 commit comments