はじめに
このメモでは、webpack loaderを自作するにあたって得た知見をまとめます。
webpack loaderのイメージが少しでも伝わればよいなーと思います。
webpackとかwebpack loaderってこんな感じ
上の図はwebpackとwebpack loaderのイメージです。様々なファイルを最終的に1つのjsファイルにまとめます。一番下の例はこちらのloader/ShaderpackLoader.ts
に書かれているloaderを使った際のファイルの変換のイメージで、glslファイルの文字列をjsのObject型に変換しています。
webpack
webpackは複数のjsファイルに書かれたモジュール(import, exportされるもの)を1つのjsファイルにまとめるツールです。webpackを使うメリットとしては、こちらやこちらなどにもあるように以下のことなどが挙げられます。
- jsを複数の小さなファイルに分けることで、全体が把握しやすくなり可読性が上がる
- jsファイルの読み込み順を決めてくれる
- 他人の作ったjsライブラリを簡単に取り込める
webpack loader
webpackの補助ツールのwebpack loaderは、webpackの実行直前にimport文などで取り込む任意の形式のファイルを一時的にjsのコードへと変換します。webpackとloaderを組み合わせることで、上の図のように任意のファイルを1つのjsファイルにまとめられます。webpack loaderの例としては、tsファイルをjsのコードに変換するts loaderなどがあり、他にも有名なものはここに載っています。
自作 webpack loader
loaderは自作することもできます。自作loaderを使うことで、以下のようなことができます。
- 任意のファイルに書かれた文字列を解析して、都合の良い形式のjsコードに機械的に変換する
- Node.js専用のライブラリで得た情報をフロントエンド用の静的なjsファイルに埋め込む
1の例としては以下に再掲するglslファイルのjsのオブジェクトへの変換です。glslファイルは、webの3DCGで使われるスクリプトファイルです。
jsのオブジェクトを直接作成せずファイルを解析するメリットとして、重複した情報をオブジェクトの別のプロパティに人為的なミスなく記入できることや、各ファイルのlinterを使えることなどが挙げられます。前者の例として、上の図の変換後のオブジェクトではshaderFunctionCode
の一部とshaderFunctionName
の値は意図的に重複させています。shaderFunctionName
の値をloaderで機械的に抜き出すことでtypoによるエラーを防いでいます。
webpack loaderを自作する
自作webpack loaderで必要となるのは以下の3つです。
- webpack loaderのスクリプト(js)
- webpackの設定ファイル
- (必要であれば)loaderで変換するファイルの型定義ファイル
webpack loaderのスクリプト
任意の形式のファイルをどのようなjsのコードに変換するかを記述するもので、jsファイルで記述します。もちろん、tsファイルで作成してjsファイルにコンパイルして使うことも可能です。
以下がwebpack loaderのスクリプトの簡単な例です。
module.exports = function (source) {
const resultJson = {
original: source,
hoge: 'hogehoge',
};
return `export default ${JSON.stringify(resultJson)}`;
};
webpack loaderのスクリプトの実態はjsの関数です。仮引数のsource
には、webpack loaderで変換するファイルに記述されている文字列が渡されます。
返り値は文字列で、その内容はmoduleをexportするjsのコードである必要があります。というのも、この返り値の文字列は、webpack loaderで変換するファイルを別のjsファイルからimport
・require
で参照した際に渡されるコードとなるためです。
webpackの設定ファイル
webpackの設定ファイルでは、webpack loaderで変換する対象のファイルや、どのloaderを使用するかなどを指定できます。module.rules
に配列としてその情報を記述します。
注意点としては以下の2点です。
-
module.rules
に書かれたloaderは配列の最後の要素から順に実行されます - loaderはjsファイルである必要があるため、tsファイルでloaderを作成している場合はwebpack実行前にjsファイルに変換する必要があります
以下はwebpackの設定ファイルの例です。
const path = require('path');
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.ts$/,
exclude: [/node_modules/],
loader: 'ts-loader',
},
{
test: /\.hoge$/,
include: /test.hoge/,
loader: path.resolve(
__dirname,
'./loader/dist/loader/hogeLoader.js'
),
},
],
},
…
webpackは、module.exports.entry
でexportされているmoduleから参照できる全てのファイルを1つのjsにまとめようとします。まとめる前に、import
, require
で参照されるファイルをmodule.rules
に従って変換します。
module.rules
のtest
はloaderを実行する対象のファイルのパスの名前を正規表現で指定します。exclude
, include
はそれぞれloaderを実行しないファイルのパス、実行するファイルのパスを指定します。loader
は、使用するwebpack loaderを指定します。npm packageとしてインストールしたloaderを使う場合はパッケージ名を指定します(上の例だとts-loader
)。ライブラリ内にloaderがある場合はその絶対パスを指定します。上の例では、(webpack.config.jsがあるディレクトリ)/loader/dist/loader
ディレクトリにhogeLoader.js
がある必要があります。
型定義ファイル
tsファイルから、webpack loaderで変換する適当な拡張子のファイルをimport
しようとすると、以下のようなエラーが起きます。
-
require
で参照する - 型定義ファイルを用意する
1の方法は簡単ではありますが、参照したObjectはany型になります。any型を使いたくない場合、2の方法を取る必要があります。
型定義ファイルの例は以下の通りです、
declare module '*.hoge' {
const original: string;
const hoge: string;
}
型定義ファイルの説明については参考文献が多数あるので、そちらをご覧ください。型定義ファイル自体についてはこちら、置く場所についてはこちらなどが参考になるかと思います。
さいごに
webpack loaderを作って思ったことは、時間的に変化するデータに応じて、機械的にアップデートしたいファイル・データがあるときに便利だなーということです。「こんな機械的で単調なアップデート作業など、わざわざ人間様が毎度やることではない!」と思ったときなど、是非webpack loaderを作ってコマンド1つで機械様にお願いすることを考えてはいかがでしょう。