Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd8766c8

Browse files
committed
module: implementregister utility
PR-URL:nodejs/node#46826Backport-PR-URL:nodejs/node#50669Reviewed-By: Jacob Smith <jacob@frende.me>Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent43aab45 commitd8766c8

21 files changed

+490
-10
lines changed

‎graal-nodejs/doc/api/errors.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,6 +1234,23 @@ provided.
12341234
Encoding provided to`TextDecoder()` API was not one of the
12351235
[WHATWG Supported Encodings][].
12361236

1237+
<aid="ERR_ESM_LOADER_REGISTRATION_UNAVAILABLE"></a>
1238+
1239+
###`ERR_ESM_LOADER_REGISTRATION_UNAVAILABLE`
1240+
1241+
<!-- YAML
1242+
added: REPLACEME
1243+
-->
1244+
1245+
Programmatically registering custom ESM loaders
1246+
currently requires at least one custom loader to have been
1247+
registered via the`--experimental-loader` flag. A no-op
1248+
loader registered via CLI is sufficient
1249+
(for example:`--experimental-loader data:text/javascript,`;
1250+
do not omit the necessary trailing comma).
1251+
A future version of Node.js will support the programmatic
1252+
registration of loaders without needing to also use the flag.
1253+
12371254
<aid="ERR_EVAL_ESM_CANNOT_PRINT"></a>
12381255

12391256
###`ERR_EVAL_ESM_CANNOT_PRINT`

