こんにちは。
現在のWebアプリ開発は、エントリポイントが1つだけのSPA(シングルページアプリ)が主流で、webpackに関する情報もSPA向けが圧倒的に多いです。
通常、MPA(マルチページアプリ環境下でそのままwebpackを使おうとすると、ページを追加するたびにwebpack.config.jsをいじる必要があり面倒です。
今回は、そんなMPA環境下で、なるべく楽をできるようなwebpackの設定ポイントを書きます。
実現したいこと
新しいページ追加しても基本的にwebpack.config.jsをいじらなくて済むようにしたい。今回は2つのページ(index.html, support.html)が存在するWebアプリ上のアセットを、webpackを使いHTMLにバンドルして出力します。
各ページは固有のJavaScriptファイルを読み込むものとし、ファイル内でESモジュールのVue.jsをつかいます。
ディレクトリ構成
1 2 3 4 5 6 7 8 |
├─webpack.config.js ├─dist │ ...(省略)... └─src ├index.html ├index.js ・・・ index.htmlで読み込む。ファイル内でVue.jsをつかう ├support.html └support.js ・・・ support.htmlで読み込む。ファイル内でVue.jsをつかう |
ビルド後は、distディレクトリ下にHTMLとJavaScriptを出力します。
1 2 3 4 5 6 |
dist ├vendor.js ・・・ 各ページ共通使用の外部モジュール(Vue.jsなど) ├index.html ・・・ vendor.jsとindex.jsをバンドル ├index.js ├support.html ・・・ vendor.jsとsupport.jsをバンドル └support.js |
ソースコード
最低限しか載せていません。webpack.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
const path = require('path') const glob = require('glob') const webpack = require('webpack') const HtmlWebpackPlugin = require('html-webpack-plugin') const webpackConfig = { entry: {}, output: { path: path.resolve('dist'), publicPath: '/dist/', filename: '[name]' }, plugins: [ new webpack.ProvidePlugin({ Vue: ['vue/dist/vue.esm.js', 'default'] }), ], optimization: { splitChunks: { name: 'vendor.js', chunks: 'initial' } } } glob.sync('**/*.js', { cwd: 'src', }).forEach(jsName => { webpackConfig.entry[jsName] = path.resolve('src', jsName) const tplName = path.basename(jsName, '.js') + '.html' webpackConfig.plugins.push(new HtmlWebpackPlugin({ template: path.resolve('src', tplName), filename: tplName, inject:'body', includeSiblingChunks :true, chunks:['vendor.js', jsName] })) }) module.exports = webpackConfig |
ポイント
1. 外部モジュールの読み込み
各ページのJavaScriptファイル内で、毎回Vue.jsのような外部モジュールを読み込ませるのは手間ですのでProvidePluginを使います。webpack.config.js(抜粋)
1 2 3 4 5 6 7 |
...(省略)... plugins: [ new webpack.ProvidePlugin({ Vue: ['vue/dist/vue.esm.js', 'default'] }) ] ...(省略)... |
src/index.js
1 2 3 4 5 6 7 |
// Vue.jsをimportなしで、すぐ使える new Vue({ el: '#app', data: { message: 'Hello Vue!' } }) |
2. 外部モジュールのバンドル
そのままだと index.js と support.js の、それぞれにVue.jsがバンドルされてしまうので、SplitChunksPluginを使い外部共通モジュールはvendor.jsとして、わけて出力します。モジュールの使用状況にあわせて適切な内容で出力してくれるみたいです。
webpack.config.js(抜粋)
1 2 3 4 5 6 7 8 |
...(省略)... optimization: { splitChunks: { name: 'vendor.js', chunks: 'initial' } } ...(省略)... |
3. エントリの自動追加
通常、新しいページを追加した場合にwebpack.config.jsのentryオプションへ、バンドルしたいJavaScriptファイルを列挙しますが、数が多い場合や後から増えることを考慮しGlobで自動化します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
glob.sync('**/*.js', { cwd: 'src', }).forEach(jsName => { webpackConfig.entry[jsName] = path.resolve('src', jsName) }) console.log(webpackConfig.entry) /* entry: { 'index.js': '...(省略).../src/index.js', 'support.js': '...(省略).../src/support.js' } */ |
4. アセットをバンドルしたHTMLを出力
HtmlWebpackPluginを使うと、各ページ毎にvendor.js と ページ固有のJavaSciptファイルのscriptタグを付加したHTMLファイルを出力できます。webpack.config.js(抜粋)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
...(省略)... glob.sync('**/*.js', { cwd: 'src', }).forEach(jsName => { ...(省略)... // index.js -> index.html const tplName = path.basename(jsName, '.js') + '.html' webpackConfig.plugins.push(new HtmlWebpackPlugin({ template: path.resolve('src', tplName), filename: tplName, inject:'body', includeSiblingChunks :true, chunks:['vendor.js', jsName] })) }) ...(省略)... |
【出力前】src/index.html
1 2 3 4 5 6 7 8 9 |
<!doctype html> <html lang="ja"> <head><meta charset="utf-8"> <title>Hello Vue</title> </head> <body> <div id="app">{{ message }}</div> </body> </html> |
【出力後】dist/index.html
1 2 3 4 5 6 7 8 9 10 11 |
<!doctype html> <html lang="ja"> <head><meta charset="utf-8"> <title>Hello Vue</title> </head> <body> <div id="app">{{ message }}</div> <script src="/dist/vendor.js"></script> <!-- ← ココが自動追加される --> <script src="/dist/index.js"></script> <!-- ← ココが自動追加される --> </body> </html> |
以上です。
細かい説明は省きましたが、MPAでwebpackを利用する際のおおまかなポイント紹介でした。
webpackについては、まだまだ自分もわからないことだらけで悪戦苦闘しております。
フロントエンド開発に詳しい方、キー・ポイントでは大募集中です。お待ちしております。