Skip to content

fix(sw): 安定名アセットの中身を SW のキャッシュ版数に含める (ISSUE-4) - #69

Merged
hidari merged 8 commits into
mainfrom
fix/sw-cache-version
Oct 29, 2026
Merged

hidari merged 8 commits into
mainfrom
fix/sw-cache-version

Conversation

@hidari

@hidari hidari commented Oct 29, 2026

Copy link
Copy Markdown
Owner

概要

名前を変えずに中身を差し替えるファイル(フォント・アイコン・ロゴ・favicon・manifest)を差し替えてデプロイしたとき、前に来たことがある人には、最初の 1 回だけ古いものが見えてもよいが、2 回目の表示からは必ず新しいものが見えるようにする。

起票時の前提(HTML だけの変更で版数が上がらない)は、/ が network-first でオンラインのたびにキャッシュを上書きするため実害がほぼ無かった。実際の穴は次の 3 つで、ISSUE-4 を書き直して比べた案と決定を残した。

  • /fonts/ が cache-first で、同じ名前で差し替えても SW が入れ替わるまで古いまま
  • stale-while-revalidate の裏の更新が event.waitUntil の外にあり、SW が止められると書き込まれない
  • 手書きの precache の一覧から、CSS が使う /icons/nd.svg と /icons/sb.svg が漏れていた(オフラインでこの 2 つのタグのアイコンが出ない)

Closes ISSUE-4

変更

  • precache の安定名の対象は、手書きの STABLE_PRECACHE をやめ、public/ の通常ファイルから isStablePrecache(src/client/sw-strategy.ts)で選ぶ。ドットファイル(Finder の .DS_Store など)とディレクトリは除く
  • SW のキャッシュ版数は、ハッシュ付き JS/CSS の名前と、安定名の対象それぞれの(パス, 中身の SHA-256)を並べ替えた JSON の SHA-256 の先頭 16 文字にした(src/client/sw-precache.ts)。どれか 1 つの中身が変われば sw.js が変わり、SW の入れ替わりで precache 全体が取り直される。/(HTML)は入れない
  • stale-while-revalidate の裏の更新を event.waitUntil に入れた
  • CSS・index.html・manifest が参照する安定名のファイルがすべて precache に入ることを vitest で pin した

検証

  • just check: Biome 80 ファイル、vitest 26 ファイル 523 件。just build: (!) 0 件
  • 書き捨てのコピーでのビルド: 基準 cospl-025374e458ee3015。nd.svg に 1 バイト足すと cospl-8b16f4abf5a8d870 に変わり、戻すと基準に戻る。OGP 画像だけを変えても、.DS_Store を置いても基準のまま。ビルドした sw.js に nd.svg と sb.svg が 1 件ずつ入る
  • 変異注入(1 つずつ): 述語の /fonts/ の分岐とドットファイルの除外、版数が stable を無視する・並べ替えない(各入力を単独で)・桁数を変える、の各変異で狙ったテストが赤になる。plugin が stable を渡さない変異では、nd.svg を変えても版数が変わらないことをビルドで確かめた。途中で 1 本の死んだ pin(/fonts/.DS_Store は .woff2 の判定で先に偽になっていた)を見つけ、/fonts/.hidden.woff2 に置き換えた
  • E2E(merge-gate.yml の固定コンテナ、全ブラウザ): 152 passed / 12 skipped / 0 failed。offline.spec(chromium)が新しい precache で通る

見送ったこと

  • stale-while-revalidate をやめて安定名もすべて cache-first にする案(版数に中身が入ったので重複しているという指摘)。承認した設計から外れ挙動も変わるため、この PR では採らず判断を仰ぐ
  • deploy の health check で precache の全項目が 200 かを確かめる案。E2E (production) が本番に offline.spec を当てており、addAll が失敗すれば赤くなる

起票時の前提(HTML だけの変更で版数が上がらない)は、/ が network-first でオンラインのたびにキャッシュを上書きするため実害がほぼ無かった。実際の穴は、フォントが cache-first であること、stale-while-revalidate の裏の更新が waitUntil の外にあること、手書きの precache の一覧から nd.svg と sb.svg が漏れていることだった。

比べた案と、版数に安定名アセットの中身を含める決定(案 C)を ISSUE-4 に、実装の計画を docs/superpowers/plans に置く。
安定名の公開ファイル (アイコン・favicon・manifest・woff2) を差し替えても SW の更新が走るよう、
事前キャッシュ対象を選ぶ isStablePrecache と、内容ハッシュを畳み込む cacheVersion を足す。
CSS・index.html・manifest の参照が漏れなく対象になることをテストで固定する。
public/ の安定名ファイル(アイコン・favicon・manifest・woff2 フォント)を isStablePrecache で選び、パスと内容の SHA-256 を cacheVersion に渡して版数にする。差し替えると版数が変わり、SW の更新と precache のやり直しが走る。

手書きの STABLE_PRECACHE を廃止した。これで nd.svg と sb.svg の取りこぼしもなくなり、OGP 画像や OFL.txt、llms.txt は precache に入らない。読めないファイルがあるときは理由を出してビルドを止める。reject は brandIndexHtmlPlugin と共有する。
public/icons/.DS_Store のような Finder のファイルが前方一致で precache と版数に入り、ローカルのビルドが CI とずれたり、本番で 404 になるパスで SW の install が失敗したりするおそれがあった。

isStablePrecache は末尾の名前が "." で始まるパスを除く。列挙は通常ファイルだけに絞り、/icons/ や /fonts/ 配下のサブディレクトリを readFile して EISDIR になる経路も塞ぐ。
stale-while-revalidate の裏の取得とキャッシュ書き込みが waitUntil に渡っておらず、応答を返した直後に SW が停止されると書き込みが完了せず、2 回目の表示で最新になる保証が崩れていた。handle に waitUntil を通し、キャッシュがあるときだけ裏の Promise を渡す。失敗は従来どおり握りつぶすため、渡す Promise は reject しない。
/fonts/.DS_Store は拡張子の判定だけで落ちるため dotfile 除外の pin にならなかった。/fonts/.hidden.woff2 に替え、dotfile の規則だけが落とす例にする。

版数の並び順のテストは、ハッシュ付きアセットと安定名ファイルを別々に入れ替える。どちらか一方の sort を外しても赤くなる。
- manifest の drift テストで JSON.parse を unknown に受け、型述語で絞る
- stale-while-revalidate の waitUntil を catch で書き直す
- cacheVersion を関数宣言にし、安定名をオブジェクトのまま path で並べ替える(版数の元の JSON 形が変わる)
- sw-strategy / sw / vite.config のコメントを実態に合わせて直す
- vite.config の readStable で、ハッシュ計算の失敗を読み取り失敗と誤報しない
- 計画書から非公開メモリへの参照を外す
タスクを全部消化したので closed/ へ移し、計画が指す Spec のパスを移動先に合わせる。計画の重複した言い回しも 1 つにまとめた。
@hidari hidari self-assigned this Oct 29, 2026
@hidari
hidari merged commit 4f0f3b4 into main Oct 29, 2026
4 checks passed
@hidari
hidari deleted the fix/sw-cache-version branch October 29, 2026 17:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant