ここでいうリビジョン管理とは、JavaScriptファイルやスタイルシートのファイル名に、ハッシュ値などのユニークな値(リビジョン)を付与すること。
そうすることで、ブラウザが古いファイルのキャッシュを利用してしまい変更が反映されない、という事態を回避できる。
手動でファイル名を更新することも可能ではあるが、ビルドの際に自動的に付与されるようにするのが一般的。
ここでは、webpack
のv4
でリビジョンを付与する方法を書いていく。
この記事で出てくるライブラリについては以下のバージョンで動作確認している。
- webpack@4.23.1
- webpack-cli@3.1.2
- html-webpack-plugin@3.2.0
- css-loader@1.0.1
- mini-css-extract-plugin@0.4.4
- clean-webpack-plugin@0.1.19
出力するファイルにハッシュをつける
ファイル名にリビジョンをつけること自体は簡単に出来る。
まずはwebpack
をインストールする。
$ npm i -D webpack webpack-cli
ビルドの対象であるsrc/index.js
と、webpack の設定ファイルであるwebpack.config.js
を用意する。
// src/index.js const body = document.querySelector('body'); body.innerHTML += 'First revision.';
// webpack.config.js const path = require('path'); const OUTPUT_DIR_NAME = 'dest'; module.exports = { entry: { main: './src/index.js' }, output: { filename: '[name].[contentHash].js', path: path.resolve(__dirname, OUTPUT_DIR_NAME), }, }
filename: '[name].[contentHash].js',
の[contentHash]
がポイント。
これによってハッシュが付与される。
$ npx webpack --mode development
でビルドして確認してみる。
以降、この記事で「ビルド」と言った場合は$ npx webpack --mode development
を指す。
ビルドの結果、dest/main.cbcf6aafd0f9c03b23ea.js
が出力された。
出力する内容が変わらない限り、何度ビルドしても必ずこのファイルが生成される。
今度は、src/index.js
を編集した上でビルドしてみる。
diff --git a/src/index.js b/src/index.js index b9d89a1..f855296 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,2 @@ const body = document.querySelector('body'); -body.innerHTML += 'First revision.'; +body.innerHTML += 'Update!';
すると、dest/main.88efe3ee8faaf3d9d00f.js
が生成された。
このように、出力されるファイル名にリビジョンをつけることは難しくない。
だがフロントエンドの場合、JSファイルはHTMLファイルで読み込まれて初めて意味がある。
今回はdest/index.html
を用意して、そこでJSファイルを読み込む形にする。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Revision control</title> </head> <body> <script type="text/javascript" src="main.88efe3ee8faaf3d9d00f.js"></script></body> </html>
取り敢えずはこれで動くが、今後ハッシュ値が変わる度にHTMLファイルも更新しないといけない。
これを手動で管理するのは煩雑なので、JSファイルのリビジョンが変わる度にHTMLファイルも自動的に更新されるようにしたい。
HTMLを動的に生成する
そのために使うのがhtml-webpack-plugin
というプラグイン。
これを使うことで、HTMLファイルを webpack で書き出せるようになる。
$ npm i -D html-webpack-plugin
でインストール。
リビジョンを新しくするために、src/index.js
を更新する。
diff --git a/src/index.js b/src/index.js index f855296..062a926 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,2 @@ const body = document.querySelector('body'); -body.innerHTML += 'Update!'; +body.innerHTML += 'Introduce HtmlWebpackPlugin!';
そして、webpack.config.js
を以下のように書き換える。
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const OUTPUT_DIR_NAME = 'dest'; module.exports = { entry: { main: './src/index.js' }, output: { filename: '[name].[contentHash].js', path: path.resolve(__dirname, OUTPUT_DIR_NAME), }, plugins: [ new HtmlWebpackPlugin({title: 'Revision control'}), ], }
これでビルドするとdest/main.8e6f23081c0f0133bd09.js
が生成される。
さらに、dest/index.html
が更新され、以下の内容に変わる。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Revision control</title> </head> <body> <script type="text/javascript" src="main.8e6f23081c0f0133bd09.js"></script></body> </html>
読み込むJSファイルの名前が書き換わっているのが分かる。
これで、JSファイルが更新されて出力される度にリビジョンが付与され、しかもそれを読み込むHTML側も自動的に更新されるようになった。
html-webpack-plugin のオプション
先程出力したHTMLファイルはかなりシンプルな内容だった。
現実的には、他にも様々なメタタグやコンテンツを入れたいはず。
html-webpack-plugin
には豊富なオプションが用意されているので、それを使うことで対応できる。
https://github.com/jantimon/html-webpack-plugin#options
先程の例で既にtitle
オプションを使っていたが、そこにmeta
オプションを加えて任意のmeta
タグを出力してみる。
diff --git a/webpack.config.js b/webpack.config.js index 9a58bce..3476b91 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -12,6 +12,9 @@ module.exports = { path: path.resolve(__dirname, OUTPUT_DIR_NAME), }, plugins: [ - new HtmlWebpackPlugin({title: 'Revision control'}), + new HtmlWebpackPlugin({ + title: 'Revision control', + meta: {description: 'サイトの説明。'}, + }), ], }
diff --git a/dest/index.html b/dest/index.html index 8c97a8f..81d7f7c 100644 --- a/dest/index.html +++ b/dest/index.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8"> <title>Revision control</title> - </head> + <meta name="description" content="サイトの説明。"></head> <body> <script type="text/javascript" src="main.8e6f23081c0f0133bd09.js"></script></body> </html> \ No newline at end of file
問題なく書き出されている。
テンプレートファイルを用意してそれを使うことも出来る。
まず、テンプレートファイルとしてsrc/index.html
を作る。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Template File</title> <link rel="stylesheet" type="text/css" href="css/base.css"> </head> <body> <p>This is static content.</p> </body> </html>
動作確認としてCSS(dest/css/base.css
)を読み込んでいるので、それも作成する。
body { background-color: beige; }
template
オプションを指定。
diff --git a/webpack.config.js b/webpack.config.js index 3476b91..db59371 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,6 +13,7 @@ module.exports = { }, plugins: [ new HtmlWebpackPlugin({ + template: './src/index.html', title: 'Revision control', meta: {description: 'サイトの説明。'}, }),
これでビルドすると、以下の結果になる。
diff --git a/dest/index.html b/dest/index.html index 81d7f7c..b4f6fb2 100644 --- a/dest/index.html +++ b/dest/index.html @@ -2,8 +2,10 @@ <html> <head> <meta charset="UTF-8"> - <title>Revision control</title> + <title>Template File</title> + <link rel="stylesheet" type="text/css" href="css/base.css"> <meta name="description" content="サイトの説明。"></head> <body> + <p>This is static content.</p> <script type="text/javascript" src="main.8e6f23081c0f0133bd09.js"></script></body> -</html> \ No newline at end of file +</html>
src/index.html
の内容にJSファイルを読み込む<script>
タグを差し込んでいる形になっているのが分かる。
なお、title
オプションは無視されテンプレートファイルの内容が採用されるので、注意が必要。
不要なので削除しておく。
diff --git a/webpack.config.js b/webpack.config.js index db59371..4c0d2a3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,7 +14,6 @@ module.exports = { plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', - title: 'Revision control', meta: {description: 'サイトの説明。'}, }), ],
CSSファイルを動的に生成する
先程はdest/css/base.css
というスタティックなファイルを読み込んでいたが、CSSファイルを webpack で生成することもある。
そのような場合、JSファイルと同様にhtml-webpack-plugin
が自動的に<link>
タグを挿入してくれる。
CSSファイルの出力そのものはこちらを参照。
numb86-tech.hatenablog.com
まずsrc/font-size.css
を作成し、それをsrc/index.js
で読み込む。
body { font-size: 40px; }
diff --git a/src/index.js b/src/index.js index 062a926..6f4d621 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,4 @@ +import './font-size.css'; + const body = document.querySelector('body'); body.innerHTML += 'Introduce HtmlWebpackPlugin!';
次に、$ npm i -D css-loader mini-css-extract-plugin
で必要なローダーをインストールした上で、webpack.config.js
を編集する。
diff --git a/webpack.config.js b/webpack.config.js index 4c0d2a3..6e196a5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,6 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OUTPUT_DIR_NAME = 'dest'; @@ -11,7 +12,19 @@ module.exports = { filename: '[name].[contentHash].js', path: path.resolve(__dirname, OUTPUT_DIR_NAME), }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + MiniCssExtractPlugin.loader, + 'css-loader', + ], + }, + ], + }, plugins: [ + new MiniCssExtractPlugin({filename: 'css/[name].[contentHash].css'}), new HtmlWebpackPlugin({ template: './src/index.html', meta: {description: 'サイトの説明。'},
これでビルドしてみる。
すると、以下の2つのファイルが出力されている。
dest/css/main.118ef897ded93b64888a.css dest/main.4aa6d0f7fbe5f0933de8.js
そしてdest/index.html
でこの2つが読み込まれているのが分かる。
diff --git a/dest/index.html b/dest/index.html index b4f6fb2..c5c889c 100644 --- a/dest/index.html +++ b/dest/index.html @@ -4,8 +4,8 @@ <meta charset="UTF-8"> <title>Template File</title> <link rel="stylesheet" type="text/css" href="css/base.css"> - <meta name="description" content="サイトの説明。"></head> + <meta name="description" content="サイトの説明。"><link href="css/main.118ef897ded93b64888a.css" rel="stylesheet"></head> <body> <p>This is static content.</p> - <script type="text/javascript" src="main.8e6f23081c0f0133bd09.js"></script></body> + <script type="text/javascript" src="main.4aa6d0f7fbe5f0933de8.js"></script></body> </html>
このように、html-webpack-plugin
はCSSファイルも対象にしてHTMLの出力を行ってくれる。
エントリポイントが複数ある場合の対応
複数のエントリポイントがあるときの挙動を確認するために、新たにファイルを作成する。
src/font-color.css
と、それを読み込むsrc/other.js
。
body { color: red; }
import './font-color.css'; const body = document.querySelector('body'); body.innerHTML += 'I am other.js !';
そして、./src/other.js
をエントリポイントとして設定する。名前はsub
とした。
diff --git a/webpack.config.js b/webpack.config.js index 6e196a5..d9ffb98 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,7 +6,8 @@ const OUTPUT_DIR_NAME = 'dest'; module.exports = { entry: { - main: './src/index.js' + main: './src/index.js', + sub: './src/other.js' }, output: { filename: '[name].[contentHash].js',
これでビルドすると以下のJSファイルとCSSファイルが出力される。
dest/css/main.d3ca65c4e54b7b002e2c.css dest/css/sub.e48e406758b0bd95f6fc.css dest/main.68f1aa6e62402727378e.js dest/sub.fc65f262edb74ee554aa.js
それに合わせてdest/index.html
も書き換えられ、main
もsub
も両方読み込まれていることが分かる。
diff --git a/dest/index.html b/dest/index.html index c5c889c..1777c27 100644 --- a/dest/index.html +++ b/dest/index.html @@ -4,8 +4,8 @@ <meta charset="UTF-8"> <title>Template File</title> <link rel="stylesheet" type="text/css" href="css/base.css"> - <meta name="description" content="サイトの説明。"><link href="css/main.118ef897ded93b64888a.css" rel="stylesheet"></head> + <meta name="description" content="サイトの説明。"><link href="css/main.d3ca65c4e54b7b002e2c.css" rel="stylesheet"><link href="css/sub.e48e406758b0bd95f6fc.css" rel="stylesheet"></head> <body> <p>This is static content.</p> - <script type="text/javascript" src="main.4aa6d0f7fbe5f0933de8.js"></script></body> + <script type="text/javascript" src="main.68f1aa6e62402727378e.js"></script><script type="text/javascript" src="sub.fc65f262edb74ee554aa.js"></script></body> </html>
このように、複数のエントリポイントがある場合、全てHTMLで読み込まれる。
だが、一部のエントリポイントだけ読み込みたいというケースもある。
そのような場合、読み込むファイルをchunks
オプションで指定すればいい。
例えば、main
は読み込まずにsub
だけ読み込みたいなら、以下のように記述すればよい。
diff --git a/webpack.config.js b/webpack.config.js index d9ffb98..3f1f6ba 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -29,6 +29,7 @@ module.exports = { new HtmlWebpackPlugin({ template: './src/index.html', meta: {description: 'サイトの説明。'}, + chunks: ['sub'], }), ], }
これでビルドすると以下のように、sub
だけを読み込むHTMLファイルが書き出される。
diff --git a/dest/index.html b/dest/index.html index 1777c27..faf5ae5 100644 --- a/dest/index.html +++ b/dest/index.html @@ -4,8 +4,8 @@ <meta charset="UTF-8"> <title>Template File</title> <link rel="stylesheet" type="text/css" href="css/base.css"> - <meta name="description" content="サイトの説明。"><link href="css/main.d3ca65c4e54b7b002e2c.css" rel="stylesheet"><link href="css/sub.e48e406758b0bd95f6fc.css" rel="stylesheet"></head> + <meta name="description" content="サイトの説明。"><link href="css/sub.e48e406758b0bd95f6fc.css" rel="stylesheet"></head> <body> <p>This is static content.</p> - <script type="text/javascript" src="main.68f1aa6e62402727378e.js"></script><script type="text/javascript" src="sub.fc65f262edb74ee554aa.js"></script></body> + <script type="text/javascript" src="sub.fc65f262edb74ee554aa.js"></script></body> </html>
最新のファイル以外を削除する
ここまでの例でdest
を対象にファイルを出力してきたが、その結果、dest
には多くのファイルが存在しているはず。
しかしそのほとんどは不要であり、最新のファイルさえあればよいはずなのだから、過去のファイルは自動的に削除されるのが望ましい。
それを実現するためによく使われるプラグインが、clean-webpack-plugin
である。
github.com
$ npm i -D clean-webpack-plugin
でインストールした上で、プラグインの記述を追加する。
diff --git a/webpack.config.js b/webpack.config.js index 3f1f6ba..eb6f1b9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,7 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const CleanWebpackPlugin = require('clean-webpack-plugin'); const OUTPUT_DIR_NAME = 'dest'; @@ -25,6 +26,7 @@ module.exports = { ], }, plugins: [ + new CleanWebpackPlugin([OUTPUT_DIR_NAME]), new MiniCssExtractPlugin({filename: 'css/[name].[contentHash].css'}), new HtmlWebpackPlugin({ template: './src/index.html',
これでビルドすると、ビルド前にdest
ディレクトリをクリーンにしてからビルドを行う。
そのため、dest
には今回のビルドで出力されたファイルのみが入っている状態になる。
気を付けないといけないのは、指定したディレクトリを一度完全にクリーンにしてしまうという点である。
そのため、 webpack では出力しないスタティックなファイルとして定義したdest/css/base.css
も削除されてしまう。
これを避けたければ以下のように指定方法を工夫する必要がある。
diff --git a/webpack.config.js b/webpack.config.js index eb6f1b9..9644ee4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -26,7 +26,7 @@ module.exports = { ], }, plugins: [ - new CleanWebpackPlugin([OUTPUT_DIR_NAME]), + new CleanWebpackPlugin([`${OUTPUT_DIR_NAME}/*.*.js`, `${OUTPUT_DIR_NAME}/css/*.*.css`]), new MiniCssExtractPlugin({filename: 'css/[name].[contentHash].css'}), new HtmlWebpackPlugin({ template: './src/index.html',
こうするとdest/css/base.css
は対象から外れるので、削除されずに済む。