webpackでdigestをつけてキャッシュ対応 + digestされたファイルの参照

## はじめに
[こちらの記事](https://qiita.com/sugamondo/items/a75e7692db20663a5497)でwebpackでbundleされたファイルの管理を行いました。
ただこのままですと本番環境で実運用するにはファイルのキャッシュ対応が足りません。ブラウザがキャッシュをしてしまうので、ファイルにdigestをつけてデプロイのたびにファイル名が異なるようにしないといけないわけです。

ですので今回は、「digestつきのファイルを出力してそれを使ってみる」ということをやってみます。
※ jsやhtmlなどは[こちらの記事](https://qiita.com/sugamondo/items/a75e7692db20663a5497)のものを使います。

## 今回のゴール

* webpackでbundleされたファイルにdigestをつける
* digestつきのファイルを読み込み


## digestつきのファイルを出力する
これはめちゃくちゃ簡単です。
webpack.config.jsでoutputの設定をしていると思いますが、出力ファイルのファイル名フォーマットに`[hash]`追加するだけです。

コードはこうなります。

```webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  plugins: [
    new CleanWebpackPlugin(['dist']),
    new HtmlWebpackPlugin({
      title: 'Output Management'
    })
  ],
  output: {
    filename: '[name]-[hash].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};
```

これを実行します。

```bash
$ yarn run build
yarn run v1.0.1
$ webpack
clean-webpack-plugin: /xxx/xxx/xxx/webpack-demo/dist has been removed.
Hash: e067b2f5e00633ee7372
Version: webpack 3.6.0
Time: 1130ms
                               Asset       Size  Chunks                    Chunk Names
  app-e067b2f5e00633ee7372.bundle.js     545 kB    0, 1  [emitted]  [big]  app
print-e067b2f5e00633ee7372.bundle.js    2.74 kB       1  [emitted]         print
                          index.html  296 bytes          [emitted]
   [0] ./src/print.js 84 bytes {0} {1} [built]
   [1] ./src/index.js 403 bytes {0} [built]
   [3] (webpack)/buildin/global.js 509 bytes {0} [built]
   [4] (webpack)/buildin/module.js 517 bytes {0} [built]
    + 1 hidden module
Child html-webpack-plugin for "index.html":
     1 asset
       [2] (webpack)/buildin/global.js 509 bytes {0} [built]
       [3] (webpack)/buildin/module.js 517 bytes {0} [built]
        + 2 hidden modules
✨  Done in 7.25s.
```

出力されたファイルを見てみます。

```dist/
$ ls dist/
app-e067b2f5e00633ee7372.bundle.js	index.html				print-e067b2f5e00633ee7372.bundle.js
```

無事digestがついてました。
これだけです。簡単なお仕事でした。

html-webpack-pluginを使ってbundleしているので、dist/index.htmlではdigestつきのファイルが自動でセットされています。
dist/index.htmlを実際に見てみます。

```dist/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Output Management</title>
  </head>
  <body>
  <script type="text/javascript" src="app-e067b2f5e00633ee7372.bundle.js"></script><script type="text/javascript" src="print-e067b2f5e00633ee7372.bundle.js"></script></body>
</html>
```

実際にブラウザで確認してみても問題なくbundleされたファイルが読み込まれていました。
これはindex.htmlから全出力ファイルを読み込むというケースですが、複数ファイルかつ特定ファイルしか読み込まないという設定もできますので次項でやっていきます。


## 複数ファイルかつ特定ファイルのみ読み込み
html-webpack-pluginにオプションを追加することでできます。オプション追加したコードは以下になります。

```webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  plugins: [
    new CleanWebpackPlugin(['dist']),
    new HtmlWebpackPlugin({
      title: 'Output Management'
    }),
    new HtmlWebpackPlugin({
      filename: 'test.html',
      chunks: ['app']
    })
  ],
  output: {
    filename: '[name]-[hash].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};
```

これを`yarn run build`でbundleしてみます。まず出力されたファイルを確認します。

```bash
$ ls dist/
app-7f4dcc4aa728d3280cbc.bundle.js	index.html				print-7f4dcc4aa728d3280cbc.bundle.js	test.html

```

test.htmlが出力されていればオーケーです。次にtest.htmlの中身を見てみます。

```dist/test.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
  </head>
  <body>
  <script type="text/javascript" src="app-7f4dcc4aa728d3280cbc.bundle.js"></script></body>
</html>
```

digestされたapp.jsだけ読み込まれていることがわかります。


### [番外編] Railsからjsファイルを参照する場合
digestがついているのでちょっと厄介です。bundleのたびに参照箇所を変更するわけにもいきません。そこで・・・[webpack-manifest-plugin](https://github.com/danethurber/webpack-manifest-plugin)を使います。

このプラグインはbundle前のファイルとbundle後のファイルをマッピングしてくれます。
マッピングされたデータを元に読み込むようにすることで、digestが変わっても変更の必要がないというわけです。

ただ、このマッピングされたデータを元に読み込むためには少し工夫が必要です。Railsを使ってサンプル作って見ます。

まずは、webpack.config.jsの設定を変更します。

```webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');

module.exports = {
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  plugins: [
    new CleanWebpackPlugin(['dist']),
    new HtmlWebpackPlugin({
      title: 'Output Management'
    }),
    new HtmlWebpackPlugin({
      filename: 'test.html',
      chunks: ['app']
    }),
    new ManifestPlugin()
  ],
  output: {
    filename: '[name]-[hash].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};
```

これをいつも通り`yarn run build`でbundleしてみます。出力結果は、

```bash
$ ls dist/
app-7f4dcc4aa728d3280cbc.bundle.js	index.html				manifest.json				print-7f4dcc4aa728d3280cbc.bundle.js	test.html
```

manifest.jsonというファイルが作成されていることがわかります。中身を見てましょう。

```manifest.json
{
  "app.js": "app-7f4dcc4aa728d3280cbc.bundle.js",
  "print.js": "print-7f4dcc4aa728d3280cbc.bundle.js"
}
```

マッピングされていることがわかります。
Railsのviewでdigestされたjsファイルを読み込む場合、このmanifest.jsonのデータを使って読み込めばオーケーです。
検索すればいろいろ落ちていますが、以下簡単なサンプルになります。

前提条件として、`manifest.jsonはRailsルート直下のpublic/assets下`とします。

※動作確認はしていませんごめんなさいm(__)m

まずは、起動時にmanifestファイルを読み込んでマッピングデータをパースする処理を追加します。

```config/initializers/assets_manifest.rb
Rails.application.config.assets_manifest =
  if File.exist?(Rails.root.join('public', 'assets', 'manifest.json'))
    JSON.parse(File.read(Rails.root.join('public', 'assets', 'manifest.json')))
  end
```

次にviewでjsファイルを参照する際に、マッピングデータを元に参照されるようにします。
application_helper.rbに関数を用意しましょう。

```app/helpers/application_helper.rb
module ApplicationHelper
  def assets_path(name)
    return "/assets/#{path}" if Rails.env == 'local'
    manifest = Rails.application.config.assets_manifest
    file_path = manifest && manifest[name].present? ? manifest[name] : name
    "/assets/#{file_path}"
  end
end
```

ローカル環境ではdigest化していない場合を想定してみました。
実際にjsファイルを読み込んでいるところはこうなります。

```app/views/layouts/application.html.erb
<script type="text/javascript" src="<%= assets_path('app.js') %>"></script>
```

これで以上になります。


## おわりに
webpackで簡単にdigest化できることが分かりました。html上で自動で毎回変更されるハッシュ値に対応させるのも簡単にできました。
Railsで対応させるのがちょっと面倒くさって思ってしまいました。もっといいやり方があったら教えてくださいm(__)m