ローダーの読み込み方法#
まず、node --loader
パラメータを紹介します。このパラメータを使用すると、ESM モジュールの読み込みルールをカスタマイズすることができます。
node --loader ./my-loader.mjs index.mjs
を実行します。index.mjs を読み込む際に、まず my-loader.mjs の内容が実行されます。my-loader.js を書いた場合はどうなるでしょうか。node には 2 つのフックが組み込まれています。それぞれresolve
とload
と呼ばれ、import の際に実行されます(注:これらのフックは ESM モジュールにのみ有効であり、CJS には無効です)。次に、これら 2 つのフックの使用方法を紹介します。
resolve#
resolve 関数は、ファイル名とファイルのフォーマット情報を取得することができます。渡されたモジュールの情報を変更して返すことができます。返された情報は、load フックに渡されて実行されます。
export async function resolve(specifier, context, nextResolve) {
const { parentURL = null } = context;
if (Math.random() > 0.5) {
return {
shortCircuit: true,
url: parentURL ?
new URL(specifier, parentURL).href :
new URL(specifier).href,
};
}
if (Math.random() < 0.5) { .
return nextResolve(specifier, {
...context,
conditions: [...context.conditions, 'another-condition'],
});
}
return nextResolve(specifier);
}
specifier は実行するファイルのパスです。context には parentURL とインポートできる条件のルールが記録されています。nextResolve は resolve 関数を書くための関数です。指定されていない場合はデフォルトの関数になります。返される構造は次のようになります。
{
format: 'commonjs' | 'module' | 'wasm',
shortCircuit: boolean, // デフォルトはfalseで、resolveフックを終了するかどうかを示します
url: string; // ファイルのURLで、ファイルの元のURLを処理することができます。
}
load#
このフックは、ファイルの URL がどのように検索および解析されるかを決定します。
export async function load(url, context, defaultLoad) {
const load = defaultLoad(url, context, defaultLoad)
const source = load.source
return {
format: "module",
source: `${source} console.log("i am injected from loader.mjs")`,
}
}
console.log("2")
node --loader ./loader.mjs index.mjs
を実行すると、2 i am injected from loader.mjs
と表示されます。これにより、実行時に必要なコードを注入することができます。
CJS のローダー#
上記ではいくつかの ESM のフックについて説明しました。次に、CJS で上記の load の機能を実装する方法について説明します。
// モジュールの読み込み
Module.prototype.load = function (filename) {
var extension = path.extname(filename) || ".js"
if (!Module._extensions[extension]) extension = ".js"
Module._extensions[extension](this, filename)
this.loaded = true
}
// 異なる拡張子の解析メソッドを呼び出す
Module._extensions[".js"] = function (module, filename) {
var content = fs.readFileSync(filename, "utf8")
module._compile(stripBOM(content), filename)
}
Module._extensions[".json"] = function (module, filename) {
var content = fs.readFileSync(filename, "utf8")
try {
module.exports = JSON.parse(stripBOM(content))
} catch (err) {
err.message = filename + ": " + err.message
throw err
}
}
// 最後に_compileメソッドを呼び出してモジュールをコンパイルします。
Module.prototype._compile = function (content, filename) {
var self = this
var args = [self.exports, require, self, filename, dirname]
return compiledWrapper.apply(self.exports, args)
}
同様に、CJS のローダーを注入することもできます。