JavaScript
webpack
riot

Webpackでフォルダ内の全ファイルを一気にrequireする

Webpackには、CommonJSのrequireよりも強力な、依存関係処理機能があります。

ふつうのrequireとWebpack

npmで入れたライブラリを指定する場合にしても、自分で作ったファイルを呼び出す場合にしても、ほとんどの状況でrequireすべきファイルはソースコードの時点で決まっているものです。

Webpackでは、リテラルでrequireを行った場合、コンパイル後にはrequireの引数が、モジュールの番号に置き換わっています。

動的なrequireとコンテキスト

ただ、CommonJS Modulesの仕様でも、requireが取る引数は一定の書式の「文字列」となっているだけで、もちろんリテラル限定なんてことはありません。任意の文字列を指定することができます。

このような場合、ソースコードの時点では何をrequireするか決まらないので、固定的にコンパイルすることはもちろん不可能です。Webpackでは、requireするモジュールが決まりきらないコードを発見すると、「requireコンテキスト」を生成します。

requireコンテキストは、ソースコードから判別できる接頭辞・接尾辞(フォルダ位置・拡張子など)からできるだけ絞り込んだ上で、候補になりうるすべてのモジュールを用意した上で、最終的にrequireを呼び出した文字列で実際にrequireするものを決定する、というような流れになっています。

コンテキストを自分で作る

上で述べた「requireコンテキスト」は、Webpackが自動で用意するだけではなく、require.contextを呼び出すことで、自分で作ることもできます。

const context = require.context(directory, useSubdirectories = false, regExp = /^\.\//)

引数の意味は次のようになっていますが、コンパイル時に解決しないといけないため、すべてリテラルである必要があります。

  • directory…どこのディレクトリの中身をrequireの対象とするか
  • useSubdirectories…対象のディレクトリ直下だけ拾うならfalse、以下の全ディレクトリを対象にするならtrue
  • regExp…対象になるファイルが満たすべき条件を書いた正規表現(/\.js$/のように、拡張子指定に使うと便利です)

コンテキストの操作

コンテキストを作っただけでは、requireは行われません。このcontextは、以下のようなものからなっています。

  • context(パス)…指定されたパスのモジュールをrequireする(もちろん、コンテキストになければ失敗)
  • context.resolve(パス)…指定されたパスをモジュール番号に変換する(もちろん、コンテキストになければ失敗)
  • context.keys()…コンテキストに含まれるパスの配列を返す
  • context.id…コンテキスト自体のモジュール番号

実用例

Riot.jsとWebpackを組み合わせる場合、riot/tag-loaderがあるので、これを使えば.tagファイルもrequireできますし、require('riot')で呼び出されるRiot本体に、requireした時点でタグとして登録されます。

Riotのタグをセットする側でriot.mount('*')のようにするのであれば、requireしたものを自分で保存しておく必要もない、ということになります。ということで、タグは「ただrequireしておくだけでいい」ということになります。

このような場合、以下のコードで一括requireできます。

function allRequire(context){
  context.keys().forEach(context);
}

allRequire(require.context('./path/to/tags/', true, /\.tag$/))

forEach(context)が直感的ではありませんが、context自体が「モジュールのパスを受け取ってrequireする関数」なので、forEachが投げてくる「1つ目が値」というパターンにぴったりなのです。

参照