Edited at

Webpacker v4で同一CSSなのにfingerprintが変わってしまう問題に対処する


発生していた問題

Webpacker 4.0.2を使ったRailsアプリケーションを本番環境にデプロイすると、CSSやJSの内容を変更していないのに毎回fingerprintの値(application-a72ac9c7.cssa72ac9c7の部分)が変わってしまう。

fingerprintがデプロイのたびに変わると、ブラウザのキャッシュ効率が悪くなるという問題が発生するが、それ以上に複数台のサーバーにデプロイするときにCSSのファイル名が一致しない問題の方が深刻だった。

なぜファイル名が一致しないのかというと、デプロイのたびにファイル名が変わるので、複数台のサーバーにデプロイしたときにもサーバーごとにファイル名が異なってしまうから。

アクセスしたタイミングによって参照するCSSファイル名が異なるため、CSSファイルの404エラーが発生し、画面の表示が崩れてしまうという問題が起きていた。


問題が発生していた実行環境


  • Rails 5.2.2.1

  • Webpacker 4.0.2

  • css-loader 2.1.1 (JSモジュール)


問題の原因

この問題が発生したのは以下のような複数の条件が重なっていたせいだった。


  • Webpacker v4を使っていた(バージョン3系では発生しない)

  • assets:precompile時にソースマップを生成するようになっていた(=デフォルト設定)

  • 複数台のサーバーにデプロイする際、デプロイ先のディレクトリ名にタイムスタンプが含まれていた(つまり、デプロイ先のパスがサーバーごと、またはデプロイごとに異なっていた)

なぜ、これが問題になるのかというと、

ソースマップ生成時に絶対パスの値(=タイムスタンプに依存する値)が含まれる

 ↓

fingerprintのハッシュ値は、ソースマップを含めたファイル全体の内容から算出する

 ↓

つまり、サーバーごと(またはデプロイごと)にCSS(+ソースマップ)の内容が異なる = fingerprintも異なる

というロジックになっているため。


解決策

根本的な対応としては「ソースマップ生成時に絶対パスではなく相対パスを使用すること」になる。

だが、これはcss-loaderの修正が必要であり、記事執筆時点(2019年03月29日、css-loaderの最新バージョン 2.2.1)では以下のissueで議論はされているものの、まだcloseされていない。

Setting sourceMap: true generates different content hashes on Linux vs MacOS · Issue #886 · webpack-contrib/css-loader

そこで、今回は応急処置として「ソースマップを生成しない」という設定を入れることにした。


手順

webpack-mergeをインストールする。

$ yarn add webpack-merge

config/webpack/production.jsに以下の変更を加える(参考)。


config/webpack/production.js

 process.env.NODE_ENV = process.env.NODE_ENV || 'production'

+const merge = require('webpack-merge')
const environment = require('./environment')

+const myCssLoaderOptions = {
+ sourceMap: false,
+}
+const CSSLoader = environment.loaders.get('sass').use.find(el => el.loader === 'css-loader')
+CSSLoader.options = merge(CSSLoader.options, myCssLoaderOptions)
+
module.exports = environment.toWebpackConfig()


この対応で複数台のサーバーにデプロイしてもfingerprintの値が変わらないようになった😄


謝辞

一緒にこの問題の解決策を調査してくれた弊社ソニックガーデンのメンバーと、原因と解決策を見つけてくれた@maedanaに感謝します。