@@ -419,9 +419,7 @@ class that closely mirrors [Module Record][]s as defined in the ECMAScript
419419specification.
420420
421421Unlike ` vm.Script ` however, every ` vm.Module ` object is bound to a context from
422- its creation. Operations on ` vm.Module ` objects are intrinsically asynchronous,
423- in contrast with the synchronous nature of ` vm.Script ` objects. The use of
424- 'async' functions can help with manipulating ` vm.Module ` objects.
422+ its creation.
425423
426424Using a ` vm.Module ` object requires three distinct steps: creation/parsing,
427425linking, and evaluation. These three steps are illustrated in the following
@@ -449,7 +447,7 @@ const contextifiedObject = vm.createContext({
449447// Here, we attempt to obtain the default export from the module "foo", and
450448// put it into local binding "secret".
451449
452- const bar = new vm.SourceTextModule (`
450+ const rootModule = new vm.SourceTextModule (`
453451 import s from 'foo';
454452 s;
455453 print(s);
@@ -459,47 +457,56 @@ const bar = new vm.SourceTextModule(`
459457//
460458// "Link" the imported dependencies of this Module to it.
461459//
462- // The provided linking callback (the "linker") accepts two arguments: the
463- // parent module (`bar` in this case) and the string that is the specifier of
464- // the imported module. The callback is expected to return a Module that
465- // corresponds to the provided specifier, with certain requirements documented
466- // in `module.link()`.
467- //
468- // If linking has not started for the returned Module, the same linker
469- // callback will be called on the returned Module.
460+ // Obtain the requested dependencies of a SourceTextModule by
461+ // `sourceTextModule.moduleRequests` and resolve them.
470462//
471463// Even top-level Modules without dependencies must be explicitly linked. The
472- // callback provided would never be called, however.
473- //
474- // The link() method returns a Promise that will be resolved when all the
475- // Promises returned by the linker resolve.
464+ // array passed to `sourceTextModule.linkRequests(modules)` can be
465+ // empty, however.
476466//
477- // Note: This is a contrived example in that the linker function creates a new
478- // "foo" module every time it is called. In a full-fledged module system, a
479- // cache would probably be used to avoid duplicated modules.
480-
481- async function linker (specifier , referencingModule ) {
482- if (specifier === ' foo' ) {
483- return new vm.SourceTextModule (`
484- // The "secret" variable refers to the global variable we added to
485- // "contextifiedObject" when creating the context.
486- export default secret;
487- ` , { context: referencingModule .context });
488-
489- // Using `contextifiedObject` instead of `referencingModule.context`
490- // here would work as well.
491- }
492- throw new Error (` Unable to resolve dependency: ${ specifier} ` );
467+ // Note: This is a contrived example in that the resolveAndLinkDependencies
468+ // creates a new "foo" module every time it is called. In a full-fledged
469+ // module system, a cache would probably be used to avoid duplicated modules.
470+
471+ const moduleMap = new Map ([
472+ [' root' , rootModule],
473+ ]);
474+
475+ function resolveAndLinkDependencies (module ) {
476+ const requestedModules = module .moduleRequests .map ((request ) => {
477+ // In a full-fledged module system, the resolveAndLinkDependencies would
478+ // resolve the module with the module cache key `[specifier, attributes]`.
479+ // In this example, we just use the specifier as the key.
480+ const specifier = request .specifier ;
481+
482+ let requestedModule = moduleMap .get (specifier);
483+ if (requestedModule === undefined ) {
484+ requestedModule = new vm.SourceTextModule (`
485+ // The "secret" variable refers to the global variable we added to
486+ // "contextifiedObject" when creating the context.
487+ export default secret;
488+ ` , { context: referencingModule .context });
489+ moduleMap .set (specifier, linkedModule);
490+ // Resolve the dependencies of the new module as well.
491+ resolveAndLinkDependencies (requestedModule);
492+ }
493+
494+ return requestedModule;
495+ });
496+
497+ module .linkRequests (requestedModules);
493498}
494- await bar .link (linker);
499+
500+ resolveAndLinkDependencies (rootModule);
501+ rootModule .instantiate ();
495502
496503// Step 3
497504//
498505// Evaluate the Module. The evaluate() method returns a promise which will
499506// resolve after the module has finished evaluating.
500507
501508// Prints 42.
502- await bar .evaluate ();
509+ await rootModule .evaluate ();
503510```
504511
505512``` cjs
@@ -521,7 +528,7 @@ const contextifiedObject = vm.createContext({
521528 // Here, we attempt to obtain the default export from the module "foo", and
522529 // put it into local binding "secret".
523530
524- const bar = new vm.SourceTextModule (`
531+ const rootModule = new vm.SourceTextModule (`
525532 import s from 'foo';
526533 s;
527534 print(s);
@@ -531,47 +538,56 @@ const contextifiedObject = vm.createContext({
531538 //
532539 // "Link" the imported dependencies of this Module to it.
533540 //
534- // The provided linking callback (the "linker") accepts two arguments: the
535- // parent module (`bar` in this case) and the string that is the specifier of
536- // the imported module. The callback is expected to return a Module that
537- // corresponds to the provided specifier, with certain requirements documented
538- // in `module.link()`.
539- //
540- // If linking has not started for the returned Module, the same linker
541- // callback will be called on the returned Module.
541+ // Obtain the requested dependencies of a SourceTextModule by
542+ // `sourceTextModule.moduleRequests` and resolve them.
542543 //
543544 // Even top-level Modules without dependencies must be explicitly linked. The
544- // callback provided would never be called, however.
545- //
546- // The link() method returns a Promise that will be resolved when all the
547- // Promises returned by the linker resolve.
545+ // array passed to `sourceTextModule.linkRequests(modules)` can be
546+ // empty, however.
548547 //
549- // Note: This is a contrived example in that the linker function creates a new
550- // "foo" module every time it is called. In a full-fledged module system, a
551- // cache would probably be used to avoid duplicated modules.
552-
553- async function linker (specifier , referencingModule ) {
554- if (specifier === ' foo' ) {
555- return new vm.SourceTextModule (`
556- // The "secret" variable refers to the global variable we added to
557- // "contextifiedObject" when creating the context.
558- export default secret;
559- ` , { context: referencingModule .context });
548+ // Note: This is a contrived example in that the resolveAndLinkDependencies
549+ // creates a new "foo" module every time it is called. In a full-fledged
550+ // module system, a cache would probably be used to avoid duplicated modules.
551+
552+ const moduleMap = new Map ([
553+ [' root' , rootModule],
554+ ]);
555+
556+ function resolveAndLinkDependencies (module ) {
557+ const requestedModules = module .moduleRequests .map ((request ) => {
558+ // In a full-fledged module system, the resolveAndLinkDependencies would
559+ // resolve the module with the module cache key `[specifier, attributes]`.
560+ // In this example, we just use the specifier as the key.
561+ const specifier = request .specifier ;
562+
563+ let requestedModule = moduleMap .get (specifier);
564+ if (requestedModule === undefined ) {
565+ requestedModule = new vm.SourceTextModule (`
566+ // The "secret" variable refers to the global variable we added to
567+ // "contextifiedObject" when creating the context.
568+ export default secret;
569+ ` , { context: referencingModule .context });
570+ moduleMap .set (specifier, linkedModule);
571+ // Resolve the dependencies of the new module as well.
572+ resolveAndLinkDependencies (requestedModule);
573+ }
574+
575+ return requestedModule;
576+ });
560577
561- // Using `contextifiedObject` instead of `referencingModule.context`
562- // here would work as well.
563- }
564- throw new Error (` Unable to resolve dependency: ${ specifier} ` );
578+ module .linkRequests (requestedModules);
565579 }
566- await bar .link (linker);
580+
581+ resolveAndLinkDependencies (rootModule);
582+ rootModule .instantiate ();
567583
568584 // Step 3
569585 //
570586 // Evaluate the Module. The evaluate() method returns a promise which will
571587 // resolve after the module has finished evaluating.
572588
573589 // Prints 42.
574- await bar .evaluate ();
590+ await rootModule .evaluate ();
575591})();
576592```
577593
@@ -660,6 +676,10 @@ changes:
660676Link module dependencies . This method must be called before evaluation, and
661677can only be called once per module .
662678
679+ Use [` sourceTextModule.linkRequests(modules)` ][] and
680+ [` sourceTextModule.instantiate()` ][] to link modules either synchronously or
681+ asynchronously.
682+
663683The function is expected to return a `Module` object or a `Promise` that
664684eventually resolves to a `Module` object. The returned `Module` must satisfy the
665685following two invariants:
@@ -805,8 +825,9 @@ const module = new vm.SourceTextModule(
805825 meta.prop = {};
806826 },
807827 });
808- // Since module has no dependencies, the linker function will never be called.
809- await module.link(() => {});
828+ // The module has an empty `moduleRequests` array.
829+ module.linkRequests([]);
830+ module.instantiate();
810831await module.evaluate();
811832
812833// Now, Object.prototype.secret will be equal to 42.
@@ -832,8 +853,9 @@ const contextifiedObject = vm.createContext({ secret: 42 });
832853 meta.prop = {};
833854 },
834855 });
835- // Since module has no dependencies, the linker function will never be called.
836- await module.link(() => {});
856+ // The module has an empty `moduleRequests` array.
857+ module.linkRequests([]);
858+ module.instantiate();
837859 await module.evaluate();
838860 // Now, Object.prototype.secret will be equal to 42.
839861 //
@@ -898,6 +920,69 @@ to disallow any changes to it.
898920Corresponds to the `[[RequestedModules]]` field of [Cyclic Module Record][]s in
899921the ECMAScript specification.
900922
923+ ### `sourceTextModule.instantiate()`
924+
925+ <!-- YAML
926+ added: REPLACEME
927+ -->
928+
929+ * Returns: {undefined}
930+
931+ Instantiate the module with the linked requested modules.
932+
933+ This resolves the imported bindings of the module, including re-exported
934+ binding names. When there are any bindings that cannot be resolved,
935+ an error would be thrown synchronously.
936+
937+ If the requested modules include cyclic dependencies, the
938+ [`sourceTextModule.linkRequests(modules)`][] method must be called on all
939+ modules in the cycle before calling this method.
940+
941+ ### `sourceTextModule.linkRequests(modules)`
942+
943+ <!-- YAML
944+ added: REPLACEME
945+ -->
946+
947+ * `modules` {vm.Module\[ ]} Array of `vm.Module` objects that this module depends on.
948+ The order of the modules in the array is the order of
949+ [`sourceTextModule.moduleRequests`][].
950+ * Returns: {undefined}
951+
952+ Link module dependencies. This method must be called before evaluation, and
953+ can only be called once per module.
954+
955+ The order of the module instances in the `modules` array should correspond to the order of
956+ [`sourceTextModule.moduleRequests`][] being resolved. If two module requests have the same
957+ specifier and import attributes, they must be resolved with the same module instance or an
958+ `ERR_MODULE_LINK_MISMATCH` would be thrown. For example, when linking requests for this
959+ module:
960+
961+ <!-- eslint-disable no-duplicate-imports -->
962+
963+ ```mjs
964+ import foo from ' foo' ;
965+ import source Foo from ' foo' ;
966+ ```
967+
968+ <!-- eslint-enable no-duplicate-imports -->
969+
970+ The `modules` array must contain two references to the same instance, because the two
971+ module requests are identical but in two phases.
972+
973+ If the module has no dependencies, the `modules` array can be empty.
974+
975+ Users can use `sourceTextModule.moduleRequests` to implement the host-defined
976+ [HostLoadImportedModule][] abstract operation in the ECMAScript specification,
977+ and using `sourceTextModule.linkRequests()` to invoke specification defined
978+ [FinishLoadingImportedModule][], on the module with all dependencies in a batch.
979+
980+ It' s up to the creator of the ` SourceTextModule` to determine if the resolution
981+ of the dependencies is synchronous or asynchronous.
982+
983+ After each module in the ` modules` array is linked, call
984+ [` sourceTextModule.instantiate()` ][].
985+
901986### ` sourceTextModule.moduleRequests`
902987
903988<!-- YAML
@@ -1017,14 +1102,17 @@ the module to access information outside the specified `context`. Use
10171102added:
10181103 - v13.0 .0
10191104 - v12.16 .0
1105+ changes:
1106+ - version: REPLACEME
1107+ pr- url: https: // github.com/nodejs/node/pull/59000
1108+ description: No longer need to call ` syntheticModule.link()` before
1109+ calling this method.
10201110-->
10211111
10221112* ` name` {string} Name of the export to set .
10231113* ` value` {any} The value to set the export to .
10241114
1025- This method is used after the module is linked to set the values of exports. If
1026- it is called before the module is linked, an [`ERR_VM_MODULE_STATUS`][] error
1027- will be thrown.
1115+ This method sets the module export binding slots with the given value .
10281116
10291117` ` ` mjs
10301118import vm from 'node:vm';
@@ -1033,7 +1121,6 @@ const m = new vm.SyntheticModule(['x'], () => {
10331121 m.setExport('x', 1);
10341122});
10351123
1036- await m.link(() => {});
10371124await m.evaluate();
10381125
10391126assert.strictEqual(m.namespace.x, 1);
@@ -1045,7 +1132,6 @@ const vm = require('node:vm');
10451132 const m = new vm.SyntheticModule(['x'], () => {
10461133 m.setExport('x', 1);
10471134 });
1048- await m.link(() => {});
10491135 await m.evaluate();
10501136 assert.strictEqual(m.namespace.x, 1);
10511137})();
@@ -2037,7 +2123,9 @@ const { Script, SyntheticModule } = require('node:vm');
20372123[Cyclic Module Record]: https://tc39.es/ecma262/#sec-cyclic-module-records
20382124[ECMAScript Module Loader]: esm.md#modules-ecmascript-modules
20392125[Evaluate() concrete method]: https://tc39.es/ecma262/#sec-moduleevaluation
2126+ [FinishLoadingImportedModule]: https://tc39.es/ecma262/#sec-FinishLoadingImportedModule
20402127[GetModuleNamespace]: https://tc39.es/ecma262/#sec-getmodulenamespace
2128+ [HostLoadImportedModule]: https://tc39.es/ecma262/#sec-HostLoadImportedModule
20412129[HostResolveImportedModule]: https://tc39.es/ecma262/#sec-hostresolveimportedmodule
20422130[ImportDeclaration]: https://tc39.es/ecma262/#prod-ImportDeclaration
20432131[Link() concrete method]: https://tc39.es/ecma262/#sec-moduledeclarationlinking
@@ -2049,13 +2137,14 @@ const { Script, SyntheticModule } = require('node:vm');
20492137[WithClause]: https://tc39.es/ecma262/#prod-WithClause
20502138[` ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG ` ]: errors.md#err_vm_dynamic_import_callback_missing_flag
20512139[` ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING ` ]: errors.md#err_vm_dynamic_import_callback_missing
2052- [` ERR_VM_MODULE_STATUS ` ]: errors.md#err_vm_module_status
20532140[` Error ` ]: errors.md#class-error
20542141[` URL ` ]: url.md#class-url
20552142[` eval ()` ]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
20562143[` optionsExpression` ]: https://tc39.es/proposal-import-attributes/#sec-evaluate-import-call
20572144[` script .runInContext ()` ]: #scriptrunincontextcontextifiedobject-options
20582145[` script .runInThisContext ()` ]: #scriptruninthiscontextoptions
2146+ [` sourceTextModule .instantiate ()` ]: #sourcetextmoduleinstantiate
2147+ [` sourceTextModule .linkRequests (modules)` ]: #sourcetextmodulelinkrequestsmodules
20592148[` sourceTextModule .moduleRequests ` ]: #sourcetextmodulemodulerequests
20602149[` url .origin ` ]: url.md#urlorigin
20612150[` vm .compileFunction ()` ]: #vmcompilefunctioncode-params-options
0 commit comments