NodeJS loader

How to load loaders#

First, let's introduce a 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 not for CJS). Let's introduce the usage of these two hooks below:


The resolve function allows us to obtain the file name and file format information. We can change the information of the module passed in and then 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, {
      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.


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")`,

Executing node --loader ./loader.mjs index.mjs will print 2 i am injected from loader.mjs, which allows us to inject the code we need at runtime.

CJS loader#

The above introduces some ESM hooks. Now let's introduce how to achieve the same functionality 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 for 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 inject CJS loaders in this way.

Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.