‎graal-nodejs/doc/api/esm.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,17 @@ console.log('some module!');
12361236
If you run`node--experimental-loader ./import-map-loader.js main.js`
12371237
the output will be`some module!`.
12381238
1239+
### Register loaders programmatically
1240+
1241+
<!-- YAML
1242+
added: REPLACEME
1243+
-->
1244+
1245+
In addition to using the`--experimental-loader` option in the CLI,
1246+
loaders can also be registered programmatically. You can find
1247+
detailed information about this process in the documentation page
1248+
for [`module.register()`][].
1249+
12391250
## Resolution and loading algorithm
12401251
12411252
### Features
@@ -1632,6 +1643,7 @@ success!
16321643
[`import.meta.url`]: #importmetaurl
16331644
[`import`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
16341645
[`module.createRequire()`]: module.md#modulecreaterequirefilename
1646+
[`module.register()`]: module.md#moduleregister
16351647
[`module.syncBuiltinESMExports()`]: module.md#modulesyncbuiltinesmexports
16361648
[`package.json`]: packages.md#nodejs-packagejson-field-definitions
16371649
[`port.ref()`]: https://nodejs.org/dist/latest-v17.x/docs/api/worker_threads.html#portref

‎graal-nodejs/doc/api/module.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,101 @@ isBuiltin('fs'); // true
7878
isBuiltin('wss');// false
7979
```
8080
81+
###`module.register()`
82+
83+
<!-- YAML
84+
added: REPLACEME
85+
-->
86+
87+
In addition to using the`--experimental-loader` option in the CLI,
88+
loaders can be registered programmatically using the
89+
`module.register()` method.
90+
91+
```mjs
92+
import {register }from'node:module';
93+
94+
register('http-to-https',import.meta.url);
95+
96+
// Because this is a dynamic `import()`, the `http-to-https` hooks will run
97+
// before importing `./my-app.mjs`.
98+
awaitimport('./my-app.mjs');
99+
```
100+
101+
In the example above, we are registering the`http-to-https` loader,
102+
but it will only be available for subsequently imported modules—in
103+
this case,`my-app.mjs`. If the`awaitimport('./my-app.mjs')` had
104+
instead been a static`import'./my-app.mjs'`, _the app would already
105+
have been loaded_ before the`http-to-https` hooks were
106+
registered. This is part of the design of ES modules, where static
107+
imports are evaluated from the leaves of the tree first back to the
108+
trunk. There can be static imports _within_`my-app.mjs`, which
109+
will not be evaluated until`my-app.mjs` is when it's dynamically
110+
imported.
111+
112+
The`--experimental-loader` flag of the CLI can be used together
113+
with the`register` function; the loaders registered with the
114+
function will follow the same evaluation chain of loaders registered
115+
within the CLI:
116+
117+
```console
118+
node \
119+
--experimental-loader unpkg \
120+
--experimental-loader http-to-https \
121+
--experimental-loader cache-buster \
122+
entrypoint.mjs
123+
```
124+
125+
```mjs
126+
// entrypoint.mjs
127+
import {URL }from'node:url';
128+
import {register }from'node:module';
129+
130+
constloaderURL=newURL('./my-programmatically-loader.mjs',import.meta.url);
131+
132+
register(loaderURL);
133+
awaitimport('./my-app.mjs');
134+
```
135+
136+
The`my-programmatic-loader.mjs` can leverage`unpkg`,
137+
`http-to-https`, and`cache-buster` loaders.
138+
139+
It's also possible to use`register` more than once:
140+
141+
```mjs
142+
// entrypoint.mjs
143+
import {URL }from'node:url';
144+
import {register }from'node:module';
145+
146+
register(newURL('./first-loader.mjs',import.meta.url));
147+
register('./second-loader.mjs',import.meta.url);
148+
awaitimport('./my-app.mjs');
149+
```
150+
151+
Both loaders (`first-loader.mjs` and`second-loader.mjs`) can use
152+
all the resources provided by the loaders registered in the CLI. But
153+
remember that they will only be available in the next imported
154+
module (`my-app.mjs`). The evaluation order of the hooks when
155+
importing`my-app.mjs` and consecutive modules in the example above
156+
will be:
157+
158+
```console
159+
resolve: second-loader.mjs
160+
resolve: first-loader.mjs
161+
resolve: cache-buster
162+
resolve: http-to-https
163+
resolve: unpkg
164+
load: second-loader.mjs
165+
load: first-loader.mjs
166+
load: cache-buster
167+
load: http-to-https
168+
load: unpkg
169+
globalPreload: second-loader.mjs
170+
globalPreload: first-loader.mjs
171+
globalPreload: cache-buster
172+
globalPreload: http-to-https
173+
globalPreload: unpkg
174+
```
175+
81176
###`module.syncBuiltinESMExports()`
82177
83178
<!-- YAML

‎graal-nodejs/lib/internal/errors.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,11 @@ E('ERR_ENCODING_INVALID_ENCODED_DATA', function(encoding, ret) {
10391039
},TypeError);
10401040
E('ERR_ENCODING_NOT_SUPPORTED','The "%s" encoding is not supported',
10411041
RangeError);
1042+
E('ERR_ESM_LOADER_REGISTRATION_UNAVAILABLE','Programmatically registering custom ESM loaders '+
1043+
'currently requires at least one custom loader to have been registered via the --experimental-loader '+
1044+
'flag. A no-op loader registered via CLI is sufficient (for example: `--experimental-loader '+
1045+
'"data:text/javascript,"` with the necessary trailing comma). A future version of Node.js '+
1046+
'will remove this requirement.',Error);
10421047
E('ERR_EVAL_ESM_CANNOT_PRINT','--print cannot be used with ESM input',Error);
10431048
E('ERR_EVENT_RECURSION','The event "%s" is already being dispatched',Error);
10441049
E('ERR_FALSY_VALUE_REJECTION',function(reason){

‎graal-nodejs/lib/internal/modules/esm/hooks.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ const {
4444
validateString,
4545
}=require('internal/validators');
4646

47+
const{ kEmptyObject}=require('internal/util');
48+
4749
const{
4850
defaultResolve,
4951
throwIfInvalidParentURL,
@@ -116,6 +118,23 @@ class Hooks {
116118
// Cache URLs we've already validated to avoid repeated validation
117119
#validatedUrls=newSafeSet();
118120

121+
/**
122+
* Import and register custom/user-defined module loader hook(s).
123+
*@param {string} urlOrSpecifier
124+
*@param {string} parentURL
125+
*/
126+
asyncregister(urlOrSpecifier,parentURL){
127+
constmoduleLoader=require('internal/process/esm_loader').esmLoader;
128+
129+
constkeyedExports=awaitmoduleLoader.import(
130+
urlOrSpecifier,
131+
parentURL,
132+
kEmptyObject,
133+
);
134+
135+
this.addCustomLoader(urlOrSpecifier,keyedExports);
136+
}
137+
119138
/**
120139
* Collect custom/user-defined module loader hook(s).
121140
* After all hooks have been collected, the global preload hook(s) must be initialized.

‎graal-nodejs/lib/internal/modules/esm/loader.js

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111
}=primordials;
1212

1313
const{
14+
ERR_ESM_LOADER_REGISTRATION_UNAVAILABLE,
1415
ERR_UNKNOWN_MODULE_FORMAT,
1516
}=require('internal/errors').codes;
1617
const{ getOptionValue}=require('internal/options');
@@ -297,12 +298,19 @@ class CustomizedModuleLoader extends DefaultModuleLoader {
297298
constructor(){
298299
super();
299300

300-
if(hooksProxy){
301-
// The worker proxy is shared across all instances, so don't recreate it if it already exists.
302-
return;
303-
}
304-
const{ HooksProxy}=require('internal/modules/esm/hooks');
305-
hooksProxy=newHooksProxy();// The user's custom hooks are loaded within the worker as part of its startup.
301+
getHooksProxy();
302+
}
303+
304+
/**
305+
* Register some loader specifier.
306+
*@param {string} originalSpecifier The specified URL path of the loader to
307+
* be registered.
308+
*@param {string} parentURL The parent URL from where the loader will be
309+
* registered if using it package name as specifier
310+
*@returns {{ format: string, url: URL['href'] }}
311+
*/
312+
register(originalSpecifier,parentURL){
313+
returnhooksProxy.makeSyncRequest('register',originalSpecifier,parentURL);
306314
}
307315

308316
/**
@@ -380,7 +388,46 @@ function createModuleLoader(useCustomLoadersIfPresent = true) {
380388
returnnewDefaultModuleLoader();
381389
}
382390

391+
/**
392+
* Get the HooksProxy instance. If it is not defined, then create a new one.
393+
*@returns {HooksProxy}
394+
*/
395+
functiongetHooksProxy(){
396+
if(!hooksProxy){
397+
const{ HooksProxy}=require('internal/modules/esm/hooks');
398+
hooksProxy=newHooksProxy();
399+
}
400+
401+
returnhooksProxy;
402+
}
403+
404+
/**
405+
* Register a single loader programmatically.
406+
*@param {string} specifier
407+
*@param {string} [parentURL]
408+
*@returns {void}
409+
*@example
410+
* ```js
411+
* register('./myLoader.js');
412+
* register('ts-node/esm', import.meta.url);
413+
* register('./myLoader.js', import.meta.url);
414+
* register(new URL('./myLoader.js', import.meta.url));
415+
* ```
416+
*/
417+
functionregister(specifier,parentURL='data:'){
418+
// TODO: Remove this limitation in a follow-up before `register` is released publicly
419+
if(getOptionValue('--experimental-loader').length<1){
420+
thrownewERR_ESM_LOADER_REGISTRATION_UNAVAILABLE();
421+
}
422+
423+
constmoduleLoader=require('internal/process/esm_loader').esmLoader;
424+
425+
moduleLoader.register(`${specifier}`,parentURL);
426+
}
427+
383428
module.exports={
384429
DefaultModuleLoader,
385430
createModuleLoader,
431+
getHooksProxy,
432+
register,
386433
};

‎graal-nodejs/lib/internal/modules/esm/utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,9 @@ async function initializeHooks() {
146146
load(url,context){returnhooks.load(url,context);}
147147
}
148148
constprivateModuleLoader=newModuleLoader();
149-
150149
constparentURL=pathToFileURL(cwd).href;
151150

151+
// TODO(jlenon7): reuse the `Hooks.register()` method for registering loaders.
152152
for(leti=0;i<customLoaderURLs.length;i++){
153153
constcustomLoaderURL=customLoaderURLs[i];
154154

‎graal-nodejs/lib/module.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
const{ findSourceMap}=require('internal/source_map/source_map_cache');
44
const{ Module}=require('internal/modules/cjs/loader');
5+
const{ register}=require('internal/modules/esm/loader');
56
const{ SourceMap}=require('internal/source_map/source_map');
67

78
Module.findSourceMap=findSourceMap;
9+
Module.register=register;
810
Module.SourceMap=SourceMap;
911
module.exports=Module;

‎graal-nodejs/test/es-module/test-esm-loader-hooks.mjs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,28 @@ describe('Loader hooks', { concurrency: true }, () => {
2424
assert.match(lines[3],/{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/);
2525
});
2626

27+
it('are called with all expected arguments using register function',async()=>{
28+
const{ code, signal, stdout, stderr}=awaitspawnPromisified(execPath,[
29+
'--no-warnings',
30+
'--experimental-loader=data:text/javascript,',
31+
'--input-type=module',
32+
'--eval',
33+
"import { register } from 'node:module';"+
34+
`register(${JSON.stringify(fixtures.fileURL('/es-module-loaders/hooks-input.mjs'))});`+
35+
`await import(${JSON.stringify(fixtures.fileURL('/es-modules/json-modules.mjs'))});`,
36+
]);
37+
38+
assert.strictEqual(stderr,'');
39+
assert.strictEqual(code,0);
40+
assert.strictEqual(signal,null);
41+
42+
constlines=stdout.split('\n');
43+
assert.match(lines[0],/{"url":"file:\/\/\/.*\/json-modules\.mjs","format":"test","shortCircuit":true}/);
44+
assert.match(lines[1],/{"source":{"type":"Buffer","data":\[.*\]},"format":"module","shortCircuit":true}/);
45+
assert.match(lines[2],/{"url":"file:\/\/\/.*\/experimental\.json","format":"test","shortCircuit":true}/);
46+
assert.match(lines[3],/{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/);
47+
});
48+
2749
describe('should handle never-settling hooks in ESM files',{concurrency:true},()=>{
2850
it('top-level await of a never-settling resolve',async()=>{
2951
const{ code, signal, stdout, stderr}=awaitspawnPromisified(execPath,[

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp