Keep the service worker's cache to the current build #380

Closed
opened 2026-09-16 15:58:59 +00:00 by jcoffey-dev · 0 comments
Owner

Summary

The service worker cached build assets on first use, and it had two problems.

  • Every deploy's chunks stayed forever. The cache name is fixed and activate only removes caches with other names, so nothing ever removed old entries from this one.
  • It cached whatever came back, errors included. The server answers a missing asset with a 404. If a chunk was requested while a deploy was switching over, that 404 was cached under the chunk's name, and the chunk stayed broken in that browser. Prod is deployed often.

What changes in web/public/sw.js:

  • Only real files are cached. An asset is stored only when it came back successfully from our own origin (res.ok && res.type === "basic").
  • The offline copy of the app page stays current. A successful page load that returns text/html replaces the cached copy. When the page has changed, tidy() removes the asset files it no longer references and any stored error responses.
    • A lazy chunk the page doesn't reference is removed too. It's downloaded again the next time it's needed; a chunk whose hash didn't change is still on the server.
  • Existing browsers get cleaned up. activate runs the same tidy step, and this worker is new code, so it activates in every browser and clears out what older workers left behind.
  • The cache name is unchanged (ihasmail-v2). The same cache holds what the worker leaves for a tab to collect: a push verification, a share, and the saved account details it uses for notifications. Renaming it would throw those away with the old files.

Trade-off: a tab still running an older build can lose a chunk from the cache when another tab loads the new build. That's only a chunk the old tab never loaded itself, and it then fails the same way it would have without a service worker. The existing stale-build check handles that case.

Related issues

None.

Translations

Adds none.

Testing

sw.js is copied into the build unchanged and has no unit tests, so I checked it in Chrome against the built app served by npm run dev:mock on port 8080:

  1. Build A: loaded the app, and the new worker activated. Asking for /assets/NeverBuilt-0000.js got a 404, which wasn't cached.
  2. Old leftovers: planted what older workers leave: a chunk from a previous build and a 404 stored as a chunk.
  3. Build B: rebuilt with a temporary marker, so the file hashes and sw.js changed, then reloaded.
    • The page ran build B.
    • All 15 cached assets were ones the new page references.
    • No build A leftovers and neither planted entry remained.
    • The new worker was active, with nothing waiting.
  4. Offline: stopped the server and loaded /mail/inbox. The app page and its assets still loaded from the cache.

I then removed the temporary marker, rebuilt, and cleared the test browser's worker and caches. npm test -w web passes (1,318 tests), and the build is clean.

Merged 2026-09-16 as coffey-labs/ihasmail@460760ba12

Rebuilt from: git history, session transcript.

## Summary The service worker cached build assets on first use, and it had two problems. - **Every deploy's chunks stayed forever.** The cache name is fixed and `activate` only removes caches with other names, so nothing ever removed old entries from this one. - **It cached whatever came back, errors included.** The server answers a missing asset with a 404. If a chunk was requested while a deploy was switching over, that 404 was cached under the chunk's name, and the chunk stayed broken in that browser. Prod is deployed often. **What changes in `web/public/sw.js`:** - **Only real files are cached.** An asset is stored only when it came back successfully from our own origin (`res.ok && res.type === "basic"`). - **The offline copy of the app page stays current.** A successful page load that returns `text/html` replaces the cached copy. When the page has changed, `tidy()` removes the asset files it no longer references and any stored error responses. - A lazy chunk the page doesn't reference is removed too. It's downloaded again the next time it's needed; a chunk whose hash didn't change is still on the server. - **Existing browsers get cleaned up.** `activate` runs the same tidy step, and this worker is new code, so it activates in every browser and clears out what older workers left behind. - **The cache name is unchanged (`ihasmail-v2`).** The same cache holds what the worker leaves for a tab to collect: a push verification, a share, and the saved account details it uses for notifications. Renaming it would throw those away with the old files. **Trade-off:** a tab still running an older build can lose a chunk from the cache when another tab loads the new build. That's only a chunk the old tab never loaded itself, and it then fails the same way it would have without a service worker. The existing stale-build check handles that case. ## Related issues None. ## Translations Adds none. ## Testing `sw.js` is copied into the build unchanged and has no unit tests, so I checked it in Chrome against the built app served by `npm run dev:mock` on port 8080: 1. **Build A:** loaded the app, and the new worker activated. Asking for `/assets/NeverBuilt-0000.js` got a 404, **which wasn't cached**. 2. **Old leftovers:** planted what older workers leave: a chunk from a previous build and a 404 stored as a chunk. 3. **Build B:** rebuilt with a temporary marker, so the file hashes and `sw.js` changed, then reloaded. - The page ran build B. - All 15 cached assets were ones the new page references. - **No build A leftovers and neither planted entry remained.** - The new worker was active, with nothing waiting. 4. **Offline:** stopped the server and loaded `/mail/inbox`. The app page and its assets still loaded from the cache. I then removed the temporary marker, rebuilt, and cleared the test browser's worker and caches. `npm test -w web` passes (1,318 tests), and the build is clean. **Merged** 2026-09-16 as coffey-labs/ihasmail@460760ba1234 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.