Webpack(Vue-CLI3)で静的pugファイルのhtml生成と格闘した
環境構築の大敵便利ツール、Webpack。自分は本格的に使い始めて約半年、設定ファイルがいまだにほとんど理解できてないので固有のことをしようとすると毎回手こずります。さらにVue CLI3ではWebpackは包含されてしまっているのでよりややこしい部分があります。今回も例に漏れず苦難の連続だったのでメモ。
環境設定はこれの続きです。
ゴール
一言でいうとテンプレート的に作ったpugファイルにjsonからいくつかのデータを渡して、複数のディレクトリに内容違いのhtmlファイルを生成したい。整理すると以下です。
入力
- index.pug (本文は変数になっている)
- data.json (複数のオブジェクトの配列になっている)
出力
- a/index.html
- b/index.html
- c/index.html
- ...(全てレイアウト及び機能は同じだが文言が違う)
どうあがいてもわかりづらい。難所はいくつかあるのですが1つずつ解決していきます。
複数のentryから複数のディレクトリにoutputを出力
これは実は非常に簡単です。pagesに設定する時点で出力後の生成ファイルに期待する相対パスをhush
として指定すればそのままのパスで出力されます。ファイルが多い時は処理用のパッケージがあるらしい。
極めて言葉だけでは伝わりづらいところ特に図解もしないのはこちらにアクセスすればすべてわかるからです。では次。
1つのentryから複数のhtmlを出力
上の項の応用ですね。これもまあ簡単です。シンプルに同じ entry
および template
を持っているが output
が違うパラメータオブジェクトを量産してpagesに渡すだけです。
const meta = require('data.json'); (() => { meta.forEach(d => { index[`page-${d.id}`] = { entry: ENTRY, template: MAIN_TEMPLATE, filename: `${d.id}/index.html`, }; }); })();
こんな感じ。index['page-${d.id}']
この部分のキー page-${d.id}
はのちにパラメータを追加するときの識別子になります。
pugにオブジェクトを渡す
最大と言っていい難所。jsonからpugにデータを渡していきます。
ちなみにここで知りましたが、ターミナルで
$ vue inspect > result.js
とやると最終的にどんなプラグインがどのパラメータをもってどのように適用されているのか見ることができます。vue.config.js
では省略されている new HtmlWebpackPlugin()
とか見られます。
全ページ共通のデータを渡す
pug-plain-loader
の data
オプションを使います。
まずはvue add pug
でお手軽にセットアップしたので、実際何が入ってるのか調査します。vue-cli-plugin-pugのソースを見ると
webpackConfig.module .rule('pug') .test(/\.pug$/) // this applies to <template lang="pug"> in Vue components .oneOf('vue-loader') .resourceQuery(/^\?vue/) .use('pug-plain') .loader('pug-plain-loader') .end() .end() // this applies to pug imports inside JavaScript, i.e. .pug files .oneOf('raw-pug-files') .use('pug-raw') .loader('raw-loader') .end() .use('pug-plain') .loader('pug-plain-loader') .end() .end()
こんな感じでチェインされているのがわかりました。これを使って自分の vue.config.js
の chainwebpack に
chainWebpack: config => { config.module.rule('pug').uses.clear(); config.module .rule('pug') .oneOf('raw-pug-files') .use('pug-plain') .loader('pug-plain-loader') .options({ root: path.resolve('src/pug/'), data: object }) .end() }
上書きしつつオプションを渡します*1。vue inspect
すると
/* config.module.rule('pug').oneOf('raw-pug-files') */ { use: [ { loader: 'raw-loader' }, { loader: 'pug-plain-loader', options: { root: '/path/to/src/pug', data: { <object> } } } ] }
ちゃんとoptionsが無駄なく*2渡っているのがわかります。serve
もbuild
も成功しデータが渡っているのを確認できました。
個別のデータを渡す
html-webpack-plugin の templateParameters
を使います。
chainWebpack: config => { config.module.rule('pug').uses.clear(); meta.forEach((data, num) => { config .plugin(`html-page-${data.id}`) .tap(args => { args[0].templateParameters = { ...data, num }; return args; }); }); },
ここで plugin
に渡している html-page-${data.id}
はhtml出力用の pages
に渡したオブジェクト群のkeyに対応しています。vue inspect
するとこう
new HtmlWebpackPlugin( { templateParameters: { <dataの中身>, num: 0 }, chunks: [ 'chunk-vendors', 'chunk-common', 'page-<id>' ], template: '/path/to/src/pug/index.pug', filename: '<id>/index.html', title: undefined } ), new HtmlWebpackPlugin( ...
pagesに渡したオブジェクトの数だけ、それぞれのデータを含んだ状態で、 HtmlWebpackPlugin インスタンスが生成されているようです。ここからが問題。
次に vue add pug
でインストールした vue-cli-plugin-pug
をアンインストールして pug と pug-loader と pug-plain-loader と raw-loader をインストールします。(?!)
前項の続きでいけるかと思ったんですが、パラメータの読み込み順をいじれない問題で断念しました。どういうことかというと以下。
configureWebpackをこんな感じで設定します。
configureWebpack: { module: { rules: [{ test: /\.pug$/, oneOf: [ // this applies to `<template lang="pug">` in Vue components { resourceQuery: /^\?vue/, use: ['pug-plain-loader'], }, { test: /index/, use: ['pug-loader'], }, // this applies to pug imports inside JavaScript { use: [ 'raw-loader', { loader: 'pug-plain-loader' }, ], }, ], }] .... } }
test: /index/
のindexはテンプレートとして使いたpugファイル名を入れます。
OneOf
の中に3つのオブジェクトがあるわけですが、これは vue-cli-plugin-pug の index.js にある2つのルールの間に、まんまもう1つ割り込ませています。これはこの位置(raw-loader と pug-plain-loaderの前)にないとtemplateParameterが適用されないんですよね……chainWebpackで上書きする方法では最後尾にルールを付け足すことしかできないので、パラメータが読み込まれませんでした。ちなみに vue-cli-plugin-pug の index.js に直接ルールを付け足しても動くには動きます。
webpackConfig.module .rule('pug') .test(/\.pug$/) // this applies to <template lang="pug"> in Vue components .oneOf('vue-loader') .resourceQuery(/^\?vue/) .use('pug-plain') .loader('pug-plain-loader') .end() .end() // 真ん中に割り込み .oneOf('pug-template') .test(/index/) .use('pug') .loader('pug-loader') .end() .end() // this applies to pug imports inside JavaScript, i.e. .pug files .oneOf('raw-pug-files') .use('pug-raw') .loader('raw-loader') .end() .use('pug-plain') .loader('pug-plain-loader') .end() .end()
node_modules の中をいじるとあとあとどういう地獄を見るのかゾクゾクしますね。
まとめ
というわけで色々回り道をしながらpugから内容違いのhtmlを生成することに成功しました。そもそもの話ですけどこれあまりVue-CLIで想定されている使い方ではない気がする。そういう意味で生産性が若干疑問ですが、各種レポジトリのissueやコードをの情報を駆使して問題解決する経験が図らずもできたのでよしとします。