ミニマムなwebpack loader

webpack loaderを自作したいと思っていろいろ試してみた。以下では、ミニマムにwebpack loaderを自作する手順をまとめてみた。

1. webpackのセットアップ

webpackとCLIをインストールする。

$ npm install -D webpack webpack-cli

エントリーポイントと依存するアセットをdist/bundle.jsにまとめるように設定する。

// webpack.config.js
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js"
  },
  mode: "development"
};

適当なエントリーポイントを用意する。あとでここからMarkdownをimportしていく。

// src/index.js
console.log("Here is entrypoint.");

webpackが実行できるか確認する。

$ npx webpack
$ node dist/bundle.js
Here is entrypoint.

2. 何もしないloader

importするMarkdownテキストをsrc/sample.mdに用意する。

# header
Here is sample markdown text.

エントリーポイントでこのMarkdownテキストをimportする。

// src/index.js
-console.log("Here is entrypoint.");
+import contents from "./sample.md";
+console.log(contents);

とりあえず何もしないloaderを作る。

// lib/loader.js
module.exports = function(source) {
  return "";
};

このままだと*.mdをwebpackはビルドできないので、さっき作ったloaderで処理するように設定を追加する。

// webpack.config.js
 module.exports = {
   entry: "./src/index.js",
   output: {
     path: path.resolve(__dirname, "dist"),
     filename: "bundle.js"
   },
-  mode: "development"
+  mode: "development",
+  module: {
+    rules: [
+      {
+        test: /\.md$/,
+        use: [
+          {
+            loader: path.resolve(__dirname, "lib/loader.js")
+          }
+        ]
+      }
+    ]
+  }
 };

何がおきるか確認する。

$ npx webpack
$ node dist/bundle.js
{}

import contents from "./sample.md"の結果、contents{}になるということがわかった。

3. 何かを返すloader

loaderが返す値はどのように使われるのか確かめるため、適当な文字列を返すようにしてみる。

// lib/loader.js
 module.exports = function(source) {
-  return "";
+  return "foo";
 };

webpackを実行して生成されたbundle.jsを確認してみると、以下のようになっていた。

// dist/bundle.js
/***/ (function(module, exports) {

eval("foo\n\n//# sourceURL=webpack:///./src/sample.md?");

/***/ })

loaderが返した文字列をevalでJavaScriptのコードとして実行しているようだ。また、eval内では関数に渡されたmoduleexportsが使えるようになっている。

ということは、このmoduleを使うことでloaderから何かをexportできそう。

// lib/loader.js
 module.exports = function(source) {
-  return "";
+  return `module.exports = ${JSON.stringify({ source })}`;
 }

webpackを実行してbundle.jsを確認してみる。

// dist/bundle.js
eval("module.exports = {\"source\":\"# header\\nHere is sample markdown text.\\n\"}\n\n//# sourceURL=webpack:///./src/sample.md?");

src/sample.mdの中身をexportする文字列が生成できた。最後にimportできるかも確認する。

$ node dist/bundle.js
{ source: '# header\nHere is sample markdown text.\n' }

import contents from "./sample.md"で確かにexportしたオブジェクトがimportできていた。

まとめ

以下のような関数が、ファイルの中身をオブジェクトとして返す機能をもったミニマムなwebpack loaderと言えそう。

module.exports = function(source) {
  return `module.exports = ${JSON.stringify({ source })}`;
};

あとは、Markdownのパースなどの機能をここに実装していけばよさそう。

参考

他に留意すべき項目はドキュメントにまとまっている。