From the public number: Magician Carson
Hello everyone, I'm Casson.
In a recent release Node v18.6.0
, an experimental feature ESM Loader Hooks API [1] was introduced .
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.
Reference for this article:
Custom ESM loaders: Who, what, when, where, why, how [2]
Feature Introduction
Friends who have used webpack
it must know webpack
that there is a loader
concept in it, which is used to load and process different types of files, such css-loader
as url-loader
.
loader
The order of execution depends on the order webpack
in which the file tree is parsed and traversed internally.
What we will introduce today is ESM Loader Hooks
similar webpack loader
, except that the parsing and traversal of the file tree Node.js
is ESM
determined by the natively supported specification (rather than the packaging tool).
With different definitions loader
, each module in the project can be processed without the use of engineering tools .ESM
For example, after --experimental-loader
enabling the feature through the command on the command line, execute the following statement:
$> node --loader redirect.mjs app.mjs
Among them, app.mjs
it is "source file to be processed" , and .mjs
the suffix indicates that the file is a ESM
module (correspondingly, the .cjs
suffix refers to a CJS
module).
--loader
Used to specify custom , what ESM Loader
is specified here will be handed over to processing.redirect.mjs
app.mjs
redirect.mjs
redirect.mjs
code show as below:
// redirect.mjs
export function resolve(specifier, context, nextResolve) {
let redirect = 'app.prod.mjs';
switch(process.env.NODE_ENV) {
case 'development':
redirect = 'app.dev.mjs';
break;
case 'test':
redirect = 'app.test.mjs';
break;
}
return nextResolve(redirect);
}
redirect.mjs
The import path of the file will be rewritten according to "Node's current environment" .
For example, in the development environment ( process.env.NODE_ENV === 'development'
), after app.mjs
processing redirect.mjs
, it will be redirected to app.dev.mjs
.
ESM Loader Hooks API
The reason for the Hooks
word in it is because each "custom ESM Loader" can connect to other "custom ESM Loaders" (or Node.js
default provided ) like hooks ESM Loader
.
For example in the following statement:
$> node --loader c.mjs --loader b.mjs --loader a.mjs app.mjs
app.mjs
It will go through a b c
three "custom ESM Loaders" in turn .
The whole process is like a promise.then
chain (in fact, each ESM loader
does return one promise
).
Practical examples
For an example closer to day-to-day development, consider the following ESM
modules:
// app.tsx
import ReactDOM from 'react-dom/client';
import {
BrowserRouter,
useRoutes,
} 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'));
root.render(
<BrowserRouter>
<App />
<main>{useRoutes(routes)}</main>
</BrowserRouter>
);
This includes many Node.js
parts that cannot be processed, such as:
-
TS
Syntax (needs to be compiled intoJS
, and to process file descriptors intoNode.js
a recognizable form) -
JSX
Convert (need to compile toReact.createElement
orjsxRuntime.jsx
) -
Incoming files need to be processed
CSS
-
Need to handle remotely imported modules (introduced
routes
statements in the code)
Working with CSS files
Take processing CSS
files as an example, assuming the CSS
contents of the files are as follows:
.Container {
border: 1px solid black;
}
.SomeInnerPiece {
background-color: blue;
}
If you only need to generate a snapshot corresponding to the class name for testing purposes, you can implement a simple CSS loader
process that processes the input CSS
file and outputs the result in an Node.js
executable JSON
format:
{
"Container": "Container",
"SomeInnerPiece": "SomeInnerPiece"
}
Reference: Simple implementation of CSS loader [3]
Handling remotely imported modules
Another example is the handling of "processing remotely imported modules" .
When the https://
beginning of the file descriptor (that is , the part of the string in the import
declaration or expression) is identified, the module can be used to initiate a request and return the request corresponding to :import()
https
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 [4]
Summarize
When the ESM Loader Hooks
features become stable and the supporting loader
ecosystem is rich enough, many engineering requirements that originally required packaging tools can be Node.js
solved natively.
For example, to process the file mentioned above app.tsx
, 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?
References
ESM Loader Hooks API:https://nodejs.org/en/blog/release/v18.6.0/
[2]
Custom ESM loaders: Who, what, when, where, why, how:https://dev.to/jakobjingleheimer/custom-esm-loaders-who-what-when-where-why-how-4i1o
[3]
Simple implementation of CSS loader:https://github.com/JakobJingleheimer/demo-css-loader/blob/main/loader.mjs
[4]
Simple implementation of Https loader:https://github.com/nodejs/loaders-test/blob/835506a638c6002c1b2d42ab7137db3e7eda53fa/https-loader/loader.js