webpack5.x対応loaderの作り方


動機

  • htmlは静的なものを使うんだけど、ejsのincludeが使いたい。
  • できればdejsでも同じejsを処理したい(<%-await include()%>)
    。。。deno移行する時のための準備。denoって有望なんでしょうかね?
  • バッチでejsコマンドとwebpack呼べばいいだけなんだけどwebpackで完結したい

どうするか

npmで「ejs loader」で検索すると、、、、
たっくさん出てくる。

でも、いくつか使ってみようとしても動かない

とりあえず、自分で作ってみる

  • module.exports = function (content, map, meta)の形で書けば良いらしい
  • webpackで設定したoptionsはrequire("loader-utils").getOptions(this)で取れるらしい
  • await includeするためには
    • ejs
      1. ejs.render()を非同期呼び出し(await ejs.render())
      2. ejs.render()の第三引数に{"async":true}が必要
    • webpack
      1. callback関数を取得(const callback = this.async()
      2. 非同期処理終了後にcallback(error, ejs.renderの戻り, map, meta)

ということを調べて作ってみたけど
「getOptionsはないよ!」
と言って、怒られる。

原因はこれ

loader-utilsのchangelog
抜粋

3.0.0 (2021-10-20) 
⚠ BREAKING CHANGES
...
removed getOptions in favor loaderContext.getOptions (loaderContext is this inside loader function), note - special query parameters like ?something=true is not supported anymore, if you need this please do it on loader side, but we strongly recommend avoid it, as alternative you can use ?something=1 and handle 1 as true
...

loaderの中のthisにgetOptionsが生えてるから、それ使ってねってことですね。
他にも色々変わってますが、基本、this使えってことらしいです。

const getOptions = (ctx) => {
	return ctx.getOptions
		? ctx.getOptions()
		: require("loader-utils").getOptions(ctx);
};

module.exports = function ejsLoader(content, map, meta) {
	const options = getOptions(this);
// ~~~~
}

最終的には、こうやって、webpack4でも5でもoptions取ってこれるようにしてみた。

npmに公開してみた

@coneyware/ejs-loader
でもね。。。ejs loaderで検索すると山ほど出てくるってことは、
みんな、それぞれ自分で作るんだろうなぁと思って作った時にハマったところをココに書いておく。