Features that may change the future of front-end engineering: ESM Loader Hooks

In the recent release Node v18.6.0 an experimental feature ESM Loader Hooks API was brought.

If he finally landed, it is likely to become a feature that changes the future of front-end engineering. Let’s talk about him in this article.

Welcome to join the human high-quality front-end framework group , with flying

Reference for this article:

Custom ESM loaders: Who, what, when, where, why, how

Feature Introduction

Friends who have used webpack must know that there is a concept of —3854b101fb8fb85f20b99bf6d03b594dc webpack in loader , which is used to load and process different types of files, such as css-loader , url-loader .

The execution order of —998ef07f30e4b586198fd5256c842b1f loader depends on the order in which webpack parses and traverses the file tree internally.

The ESM Loader Hooks to be introduced today is similar to webpack loader , except that the parsing and traversal of the file tree is supported by Node.js natively supported ESM (rather than packaging tools) determined.

By defining different loader , you can process each ESM module in the project without using engineering tools .

For example, after enabling the feature through the --experimental-loader command on the command line, execute the following statement:

 $> node --loader redirect.mjs app.mjs

其中, app.mjs的源文件, .mjs文件是个ESM模块(相对应的, .cjs Refers to the CJS module).

--loader自定义的ESM Loader ,这里redirect.mjs , app.mjsredirect.mjs deal with.

redirect.mjs code is as follows:

 // redirect.mjs
export function resolve(specifier, context, nextResolve) {
  let redirect = 'app.prod.mjs';

  switch(process.env.NODE_ENV) {
    case 'development':
      redirect = 'app.dev.mjs';
    case 'test':
      redirect = 'app.test.mjs';

  return nextResolve(redirect);

redirect.mjs will rewrite the import path of the file according to the current environment of Node .

For example, in the development environment ( process.env.NODE_ENV === 'development' ), app.mjs redirect.mjs will be redirected to app.dev.mjs

ESM Loader Hooks API contains the word Hooks because each custom ESM Loader can be connected to other custom ESM Loaders like hooks (or Node.js –provided default Node.js ESM Loader ).

For example in the following statement:

 $> node --loader c.mjs --loader b.mjs --loader a.mjs app.mjs

app.mjs will go through a b c three custom ESM Loaders in sequence.

The whole process is like a chain of promise.then (in fact, each ESM loader does return a promise ).

Practical examples

For an example closer to day-to-day development, consider the following ESM module:

 // app.tsx
import ReactDOM from 'react-dom/client';
import {
} from 'react-router-dom';

import App from './AppHeader.tsx';

import routes from 'https://example.com/routes.json' assert { type: 'json' };

import './global.css' assert { type: 'css' };

const root = ReactDOM.createRoot(document.getElementById('root'));

    <App />

This includes many parts Node.js that cannot be processed, such as:

  • TS syntax (need to be compiled into JS and process the file descriptor as Node.js recognizable form)
  • JSX conversion (need to compile to React.createElement or jsxRuntime.jsx )
  • Need to process the imported CSS file
  • Need to handle remotely imported modules (statements introduced in the code routes )

Working with CSS files

Taking the processing of the CSS file as an example, suppose the content of the CSS file is as follows:

 .Container {
  border: 1px solid black;

.SomeInnerPiece {
  background-color: blue;

For testing purposes, you only need to generate a snapshot corresponding to the class name, so you can implement a simple CSS loader , process the input CSS file, and output the result as Node.js JSON Format:

  "Container": "Container", 
  "SomeInnerPiece": "SomeInnerPiece"

Reference: Simple implementation of CSS loader

Handling remotely imported modules

Another example is a module that handles remote imports .

当识别到https:// ( import声明或import() )时, 可以利用https module initiates a request and returns the request corresponding to promise :

 import { get } from 'https';

export function load(url, context, nextLoad) {
  if (url.startsWith('https://')) {
    return new Promise((resolve, reject) => {
      get(url, (res) => {
        let data = '';
        res.on('data', chunk => data += chunk);
        res.on('end', () => resolve({
          format: 'module',
          shortCircuit: true,
          source: data,
      }).on('error', err => reject(err));

  return nextLoad(url, context);

Reference: Simple implementation of Https loader


When the ESM Loader Hooks features tend to be stable, and the supporting loader ecology is rich enough, many engineering requirements that originally required packaging tools can be used Node.js native solution .

For example, to process the app.tsx file mentioned above, just execute the following command:

 $> node --loader typescript-loader --loader css-loader --loader network-loader app.tsx

How much impact do you think this feature will have on future front-end engineering?