How to load loaders#
First, let's introduce the node --loader
parameter, which allows us to customize the rules for loading ESM modules.
Execute node --loader ./my-loader.mjs index.mjs
. When loading index.mjs, the content inside my-loader.mjs will be executed first. What if we write my-loader.js? Node has two built-in hooks, resolve
and load
, which can be executed when importing (note: these two hooks only work for ESM modules and are not effective for CJS). Let's introduce the usage of these two hooks below:
resolve#
The resolve function allows us to obtain the file name and file format information. We can change the information of the incoming module and return it. The returned information will be handed over to the load hook for execution.
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 is the path of the file to be executed, and context records the parentURL and the rules for importable conditions. nextResolve is to write a resolve function. If it doesn't exist, it is the default one. The returned structure is
{
format: 'commonjs' | 'module' | 'wasm',
shortCircuit: boolean, // default false Whether to end the resolve hooks
url: string; // The URL of the file, where we can process the original URL of the file.
}
load#
This hook determines how a file's URL is retrieved and resolved.
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")
Executing node --loader ./loader.mjs index.mjs
will print 2 i am injected from loader.mjs
, which can inject the code we need at runtime.
CJS loader#
The above introduces some ESM hooks. Now let's introduce how to implement the above load function in CJS.
// Module loading
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
}
// Call the parsing method of different file extensions
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
}
}
// Finally, call the _compile method to compile our module.
Module.prototype._compile = function (content, filename) {
var self = this
var args = [self.exports, require, self, filename, dirname]
return compiledWrapper.apply(self.exports, args)
}
Similarly, we can also inject CJS loaders like this.