レガシーPHPリファクタ 12:JSファイルのキャッシュバスター導入とハッシュ選定の理由

レガシーPHP/Smartyの保守・改修を任されてしまったエンジニアの皆さん、日々のリファクタリングお疲れ様です。

レガシーなWebアプリケーションでは、JavaScriptファイルを改修してデプロイした際、ブラウザのキャッシュが残ってしまい、ユーザーの環境で古いスクリプトが動き続けるというトラブルがよく発生します。

Web開発者自身も、JavaScriptを変更したのにブラウザへ反映されず、デベロッパーツールを開いて強制リロード(ハードリロード)した経験は多いのではないでしょうか。

今回は、リポジトリ(smartybook)のPR #625 で実施した「JSファイルのキャッシュバスター(クエリパラメータによるバージョン管理)」の導入事例をご紹介します。


1. 修正前:キャッシュバスターのない状態

修正前は、テンプレートファイル(.tpl)に直接JSファイルのパスが記述されていました。

<script type="module" src="./js/sortNavigator.js"></script>

これでは、sortNavigator.js の中身をどれだけ書き換えても、ブラウザがキャッシュを保持している限り古いファイルが読み込まれてしまいます。


2. 修正後:PHP側でコンテンツハッシュを生成し、Smartyに渡す

ファイルの内容が変更されたことを確実に検知できるよう、PHP側でファイルのハッシュ値を算出し、Smartyを経由してテンプレートに渡す方式に変更しました。

PHP側の実装

$jsPath = __DIR__ . '/js/sortNavigator.js';
$jsVersion = file_exists($jsPath) ? substr(sha1_file($jsPath), 0, 8) : '1.0.0';
$smarty->assign('jsVersion', $jsVersion);

テンプレート側の実装

<script type="module" src="./js/sortNavigator.js?v={$jsVersion}"></script>

ポイント:なぜ sha1_file() の「先頭8文字」なのか?

  • SHA-1ハッシュの利用: ファイルの中身(内容)を元にハッシュ値を算出するため、コードが1文字でも変わればハッシュ値が変わり、キャッシュが確実にクリアされます。
  • 8文字への切り詰め: フルハッシュ(40文字)はURLが長くなりすぎて見通しが悪くなります。Gitのショートコミットハッシュと同様に、先頭8文字に切り詰めることで、一意性を保ちつつスッキリとした記述にしています。

3. なぜこの方法を選んだか?(他の方法との比較)

1 手作業でバージョン(?v=1 や ?v=20261026 など)をつける方法

  • 課題・デメリット:
  • JSファイルを修正するたびに手動でバージョンを書き換える必要があり、ときどき修正漏れが起こります。
  • 「連番がいいのか」「日時(タイムスタンプ)がいいのか」「日時はどの程度まで正確に表現すればいいのか(秒単位か、日単位か)」といった運用ルールを決めて維持し続けるコストがかかります。
  • 今回の方法のメリット: PHP側で自動的にハッシュを生成するため、人間の手による更新漏れやルール決めの悩みが一切なくなります。

2 Smartyプラグインでテンプレート側でハッシュ算出する方法

テンプレート側でハッシュを計算するカスタムプラグインを用意するアプローチです。 テンプレートの中に「JSファイルの物理的なファイルパス」を持ち込むことになります。

関心の分離(テンプレートは描画に専念すべき)の観点から、PHP側で準備してアサインする方が責務が明確だと判断しました。

3 「ファイルサイズ」をバージョンにする方法

ファイルサイズが変わっていなくても中身が変更されているケース(例: 変数名のリネームやロジックの微修正など)があります。 中身が変わっているのに、古いキャッシュを表示してしまいます。

4 「ファイルタイムスタンプ (filemtime)」を利用する方法

手軽によく使われる手法ですが、「デプロイするとタイムスタンプが更新されてしまう」という問題があります。 中身が1バイトも変わっていないのに、全ユーザーのキャッシュが効きません。

5 デプロイ時にスクリプトを走らせて ?v=xxx をつける

  • 課題・デメリット:
    • 作った人以外はメンテナンスできない: 独自の書き換えスクリプトやビルドのハックが増えると、担当者が変わったときにブラックボックス化し、トラブル時に誰も直せなくなります。
    • ローカル環境と本番環境のHTMLが一致しない: JSキャッシュバスターをつける・つけないだけなので、これだけなら大きな問題にはなりませんが、このようなデプロイプロセスが積み重なるとデバッグ困難になる可能性があります 。
  • 今回の方法のメリット: ローカルでも本番でも全く同じコード・同じロジックで動的にハッシュが生成されるため、環境差異による無駄なデバッグ工数をゼロにできます。

4. まとめ

今回の改修では、キャッシュバスターとしてコンテンツハッシュを導入しました。これにより、以下のメリットが得られます。

  • 実装担当者の負担軽減: ?v= を手動で書き換えるトイル(作業負荷)から解放されます。
  • レビューコストの削減: AIを含むレビュアーが「?v= が適切に更新されたか」をわざわざ確認・チェックする必要がなくなります。
  • デプロイの安全性向上: ?v= の更新漏れによる「変更が反映されない」といったデプロイ時のキャッシュトラブルを確実に防ぐことができます。

参考PR

feat(chapter4_6): add content-hash cache buster for sortNavigator.js by ninton · Pull Request #625 · ninton/smartybook
概要『Smarty動的Webサイト構築入門』Chapter 4 のサンプルコードリファクタリングの PR3 です。ビルドツールを導入していないレガシーなPHP/Smarty環境において、前回の PR2 で追加した js/sortNaviga...
レガシーPHPリファクタ 12:JSファイルのキャッシュバスター導入とハッシュ選定の理由
Smarty動的Webサイト構築入門サンプルソース. Contribute to ninton/smartybook development by creating an account on GitHub.
タイトルとURLをコピーしました