Webpack でファイルの複合体を分離して処理する

あるプロジェクトで使われているライブラリでは、コンパイル結果として一つのファイルからはページ・スタイル・スクリプトがそれぞれ生成されます。
Webpack でこれを正しいらしい設定で処理するにはどうすればいいかを実験してみました。

サンプルプロジェクトはこちら
(これは概念検証用のため、性能や安全性等は保証できません。)

サンプルプロジェクトに含まれる Webpack loader では、 HTML ファイルに直接書かれたスタイルシートとスクリプトを、別ファイルとしてパイプラインに通します。(非 emitFile

基本的な発想は vue-loader から出てきています。
(今の Vue には詳しくないですが)この中には、 HTML 用のテンプレートとスクリプト、スタイルを全て 1 つのファイルに含むという Single-File Components を処理するための loader が実装されています。

戦略としては、実装する loader の中で、受け取ったリクエストと同じリクエストをタグ付きで import して同じファイルを再び読み込ませるようにします。
2 回目に loader 入ってきたときはタグに応じた部分結果を返すことにし、続きは別の設定に書いた loader に処理させるようにします。

全体のソースの流れを図にするとこんな感じになります。

Flow Flow

実装すべき loader を疑似コードで表すとこんな感じになります。

module.exports = function loader(source) {
// get query

if (query) {
if (query.type === "js") {
callback(null, `module.exports = 1 + 1;`/* js part */);
}
/* like above */
}

const req = this.resourcePath + "?type=js";
/* like above */

callback(null, `import ${req};`/* ... */);
}

webpack.config.dev.js のように設定することによって、 “Before” のような HTML ファイルを “After” のような HTML ファイルに変換できます。

Before Before
After After

loader の中の this.emitFile でも似たようなことはできますが、 output.path 以下に出す必要があったり、生成物が同じタイミングでは参照されなかったり( 2 回のビルドが必要)という問題があります。
(直接 Webpack からは外部の fs を通して出力するという手もあるかもしれませんが…)

若干目的は異なりますが、 multi-loader と比較すると、こちらのほうがより設定は柔軟に行なえます。逆に、複雑になるとも言えます。
このサンプルは、新たに複合コンテンツをそれぞれ別途に処理したい loader が必要になった場合に役立つのではないかと思います。