webpack loaderの自作メモ

# はじめに

このメモでは、[webpack loader](https://webpack.js.org/concepts/loaders/)を自作するにあたって得た知見をまとめます。
webpack loaderのイメージが少しでも伝わればよいなーと思います。

# webpackとかwebpack loaderってこんな感じ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309178/5fbc1459-9a33-21cd-e738-d4ca864c816b.png)
上の図は[webpack](https://webpack.js.org/)とwebpack loaderのイメージです。様々なファイルを最終的に1つのjsファイルにまとめます。一番下の例は[こちら](https://github.com/actnwit/shaderity-graph-shaderpack)の`loader/ShaderpackLoader.ts`に書かれているloaderを使った際のファイルの変換のイメージで、glslファイルの文字列をjsのObject型に変換しています。

## webpack

webpackは複数のjsファイルに書かれたモジュール(import, exportされるもの)を1つのjsファイルにまとめるツールです。webpackを使うメリットとしては、[こちら](https://udemy.benesse.co.jp/development/system/webpack.html)や[こちら](https://masa-enjoy.com/webpack-usage)などにもあるように以下のことなどが挙げられます。
1. jsを複数の小さなファイルに分けることで、全体が把握しやすくなり可読性が上がる
2. jsファイルの読み込み順を決めてくれる
3. 他人の作ったjsライブラリを簡単に取り込める

## webpack loader

webpackの補助ツールのwebpack loaderは、webpackの実行直前にimport文などで取り込む任意の形式のファイルを一時的にjsのコードへと変換します。webpackとloaderを組み合わせることで、上の図のように任意のファイルを1つのjsファイルにまとめられます。webpack loaderの例としては、tsファイルをjsのコードに変換するts loaderなどがあり、他にも有名なものは[ここ](https://webpack.js.org/loaders/)に載っています。

### 自作 webpack loader

loaderは自作することもできます。自作loaderを使うことで、以下のようなことができます。
1. 任意のファイルに書かれた文字列を解析して、都合の良い形式のjsコードに機械的に変換する
2. Node.js専用のライブラリで得た情報をフロントエンド用の静的なjsファイルに埋め込む

1の例としては以下に再掲するglslファイルのjsのオブジェクトへの変換です。glslファイルは、webの3DCGで使われるスクリプトファイルです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309178/6d3f4af8-b6cb-fb0e-90c2-818e7dcaf72e.png)
jsのオブジェクトを直接作成せずファイルを解析するメリットとして、重複した情報をオブジェクトの別のプロパティに人為的なミスなく記入できることや、各ファイルのlinterを使えることなどが挙げられます。前者の例として、上の図の変換後のオブジェクトでは`shaderFunctionCode`の一部と`shaderFunctionName`の値は意図的に重複させています。`shaderFunctionName`の値をloaderで機械的に抜き出すことでtypoによるエラーを防いでいます。

# webpack loaderを自作する

自作webpack loaderで必要となるのは以下の3つです。
1. webpack loaderのスクリプト(js)
2. webpackの設定ファイル
3. (必要であれば)loaderで変換するファイルの型定義ファイル

## webpack loaderのスクリプト

任意の形式のファイルをどのようなjsのコードに変換するかを記述するもので、jsファイルで記述します。もちろん、tsファイルで作成してjsファイルにコンパイルして使うことも可能です。

以下がwebpack loaderのスクリプトの簡単な例です。

```hogeLoader.js
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点です。
1. `module.rules`に書かれたloaderは配列の最後の要素から順に実行されます
2. loaderはjsファイルである必要があるため、tsファイルでloaderを作成している場合はwebpack実行前にjsファイルに変換する必要があります

以下はwebpackの設定ファイルの例です。

``` webpack.config.js
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`しようとすると、以下のようなエラーが起きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309178/d96f649f-dd0f-6fa5-1513-163d5d6a00c8.png)
解決方法としては、以下の2つです。
1. `require`で参照する
2. 型定義ファイルを用意する

1の方法は簡単ではありますが、参照したObjectはany型になります。any型を使いたくない場合、2の方法を取る必要があります。

型定義ファイルの例は以下の通りです、

``` hoge.d.ts
declare module '*.hoge' {
  const original: string;
  const hoge: string;
}
```

型定義ファイルの説明については参考文献が多数あるので、そちらをご覧ください。型定義ファイル自体については[こちら](https://typescriptbook.jp/reference/declaration-file)、置く場所については[こちら](https://zenn.dev/fagai/articles/7f76a3b3b5a415)などが参考になるかと思います。

# さいごに

webpack loaderを作って思ったことは、時間的に変化するデータに応じて、機械的にアップデートしたいファイル・データがあるときに便利だなーということです。「こんな機械的で単調なアップデート作業など、わざわざ人間様が毎度やることではない！」と思ったときなど、是非webpack loaderを作ってコマンド1つで機械様にお願いすることを考えてはいかがでしょう。

# 参考文献など
- [shaderity-graph-shaderpack](https://github.com/actnwit/shaderity-graph-shaderpack)
- [Webpackとは？メリットや使い方、インストール方法を初心者向けに解説](https://udemy.benesse.co.jp/development/system/webpack.html)
- [webpack初心者のためのメリット/デメリットと簡単に始める方法と使い方！](https://masa-enjoy.com/webpack-usage)
- [型定義ファイル (.d.ts) (サバイバルTypescript)](https://typescriptbook.jp/reference/declaration-file)
- [TypeScriptでグローバルな型定義ファイルを用意する](https://zenn.dev/fagai/articles/7f76a3b3b5a415)