Gulpでキャッシュ対策パラメータを自動付与する


キャッシュ回避パラメータとは

フロントエンド業務に従事している方であれば、htmlで読み込むファイル末尾に?hogeとクエリー文字列を付与することで、キャッシュが残存して更新内容が反映されないのを回避できることはご存知かと思います。いわゆるcache bustingというやつですが、日本語での正式名称がわからないので私は勝手にこう読んでいます。

index.html
<!-- 読み込んでいるのは同じファイルでも、別物として扱われる -->
<img src="/images/image.jpg">
<img src="/images/image.jpg?hoge">

画像ファイルやcss、jsなどで幅広く応用できるため、特に開発進行中にテストアップした際、手元でキャッシュの飛ばし方がわからない程度のリテラシーを持ったクライアントとのやりとりを始め、様々な場面で使用する機会の多いことかと思います。

Gulp使ってるのに手動でパラメータつける(書き換える)のが面倒くさい

一つのファイルだけならいいのですが、読み込んでいるcssやjs、画像が増えてくるといちいち書き換えて回るのが面倒ですし、ヒューマンエラーも発生します。grepで書き換えてもいいんですが、せっかくなら自動で勝手に書き換えてほしいものですし、度重なる更新のたびに同じ作業を繰り返すのも億劫です。

最近のフロントエンド開発(特に静的なhtmlのコーディングなど)ではほぼgulpが使われていることかと思いますので、こういった面倒くさいことはとりあえずgulpにやらせることを考えてみます。

npmに探しに行く

手っ取り早く既製のものがあればそれを使うのが一番はやいんじゃ?と思い、早速探しに行きました。

結論から言うと、探した限りでは下記を始め何個かのプラグインが見つかりました。

gulp-rev - npm

パラメータを付与するという形でなくファイル名を書き換えるという考え方だったので、自分の思想と違うなと思い採用を見送りました。

gulp-cache-bust - npm

readme.mdにはMD5も使えるようなことを書いてあったのですが、手元ではtimestampしか動作しませんでした。cssやjsはこれでよしなにしてくれるようですが、画像は特に何もしてくれません。Issueを見ると「忙しくて書いてる暇がないから、自分で書いてPRしてくれればmergeするよ!」とあるようなので、すぐには期待できなさそうです。また、css内のbackground-imageも、守備範囲外のようです。

htmlから読んでいるファイルは手動で対応した

もっと掘ればきっとあるんでしょうが、あんまりメンテナンスされていないようなものを使うのも気がひけるので、手動で適当に乱数を生成して、それをファイル拡張子末尾に付与させることにします。乱数はcryptoで生成出来ますので、適当に変数にぶちこんで、拡張子をgulp-replaceで置換し、置換対象の拡張子を自分で書き並べるという至って原始的な作戦に出ます。(gulp-load-pluginsを使った書き方をしていますので、導入していない方は適時読み替えてください)

npm install --save crypto
npm install --save-dev gulp-replace
gulpfile.js
const crypto = require('crypto'); 
const hash = crypto.randomBytes(8).toString('hex');

gulp.task('cache', function () {
  return gulp.src([
    'dist/index.html',
    'dist/**/*.html'
  ])
  .pipe($.replace('.css"','.css?' + hash + '"'))
  .pipe($.replace('.js"','.js?' + hash + '"'))
  .pipe($.replace('.jpg"','.jpg?' + hash + '"'))
  .pipe($.replace('.png"','.png?' + hash + '"'))
  .pipe($.replace('.gif"','.gif?' + hash + '"'))
  .pipe(gulp.dest(dir.dist))
});

拡張子が増えればその都度追記する必要はありますが、一度書いてしまえばいいだけなのでそこは我慢します。

cssファイル内の画像ファイルにはpostcss-cachebuster

postcss-cachebuster - npm

postcssを経由すれば、css内のbackground-imageで使用している画像類やフォントファイルにもパラメータをつけてくれますので、SCSSで書いているときでも問題なくパラメータを付与できます。

watchさせておく

上記で作ったタスクをhtmlファイル対象でwatchすれば、htmlファイルを更新するごとに自動でパラメータを更新してくれます。ejsなどを使っている方はdistのhtmlファイルをwatch対象にすればOKです。

実行してみる

index.html
<link rel="stylesheet" href="/css/common.css?d231d5de5fdc51bc" />
<script async src="/js/app.js?d231d5de5fdc51bc"></script>

<img src="/images/sample.jpg?d231d5de5fdc51bc">

ひとまずやりたかった形に処理してくれているようです。

終わりに。

本当はこういうのをプラグイン化できればいいんですが、まだ自分にそこまでの知識がないのと、恒常的にメンテナンスできる自信もなかったのでひとまず直書きでお茶を濁しましたが、こういうことが簡単にできるようになってくるとgulpは楽しいですね。

余談

念の為同様の記事がないか少し調べては見たのですが、Qiitaでgulpを使用した際のキャッシュ対策に言及した記事があまりなかったので、折角の機会なので今回はブログでなくこちらに書いてみることにしました。なかなか書きやすかったので、自分が書けるようなネタがあればまた書いて見ようと思います。

2019/05/20 やり方を少し改良

以前のやり方だとCDNで読み込んでいるものも全てキャッシュ避けをしてしまっていたので、自分で指定したものに限りキャッシュ避けが出来るように少し手を入れました。

乱数の生成

gulpfile.js
const crypto   = require('crypto')
const revision = crypto.randomBytes(8).toString('hex')

キャッシュ避けパラメータを付与したいファイルにaffixを追加

この例ではcssに?revを追加したものにパラメータ付与をするようにします。
jsや画像などでもhtmlからのパスの指定を同様にすれば対応可能です。
?rev部分は後述の箇所で好きなものを指定出来ますが、他の箇所で同様の指定が使っていないものを指定しないと誤爆します。

sample.ejs
<link rel="stylesheet" href="/assets/css/common.css?rev" />

コンパイル時にaffixを置換する

EJSでなくとも、pugedgeなどでも流用可能です。
共有変数などでON/OFFを管理するようにしておくと楽だと思います。
先程は?revを指定したのでそれを置換するようにしていますが、自分で別のものを指定した場合はそれを置換するように読み替えてください。

gulpfile.js
task('ejs', () => {
  return src([
    dir.src + 'ejs/**/*.ejs',
    '!' + dir.src + '/ejs/**/_*.ejs'
  ])
  ~~~ 省略 ~~~

  .pipe(
    $.replace(/\.(js|css|gif|jpg|jpeg|png|svg)\?rev/g, '.$1?rev='+revision)
  ))

  ~~~ 省略 ~~~
  // 出力先ディレクトリ
  .pipe(dest(dir.dist))
  // ブラウザを更新する
  .pipe(browser.stream())
})

出力結果

index.html
<link rel="stylesheet" href="/assets/css/common.css?rev=5e69fea66b397e3d" />

問題なさそうです。
以前の手法では一括で拡張子を指定して付与するというアプローチだったので、案件に応じて使い分けるのが良いかなと思います。