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

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

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

```html: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](https://www.npmjs.com/package/gulp-rev)

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

[gulp-cache-bust - npm](https://www.npmjs.com/package/gulp-cache-bust)

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

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

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

```terminal
npm install --save crypto
npm install --save-dev gulp-replace
```

```javascript: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](https://www.npmjs.com/package/postcss-cachebuster)

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

## watchさせておく

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

# 実行してみる

```html: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で読み込んでいるものも全てキャッシュ避けをしてしまっていたので、自分で指定したものに限りキャッシュ避けが出来るように少し手を入れました。

## 乱数の生成

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

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

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

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


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

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

```js: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())
})
```

## 出力結果

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

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