🖼️ fix: Precache PWA Icons and Let Them Refresh (#15654)

* 🖼️ fix: Precache PWA Icons and Let Them Refresh

Production builds set `publicDir: false`, so `client/scripts/post-build.cjs`
copied `public/assets` into `dist/assets` only after `vite build` had finished.
vite-plugin-pwa globs `dist/` from its `closeBundle` hook, inside that build, so
the icon patterns in `workbox.globPatterns` matched zero files and the generated
`sw.js` shipped without a single icon, silently: workbox treats an empty glob as
a no-op. `build:ci`, used by the Playwright lanes, never ran the post-build step
at all and produced a `dist` with no icons or `robots.txt`.

Move the copy into a `copyPublicAssets()` build plugin that runs in
`writeBundle`, which completes for every plugin before `closeBundle`, and drop
the post-build script with its lone `fs-extra` dependency.

Ordering alone was not enough. The plugin defaults `dontCacheBustURLsMatching`
to `/^assets\//`, true only for hashed bundle output. The icons live in
`assets/` under stable filenames, so they entered the manifest with
`revision: null`, permanently immutable, meaning an installed PWA would never
pick up a rebranded icon: the other half of the report. Match Vite's
`[name].[hash].[ext]` shape instead so hashed chunks stay revision-free while
the icons carry content revisions.

Also drop `manifest.webmanifest` from `globPatterns`, since the plugin always
appends it as an additional manifest entry and globbing it duplicated the
precache entry.

A clean `npm run build` now yields 55 precache entries with all five icons
revisioned, against 0 icon entries before.

* 📝 docs: Describe the Full Copy Set in copyPublicAssets

* 🔒 chore: Drop fs-extra From bun.lock
This commit is contained in:
Marco Beretta 2026-09-06 17:32:09 +02:00 • committed by GitHub
parent d812e2f98f
commit fdd5f9ce65
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 50 additions and 39 deletions

View file

@ -125,7 +125,7 @@ jobs:
uses: actions/cache@v5
with:
path: client/dist
key: build-client-app-e2e-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'client/src/**', 'client/public/**', 'client/scripts/post-build.cjs', 'client/index.html', 'client/package.json', 'client/vite.config.*', 'client/tsconfig*.json', 'client/tailwind.config.*', 'client/postcss.config.*', 'packages/client/src/**', 'packages/client/tailwind.preset.cjs', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }}
key: build-client-app-e2e-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'client/src/**', 'client/public/**', 'client/index.html', 'client/package.json', 'client/vite.config.*', 'client/tsconfig*.json', 'client/tailwind.config.*', 'client/postcss.config.*', 'packages/client/src/**', 'packages/client/tailwind.preset.cjs', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }}
- name: Build client app
if: steps.cache-client-app.outputs.cache-hit != 'true'