@@ -102,114 +102,7 @@ added: v20.6.0
102
102
* Returns: {any} returns whatever was returned by the ` initialize` hook.
103
103
104
104
Register a module that exports [hooks][] that customize Node.js module
105
- resolution and loading behavior.
106
-
107
- ` ` ` mjs
108
- import { register } from ' node:module' ;
109
-
110
- register (' http-to-https' , import .meta.url);
111
-
112
- // Because this is a dynamic `import()`, the `http-to-https` hooks will run
113
- // before importing `./my-app.mjs`.
114
- await import (' ./my-app.mjs' );
115
- ` ` `
116
-
117
- In the example above, we are registering the ` http- to- https` loader,
118
- but it will only be available for subsequently imported modules—in
119
- this case, ` my- app .mjs ` . If the ` await import (' ./my-app.mjs' )` had
120
- instead been a static ` import ' ./my-app.mjs' ` , _the app would already
121
- have been loaded_ before the ` http-to-https` hooks were
122
- registered. This is part of the design of ES modules, where static
123
- imports are evaluated from the leaves of the tree first back to the
124
- trunk. There can be static imports _within_ ` my-app.mjs` , which
125
- will not be evaluated until ` my-app.mjs` is when it's dynamically
126
- imported.
127
-
128
- The ` --experimental-loader` flag of the CLI can be used together
129
- with the ` register` function; the hooks registered with the
130
- function will follow the same evaluation chain of hooks registered
131
- within the CLI:
132
-
133
- ` ` ` console
134
- node \
135
- -- experimental- loader unpkg \
136
- -- experimental- loader http- to- https \
137
- -- experimental- loader cache- buster \
138
- entrypoint .mjs
139
- ` ` `
140
-
141
- ` ` ` mjs
142
- // entrypoint.mjs
143
- import { URL } from ' node:url' ;
144
- import { register } from ' node:module' ;
145
-
146
- const loaderURL = new URL (' ./my-programmatically-loader.mjs' , import .meta.url);
147
-
148
- register (loaderURL);
149
- await import (' ./my-app.mjs' );
150
- ` ` `
151
-
152
- The ` my- programmatic- loader .mjs ` can leverage ` unpkg` ,
153
- ` http- to- https` , and ` cache- buster` loaders.
154
-
155
- It's also possible to use ` register` more than once:
156
-
157
- ` ` ` mjs
158
- // entrypoint.mjs
159
- import { URL } from ' node:url' ;
160
- import { register } from ' node:module' ;
161
-
162
- register (new URL (' ./first-loader.mjs' , import .meta.url));
163
- register (' ./second-loader.mjs' , import .meta.url);
164
- await import (' ./my-app.mjs' );
165
- ` ` `
166
-
167
- Both loaders (` first- loader .mjs ` and ` second- loader .mjs ` ) can use
168
- all the resources provided by the loaders registered in the CLI. But
169
- remember that they will only be available in the next imported
170
- module (` my- app .mjs ` ). The evaluation order of the hooks when
171
- importing ` my- app .mjs ` and consecutive modules in the example above
172
- will be:
173
-
174
- ` ` ` console
175
- resolve: second- loader .mjs
176
- resolve: first- loader .mjs
177
- resolve: cache- buster
178
- resolve: http- to- https
179
- resolve: unpkg
180
- load: second- loader .mjs
181
- load: first- loader .mjs
182
- load: cache- buster
183
- load: http- to- https
184
- load: unpkg
185
- globalPreload: second- loader .mjs
186
- globalPreload: first- loader .mjs
187
- globalPreload: cache- buster
188
- globalPreload: http- to- https
189
- globalPreload: unpkg
190
- ` ` `
191
-
192
- This function can also be used to pass data to the loader's [` initialize` ][]
193
- hook; the data passed to the hook may include transferrable objects like ports.
194
-
195
- ` ` ` mjs
196
- import { register } from ' node:module' ;
197
- import { MessageChannel } from ' node:worker_threads' ;
198
-
199
- // This example showcases how a message channel can be used to
200
- // communicate to the loader, by sending `port2` to the loader.
201
- const { port1 , port2 } = new MessageChannel ();
202
-
203
- port1 .on (' message' , (msg ) => {
204
- console .log (msg);
205
- });
206
-
207
- register (' ./my-programmatic-loader.mjs' , {
208
- parentURL: import .meta.url,
209
- data: { number: 1 , port: port2 },
210
- transferList: [port2],
211
- });
212
- ` ` `
105
+ resolution and loading behavior. See [Customization hooks][].
213
106
214
107
### ` module.syncBuiltinESMExports()`
215
108
@@ -250,6 +143,8 @@ import('node:fs').then((esmFS) => {
250
143
});
251
144
` ` `
252
145
146
+ <i id="module_customization_hooks"></i>
147
+
253
148
## Customization Hooks
254
149
255
150
<!-- YAML
@@ -269,65 +164,233 @@ changes:
269
164
` globalPreload` ; added ` load` hook and ` getGlobalPreload` hook.
270
165
-->
271
166
272
- > Stability: 1 - Experimental
273
-
274
- > This API is currently being redesigned and will still change.
167
+ > Stability: 1.1 - Active development
275
168
276
169
<!-- type=misc -->
277
170
278
- To customize the default module resolution, loader hooks can optionally be
279
- provided via a ` -- experimental- loader ./ loader- name .mjs ` argument to Node.js.
171
+ <i id="enabling_module_customization_hooks"></i>
172
+
173
+ ### Enabling
174
+
175
+ Module resolution and loading can be customized by registering a file which
176
+ exports a set of hooks. This can be done using the [` register` ][] method
177
+ from ` node: module ` , which you can run before your application code by
178
+ using the ` -- import ` flag:
179
+
180
+ ` ` ` bash
181
+ node -- import ./register-hooks.js ./my-app.js
182
+ ` ` `
183
+
184
+ ` ` ` mjs
185
+ // register-hooks.js
186
+ import { register } from ' node:module' ;
187
+
188
+ register (' ./hooks.mjs' , import .meta.url);
189
+ ` ` `
190
+
191
+ ` ` ` cjs
192
+ // register-hooks.js
193
+ const { register } = require (' node:module' );
194
+ const { pathToFileURL } = require (' node:url' );
195
+
196
+ register (' ./hooks.mjs' , pathToFileURL (__filename ));
197
+ ` ` `
198
+
199
+ The file passed to ` -- import ` can also be an export from a dependency:
200
+
201
+ ` ` ` bash
202
+ node -- import some-package/register ./my-app.js
203
+ ` ` `
204
+
205
+ Where ` some- package ` has an [` " exports" ` ][] field defining the ` / register`
206
+ export to map to a file that calls ` register ()` , like the following ` register- hooks .js `
207
+ example.
208
+
209
+ Using ` -- import ` ensures that the hooks are registered before any application
210
+ files are imported, including the entry point of the application. Alternatively,
211
+ ` register ` can be called from the entry point, but dynamic ` import()` must be
212
+ used for any code that should be run after the hooks are registered:
213
+
214
+ ` ` ` mjs
215
+ import { register } from ' node:module' ;
216
+
217
+ register (' http-to-https' , import .meta.url);
218
+
219
+ // Because this is a dynamic `import()`, the `http-to-https` hooks will run
220
+ // to handle `./my-app.js` and any other files it imports or requires.
221
+ await import (' ./my-app.js' );
222
+ ` ` `
223
+
224
+ ` ` ` cjs
225
+ const { register } = require (' node:module' );
226
+ const { pathToFileURL } = require (' node:url' );
227
+
228
+ register (' http-to-https' , pathToFileURL (__filename ));
229
+
230
+ // Because this is a dynamic `import()`, the `http-to-https` hooks will run
231
+ // to handle `./my-app.js` and any other files it imports or requires.
232
+ import (' ./my-app.js' );
233
+ ` ` `
234
+
235
+ In this example, we are registering the ` http- to- https` hooks, but they will
236
+ only be available for subsequently imported modules—in this case, ` my- app .js `
237
+ and anything it references via ` import ` (and optionally ` require` ). If the
238
+ ` import(' ./my-app.js' )` had instead been a static ` import ' ./my-app.js' ` , the
239
+ app would have _already_ been loaded **before** the ` http-to-https` hooks were
240
+ registered. This due to the ES modules specification, where static imports are
241
+ evaluated from the leaves of the tree first, then back to the trunk. There can
242
+ be static imports _within_ ` my-app.js` , which will not be evaluated until
243
+ ` my-app.js` is dynamically imported.
280
244
281
- When hooks are used they apply to each subsequent loader, the entry point, and
282
- all ` import ` calls. They won't apply to ` require` calls; those still follow
283
- [CommonJS][] rules.
245
+ ` my-app.js` can also be CommonJS. Customization hooks will run for any
246
+ modules that it references via ` import` (and optionally ` require` ).
284
247
285
- Loaders follow the pattern of ` --require` :
248
+ Finally, if all you want to do is register hooks before your app runs and you
249
+ don't want to create a separate file for that purpose, you can pass a ` data:`
250
+ URL to ` --import` :
286
251
287
252
` ` ` bash
288
- node \
289
- -- experimental- loader unpkg \
290
- -- experimental- loader http- to- https \
291
- -- experimental- loader cache- buster
253
+ node -- import ' data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("http-to-https", pathToFileURL("./"));' ./my-app.js
292
254
` ` `
293
255
294
- These are called in the following sequence: ` cache- buster` calls
295
- ` http- to- https` which calls ` unpkg` .
256
+ ### Chaining
257
+
258
+ It's possible to call ` register` more than once:
259
+
260
+ ` ` ` mjs
261
+ // entrypoint.mjs
262
+ import { register } from ' node:module' ;
263
+
264
+ register (' ./first.mjs' , import .meta.url);
265
+ register (' ./second.mjs' , import .meta.url);
266
+ await import (' ./my-app.mjs' );
267
+ ` ` `
268
+
269
+ ` ` ` cjs
270
+ // entrypoint.cjs
271
+ const { register } = require (' node:module' );
272
+ const { pathToFileURL } = require (' node:url' );
273
+
274
+ const parentURL = pathToFileURL (__filename );
275
+ register (' ./first.mjs' , parentURL);
276
+ register (' ./second.mjs' , parentURL);
277
+ import (' ./my-app.mjs' );
278
+ ` ` `
279
+
280
+ In this example, the registered hooks will form chains. If both ` first .mjs ` and
281
+ ` second .mjs ` define a ` resolve` hook, both will be called, in the order they
282
+ were registered. The same applies to all the other hooks.
283
+
284
+ The registered hooks also affect ` register` itself. In this example,
285
+ ` second .mjs ` will be resolved and loaded per the hooks registered by
286
+ ` first .mjs ` . This allows for things like writing hooks in non-JavaScript
287
+ languages, so long as an earlier registered loader is one that transpiles into
288
+ JavaScript.
289
+
290
+ The ` register` method cannot be called from within the module that defines the
291
+ hooks.
292
+
293
+ ### Communication with module customization hooks
294
+
295
+ Module customization hooks run on a dedicated thread, separate from the main
296
+ thread that runs application code. This means mutating global variables won't
297
+ affect the other thread(s), and message channels must be used to communicate
298
+ between the threads.
299
+
300
+ The ` register` method can be used to pass data to an [` initialize` ][] hook. The
301
+ data passed to the hook may include transferrable objects like ports.
302
+
303
+ ` ` ` mjs
304
+ import { register } from ' node:module' ;
305
+ import { MessageChannel } from ' node:worker_threads' ;
306
+
307
+ // This example demonstrates how a message channel can be used to
308
+ // communicate with the hooks, by sending `port2` to the hooks.
309
+ const { port1 , port2 } = new MessageChannel ();
310
+
311
+ port1 .on (' message' , (msg ) => {
312
+ console .log (msg);
313
+ });
314
+
315
+ register (' ./my-hooks.mjs' , {
316
+ parentURL: import .meta.url,
317
+ data: { number: 1 , port: port2 },
318
+ transferList: [port2],
319
+ });
320
+ ` ` `
321
+
322
+ ` ` ` cjs
323
+ const { register } = require (' node:module' );
324
+ const { pathToFileURL } = require (' node:url' );
325
+ const { MessageChannel } = require (' node:worker_threads' );
326
+
327
+ // This example showcases how a message channel can be used to
328
+ // communicate with the hooks, by sending `port2` to the hooks.
329
+ const { port1 , port2 } = new MessageChannel ();
330
+
331
+ port1 .on (' message' , (msg ) => {
332
+ console .log (msg);
333
+ });
334
+
335
+ register (' ./my-hooks.mjs' , {
336
+ parentURL: pathToFileURL (__filename ),
337
+ data: { number: 1 , port: port2 },
338
+ transferList: [port2],
339
+ });
340
+ ` ` `
296
341
297
342
### Hooks
298
343
344
+ The [` register` ][] method can be used to register a module that exports a set of
345
+ hooks. The hooks are functions that are called by Node.js to customize the
346
+ module resolution and loading process. The exported functions must have specific
347
+ names and signatures, and they must be exported as named exports.
348
+
349
+ ` ` ` mjs
350
+ export async function initialize ({ number, port }) {
351
+ // Receive data from `register`, return data to `register`.
352
+ }
353
+
354
+ export async function resolve (specifier , context , nextResolve ) {
355
+ // Take an `import` or `require` specifier and resolve it to a URL.
356
+ }
357
+
358
+ export async function load (url , context , nextLoad ) {
359
+ // Take a resolved URL and return the source code to be evaluated.
360
+ }
361
+ ` ` `
362
+
299
363
Hooks are part of a chain, even if that chain consists of only one custom
300
364
(user-provided) hook and the default hook, which is always present. Hook
301
365
functions nest: each one must always return a plain object, and chaining happens
302
- as a result of each function calling ` next< hookName> ()` , which is a reference
303
- to the subsequent loader's hook.
366
+ as a result of each function calling ` next< hookName> ()` , which is a reference to
367
+ the subsequent loader's hook.
304
368
305
- A hook that returns a value lacking a required property triggers an exception.
306
- A hook that returns without calling ` next< hookName> ()` _and_ without returning
369
+ A hook that returns a value lacking a required property triggers an exception. A
370
+ hook that returns without calling ` next< hookName> ()` _and_ without returning
307
371
` shortCircuit: true ` also triggers an exception. These errors are to help
308
- prevent unintentional breaks in the chain.
372
+ prevent unintentional breaks in the chain. Return ` shortCircuit: true ` from a
373
+ hook to signal that the chain is intentionally ending at your hook.
309
374
310
- Hooks are run in a separate thread, isolated from the main. That means it is a
311
- different [realm](https://tc39.es/ecma262/#realm). The hooks thread may be
312
- terminated by the main thread at any time, so do not depend on asynchronous
313
- operations (like ` console .log ` ) to complete.
375
+ Hooks are run in a separate thread, isolated from the main thread where
376
+ application code runs. That means it is a different [realm][]. The hooks thread
377
+ may be terminated by the main thread at any time, so do not depend on
378
+ asynchronous operations (like ` console .log ` ) to complete.
314
379
315
380
#### ` initialize ()`
316
381
317
382
<!-- YAML
318
383
added: REPLACEME
319
384
-->
320
385
321
- > The loaders API is being redesigned. This hook may disappear or its
322
- > signature may change. Do not rely on the API described below.
386
+ > Stability: 1.1 - Active development
323
387
324
388
* ` data` {any} The data from ` register (loader, import .meta.url, { data })` .
325
389
* Returns: {any} The data to be returned to the caller of ` register` .
326
390
327
- The ` initialize` hook provides a way to define a custom function that runs
328
- in the loader's thread when the loader is initialized. Initialization happens
329
- when the loader is registered via [` register` ][] or registered via the
330
- ` --experimental-loader` command line option.
391
+ The ` initialize` hook provides a way to define a custom function that runs in
392
+ the hooks thread when the hooks module is initialized. Initialization happens
393
+ when the hooks module is registered via [` register` ][].
331
394
332
395
This hook can send and receive data from a [` register` ][] invocation, including
333
396
ports and other transferrable objects. The return value of ` initialize` must be
@@ -338,11 +401,10 @@ either:
338
401
[` port.postMessage` ][]),
339
402
* a ` Promise` resolving to one of the aforementioned values.
340
403
341
- Loader code:
404
+ Module customization code:
342
405
343
406
` ` ` mjs
344
- // In the below example this file is referenced as
345
- // '/path-to-my-loader.js'
407
+ // path-to-my-hooks.js
346
408
347
409
export async function initialize ({ number, port }) {
348
410
port .postMessage (` increment: ${ number + 1 } ` );
@@ -357,16 +419,16 @@ import assert from 'node:assert';
357
419
import { register } from ' node:module' ;
358
420
import { MessageChannel } from ' node:worker_threads' ;
359
421
360
- // This example showcases how a message channel can be used to
361
- // communicate between the main (application) thread and the loader
362
- // running on the loaders thread, by sending `port2` to the loader .
422
+ // This example showcases how a message channel can be used to communicate
423
+ // between the main (application) thread and the hooks running on the hooks
424
+ // thread, by sending `port2` to the `initialize` hook .
363
425
const { port1 , port2 } = new MessageChannel ();
364
426
365
427
port1 .on (' message' , (msg ) => {
366
428
assert .strictEqual (msg, ' increment: 2' );
367
429
});
368
430
369
- const result = register (' /path-to-my-loader .js' , {
431
+ const result = register (' . /path-to-my-hooks .js' , {
370
432
parentURL: import .meta.url,
371
433
data: { number: 1 , port: port2 },
372
434
transferList: [port2],
@@ -375,6 +437,30 @@ const result = register('/path-to-my-loader.js', {
375
437
assert .strictEqual (result, ' ok' );
376
438
` ` `
377
439
440
+ ` ` ` cjs
441
+ const assert = require (' node:assert' );
442
+ const { register } = require (' node:module' );
443
+ const { pathToFileURL } = require (' node:url' );
444
+ const { MessageChannel } = require (' node:worker_threads' );
445
+
446
+ // This example showcases how a message channel can be used to communicate
447
+ // between the main (application) thread and the hooks running on the hooks
448
+ // thread, by sending `port2` to the `initialize` hook.
449
+ const { port1 , port2 } = new MessageChannel ();
450
+
451
+ port1 .on (' message' , (msg ) => {
452
+ assert .strictEqual (msg, ' increment: 2' );
453
+ });
454
+
455
+ const result = register (' ./path-to-my-hooks.js' , {
456
+ parentURL: pathToFileURL (__filename ),
457
+ data: { number: 1 , port: port2 },
458
+ transferList: [port2],
459
+ });
460
+
461
+ assert .strictEqual (result, ' ok' );
462
+ ` ` `
463
+
378
464
#### ` resolve (specifier, context, nextResolve)`
379
465
380
466
<!-- YAML
@@ -393,8 +479,7 @@ changes:
393
479
description: Add support for import assertions.
394
480
-->
395
481
396
- > The loaders API is being redesigned. This hook may disappear or its
397
- > signature may change. Do not rely on the API described below.
482
+ > Stability: 1.2 - Release candidate
398
483
399
484
* ` specifier` {string}
400
485
* ` context` {Object}
@@ -417,21 +502,21 @@ changes:
417
502
terminate the chain of ` resolve` hooks. **Default:** ` false `
418
503
* ` url` {string} The absolute URL to which this input resolves
419
504
420
- > **Caveat ** Despite support for returning promises and async functions, calls
505
+ > **Warning ** Despite support for returning promises and async functions, calls
421
506
> to ` resolve` may block the main thread which can impact performance.
422
507
423
508
The ` resolve` hook chain is responsible for telling Node.js where to find and
424
- how to cache a given ` import ` statement or expression. It can optionally return
425
- its format (such as ` ' module' ` ) as a hint to the ` load` hook. If a format is
426
- specified, the ` load` hook is ultimately responsible for providing the final
427
- ` format` value (and it is free to ignore the hint provided by ` resolve ` ); if
428
- ` resolve` provides a ` format` , a custom ` load` hook is required even if only to
429
- pass the value to the Node.js default ` load` hook.
509
+ how to cache a given ` import ` statement or expression, or ` require ` call . It can
510
+ optionally return a format (such as ` ' module' ` ) as a hint to the ` load` hook. If
511
+ a format is specified, the ` load` hook is ultimately responsible for providing
512
+ the final ` format` value (and it is free to ignore the hint provided by
513
+ ` resolve` ); if ` resolve ` provides a ` format` , a custom ` load` hook is required
514
+ even if only to pass the value to the Node.js default ` load` hook.
430
515
431
516
Import type assertions are part of the cache key for saving loaded modules into
432
- the internal module cache. The ` resolve` hook is responsible for
433
- returning an ` importAssertions` object if the module should be cached with
434
- different assertions than were present in the source code.
517
+ the internal module cache. The ` resolve` hook is responsible for returning an
518
+ ` importAssertions` object if the module should be cached with different
519
+ assertions than were present in the source code.
435
520
436
521
The ` conditions` property in ` context` is an array of conditions for
437
522
[package exports conditions][Conditional exports] that apply to this resolution
@@ -445,7 +530,7 @@ Node.js module specifier resolution behavior_ when calling `defaultResolve`, the
445
530
` context.conditions` array originally passed into the ` resolve` hook.
446
531
447
532
` ` ` mjs
448
- export function resolve (specifier , context , nextResolve ) {
533
+ export async function resolve (specifier , context , nextResolve ) {
449
534
const { parentURL = null } = context;
450
535
451
536
if (Math .random () > 0.5 ) { // Some condition.
@@ -490,11 +575,7 @@ changes:
490
575
its return.
491
576
-->
492
577
493
- > The loaders API is being redesigned. This hook may disappear or its
494
- > signature may change. Do not rely on the API described below.
495
-
496
- > In a previous version of this API, this was split across 3 separate, now
497
- > deprecated, hooks (` getFormat` , ` getSource` , and ` transformSource` ).
578
+ > Stability: 1.2 - Release candidate
498
579
499
580
* ` url` {string} The URL returned by the ` resolve` chain
500
581
* ` context` {Object}
@@ -512,8 +593,8 @@ changes:
512
593
terminate the chain of ` resolve` hooks. **Default:** ` false `
513
594
* ` source` {string|ArrayBuffer|TypedArray} The source for Node.js to evaluate
514
595
515
- The ` load` hook provides a way to define a custom method of determining how
516
- a URL should be interpreted, retrieved, and parsed. It is also in charge of
596
+ The ` load` hook provides a way to define a custom method of determining how a
597
+ URL should be interpreted, retrieved, and parsed. It is also in charge of
517
598
validating the import assertion.
518
599
519
600
The final value of ` format` must be one of the following:
@@ -529,19 +610,23 @@ The final value of `format` must be one of the following:
529
610
The value of ` source` is ignored for type ` ' builtin' ` because currently it is
530
611
not possible to replace the value of a Node.js builtin (core) module.
531
612
532
- The value of ` source` can be omitted for type ` ' commonjs' ` . When a ` source` is
533
- provided, all ` require` calls from this module will be processed by the ESM
534
- loader with registered ` resolve` and ` load` hooks; all ` require .resolve ` calls
535
- from this module will be processed by the ESM loader with registered ` resolve`
536
- hooks; ` require .extensions ` and monkey-patching on the CommonJS module loader
537
- will not apply. If ` source` is undefined or ` null ` , it will be handled by the
538
- CommonJS module loader and ` require` /` require .resolve ` calls will not go through
539
- the registered hooks. This behavior for nullish ` source` is temporary — in the
540
- future, nullish ` source` will not be supported.
541
-
542
- The Node.js own ` load` implementation, which is the value of ` next` for the last
543
- loader in the ` load` chain, returns ` null ` for ` source` when ` format` is
544
- ` ' commonjs' ` for backward compatibility. Here is an example loader that would
613
+ Omitting vs providing a ` source` for ` ' commonjs' ` has very different effects:
614
+
615
+ * When a ` source` is provided, all ` require` calls from this module will be
616
+ processed by the ESM loader with registered ` resolve` and ` load` hooks; all
617
+ ` require .resolve ` calls from this module will be processed by the ESM loader
618
+ with registered ` resolve` hooks; only a subset of the CommonJS API will be
619
+ available (e.g. no ` require .extensions ` , no ` require .cache ` , no
620
+ ` require .resolve .paths ` ) and monkey-patching on the CommonJS module loader
621
+ will not apply.
622
+ * If ` source` is undefined or ` null ` , it will be handled by the CommonJS module
623
+ loader and ` require` /` require .resolve ` calls will not go through the
624
+ registered hooks. This behavior for nullish ` source` is temporary — in the
625
+ future, nullish ` source` will not be supported.
626
+
627
+ The Node.js internal ` load` implementation, which is the value of ` next` for the
628
+ last hook in the ` load` chain, returns ` null ` for ` source` when ` format` is
629
+ ` ' commonjs' ` for backward compatibility. Here is an example hook that would
545
630
opt-in to using the non-default behavior:
546
631
547
632
` ` ` mjs
@@ -556,7 +641,7 @@ export async function load(url, context, nextLoad) {
556
641
}
557
642
` ` `
558
643
559
- > **Caveat **: The ESM ` load` hook and namespaced exports from CommonJS modules
644
+ > **Warning **: The ESM ` load` hook and namespaced exports from CommonJS modules
560
645
> are incompatible. Attempting to use them together will result in an empty
561
646
> object from the import. This may be addressed in the future.
562
647
@@ -569,9 +654,9 @@ If the source value of a text-based format (i.e., `'json'`, `'module'`)
569
654
is not a string, it is converted to a string using [` util .TextDecoder ` ][].
570
655
571
656
The ` load` hook provides a way to define a custom method for retrieving the
572
- source code of an ES module specifier . This would allow a loader to potentially
573
- avoid reading files from disk. It could also be used to map an unrecognized
574
- format to a supported one, for example ` yaml` to ` module ` .
657
+ source code of a resolved URL . This would allow a loader to potentially avoid
658
+ reading files from disk. It could also be used to map an unrecognized format to
659
+ a supported one, for example ` yaml` to ` module ` .
575
660
576
661
` ` ` mjs
577
662
export async function load (url , context , nextLoad ) {
@@ -611,11 +696,11 @@ changes:
611
696
description: Add support for chaining globalPreload hooks.
612
697
-->
613
698
614
- > This hook will be removed in a future version. Use [` initialize` ][] instead.
615
- > When a loader has an ` initialize` export, ` globalPreload` will be ignored.
699
+ > Stability: 1.0 - Early development
616
700
617
- > In a previous version of this API, this hook was named
618
- > ` getGlobalPreloadCode` .
701
+ > **Warning:** This hook will be removed in a future version. Use
702
+ > [` initialize` ][] instead. When a hooks module has an ` initialize` export,
703
+ > ` globalPreload` will be ignored.
619
704
620
705
* ` context` {Object} Information to assist the preload code
621
706
* ` port` {MessagePort}
@@ -647,24 +732,25 @@ const require = createRequire(cwd() + '/<preload>');
647
732
}
648
733
` ` `
649
734
650
- In order to allow communication between the application and the loader, another
651
- argument is provided to the preload code: ` port` . This is available as a
652
- parameter to the loader hook and inside of the source text returned by the hook.
653
- Some care must be taken in order to properly call [` port .ref ()` ][] and
735
+ Another argument is provided to the preload code: ` port` . This is available as a
736
+ parameter to the hook and inside of the source text returned by the hook. This
737
+ functionality has been moved to the ` initialize` hook.
738
+
739
+ Care must be taken in order to properly call [` port .ref ()` ][] and
654
740
[` port .unref ()` ][] to prevent a process from being in a state where it won't
655
741
close normally.
656
742
657
743
` ` ` mjs
658
744
/**
659
- * This example has the application context send a message to the loader
745
+ * This example has the application context send a message to the hook
660
746
* and sends the message back to the application context
661
747
*/
662
748
export function globalPreload ({ port }) {
663
749
port .onmessage = (evt ) => {
664
750
port .postMessage (evt .data );
665
751
};
666
752
return ` \
667
- port.postMessage('console.log("I went to the Loader and back");');
753
+ port.postMessage('console.log("I went to the hook and back");');
668
754
port.onmessage = (evt) => {
669
755
eval(evt.data);
670
756
};
@@ -674,22 +760,23 @@ export function globalPreload({ port }) {
674
760
675
761
### Examples
676
762
677
- The various loader hooks can be used together to accomplish wide-ranging
678
- customizations of the Node.js code loading and evaluation behaviors.
763
+ The various module customization hooks can be used together to accomplish
764
+ wide-ranging customizations of the Node.js code loading and evaluation
765
+ behaviors.
679
766
680
- #### HTTPS loader
767
+ #### Import from HTTPS
681
768
682
769
In current Node.js, specifiers starting with ` https: // ` are experimental (see
683
770
[HTTPS and HTTP imports][]).
684
771
685
- The loader below registers hooks to enable rudimentary support for such
772
+ The hook below registers hooks to enable rudimentary support for such
686
773
specifiers . While this may seem like a significant improvement to Node .js core
687
- functionality, there are substantial downsides to actually using this loader :
774
+ functionality, there are substantial downsides to actually using these hooks :
688
775
performance is much slower than loading files from disk, there is no caching,
689
776
and there is no security.
690
777
691
778
` ` ` mjs
692
- // https-loader .mjs
779
+ // https-hooks .mjs
693
780
import { get } from 'node:https';
694
781
695
782
export function load(url, context, nextLoad) {
@@ -724,59 +811,42 @@ import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffee
724
811
console.log(VERSION);
725
812
` ` `
726
813
727
- With the preceding loader , running
728
- ` node --experimental-loader ./https-loader .mjs ./main.mjs`
814
+ With the preceding hooks module , running
815
+ ` node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL(" ./https-hooks .mjs"));' ./main.mjs`
729
816
prints the current version of CoffeeScript per the module at the URL in
730
817
` main.mjs` .
731
818
732
- #### Transpiler loader
819
+ #### Transpilation
733
820
734
821
Sources that are in formats Node .js doesn' t understand can be converted into
735
822
JavaScript using the [`load` hook][load hook].
736
823
737
- This is less performant than transpiling source files before running
738
- Node.js; a transpiler loader should only be used for development and testing
739
- purposes.
824
+ This is less performant than transpiling source files before running Node.js;
825
+ transpiler hooks should only be used for development and testing purposes.
740
826
741
827
```mjs
742
- // coffeescript-loader .mjs
828
+ // coffeescript-hooks .mjs
743
829
import { readFile } from ' node: fs/ promises' ;
744
830
import { dirname, extname, resolve as resolvePath } from ' node: path' ;
745
831
import { cwd } from ' node: process ' ;
746
832
import { fileURLToPath, pathToFileURL } from ' node: url' ;
747
- import CoffeeScript from ' coffeescript' ;
833
+ import coffeescript from ' coffeescript' ;
748
834
749
- const baseURL = pathToFileURL(`${cwd()}/`).href ;
835
+ const extensionsRegex = / \. (coffee|litcoffee|coffee \. md)$/ ;
750
836
751
837
export async function load(url, context, nextLoad) {
752
838
if (extensionsRegex.test(url)) {
753
- // Now that we patched resolve to let CoffeeScript URLs through, we need to
754
- // tell Node.js what format such URLs should be interpreted as. Because
755
- // CoffeeScript transpiles into JavaScript, it should be one of the two
756
- // JavaScript formats: ' commonjs' or ' module ' .
757
-
758
839
// CoffeeScript files can be either CommonJS or ES modules, so we want any
759
840
// CoffeeScript file to be treated by Node.js the same as a .js file at the
760
841
// same location. To determine how Node.js would interpret an arbitrary .js
761
842
// file, search up the file system for the nearest parent package.json file
762
843
// and read its "type" field.
763
844
const format = await getPackageType(url);
764
- // When a hook returns a format of ' commonjs' , `source` is ignored.
765
- // To handle CommonJS files, a handler needs to be registered with
766
- // `require.extensions` in order to process the files with the CommonJS
767
- // loader. Avoiding the need for a separate CommonJS handler is a future
768
- // enhancement planned for ES module loaders.
769
- if (format === ' commonjs' ) {
770
- return {
771
- format,
772
- shortCircuit: true,
773
- };
774
- }
775
845
776
846
const { source: rawSource } = await nextLoad(url, { ...context, format });
777
847
// This hook converts CoffeeScript source code into JavaScript source code
778
848
// for all imported CoffeeScript files.
779
- const transformedSource = coffeeCompile (rawSource.toString(), url);
849
+ const transformedSource = coffeescript.compile (rawSource.toString(), url);
780
850
781
851
return {
782
852
format,
@@ -833,23 +903,22 @@ console.log "Brought to you by Node.js version #{version}"
833
903
export scream = (str ) - > str .toUpperCase ()
834
904
` ` `
835
905
836
- With the preceding loader , running
837
- ` node -- experimental - loader ./ coffeescript- loader .mjs main .coffee `
906
+ With the preceding hooks module , running
907
+ ` node -- import ' data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL(" ./coffeescript-hooks .mjs")); ' ./ main.coffee`
838
908
causes ` main.coffee` to be turned into JavaScript after its source code is
839
909
loaded from disk but before Node.js executes it; and so on for any ` .coffee` ,
840
910
` .litcoffee` or ` .coffee.md` files referenced via ` import` statements of any
841
911
loaded file.
842
912
843
- #### "import map" loader
913
+ #### Import maps
844
914
845
- The previous two loaders defined ` load` hooks. This is an example of a loader
846
- that does its work via the ` resolve` hook. This loader reads an
847
- ` import-map.json` file that specifies which specifiers to override to another
848
- URL (this is a very simplistic implemenation of a small subset of the
849
- "import maps" specification).
915
+ The previous two examples defined ` load` hooks. This is an example of a
916
+ ` resolve` hook. This hooks module reads an ` import-map.json` file that defines
917
+ which specifiers to override to other URLs (this is a very simplistic
918
+ implementation of a small subset of the "import maps" specification).
850
919
851
920
` ` ` mjs
852
- // import-map-loader .js
921
+ // import-map-hooks .js
853
922
import fs from ' node:fs/promises' ;
854
923
855
924
const { imports } = JSON .parse (await fs .readFile (' import-map.json' ));
@@ -863,7 +932,7 @@ export async function resolve(specifier, context, nextResolve) {
863
932
}
864
933
` ` `
865
934
866
- Let's assume we have these files:
935
+ With these files:
867
936
868
937
` ` ` mjs
869
938
// main.js
@@ -884,19 +953,8 @@ import 'a-module';
884
953
console .log (' some module!' );
885
954
` ` `
886
955
887
- If you run ` node -- experimental- loader ./ import -map-loader.js main.js`
888
- the output will be ` some module!` .
889
-
890
- ### Register loaders programmatically
891
-
892
- <!-- YAML
893
- added: REPLACEME
894
- -->
895
-
896
- In addition to using the ` --experimental-loader` option in the CLI,
897
- loaders can also be registered programmatically. You can find
898
- detailed information about this process in the documentation page
899
- for [` module.register()` ][].
956
+ Running ` node -- import ' data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./import-map-hooks.js"));' main.js`
957
+ should print ` some module!` .
900
958
901
959
## Source map v3 support
902
960
@@ -1044,9 +1102,11 @@ returned object contains the following keys:
1044
1102
1045
1103
[CommonJS]: modules.md
1046
1104
[Conditional exports]: packages.md#conditional-exports
1105
+ [Customization hooks]: #customization-hooks
1047
1106
[ES Modules]: esm.md
1048
1107
[HTTPS and HTTP imports]: esm.md#https-and-http-imports
1049
1108
[Source map v3 format]: https://sourcemaps.info/spec.html#h.mofvlxcwqzej
1109
+ [` " exports" ` ]: packages.md#exports
1050
1110
[` -- enable- source- maps` ]: cli.md#--enable-source-maps
1051
1111
[` ArrayBuffer ` ]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
1052
1112
[` NODE_V8_COVERAGE = dir` ]: cli.md#node_v8_coveragedir
@@ -1055,7 +1115,6 @@ returned object contains the following keys:
1055
1115
[` TypedArray` ]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
1056
1116
[` Uint8Array ` ]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
1057
1117
[` initialize` ]: #initialize
1058
- [` module .register ()` ]: #moduleregisterspecifier-parenturl-options
1059
1118
[` module ` ]: modules.md#the-module-object
1060
1119
[` port .postMessage ` ]: worker_threads.md#portpostmessagevalue-transferlist
1061
1120
[` port .ref ()` ]: worker_threads.md#portref
@@ -1066,5 +1125,6 @@ returned object contains the following keys:
1066
1125
[hooks]: #customization-hooks
1067
1126
[load hook]: #loadurl-context-nextload
1068
1127
[module wrapper]: modules.md#the-module-wrapper
1128
+ [realm]: https://tc39.es/ecma262/#realm
1069
1129
[source map include directives]: https://sourcemaps.info/spec.html#h.lmz475t4mvbx
1070
1130
[transferrable objects]: worker_threads.md#portpostmessagevalue-transferlist
0 commit comments