@@ -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
467+ // Note: This is a contrived example in that the linker creates a new
478468// "foo" module every time it is called. In a full-fledged module system, a
479469// cache would probably be used to avoid duplicated modules.
480470
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 });
471+ const moduleMap = new Map ([
472+ [' root' , rootModule],
473+ ]);
488474
489- // Using `contextifiedObject` instead of `referencingModule.context`
490- // here would work as well.
491- }
492- throw new Error (` Unable to resolve dependency: ${ specifier} ` );
475+ function linker (module ) {
476+ const requestedModules = module .moduleRequests .map ((request ) => {
477+ // In a full-fledged module system, the linker would resolve the
478+ // 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+ linker (requestedModule);
492+ }
493+
494+ return requestedModule;
495+ });
496+
497+ module .linkRequests (requestedModules);
493498}
494- await bar .link (linker);
499+
500+ linker (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,55 @@ 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
548+ // Note: This is a contrived example in that the linker creates a new
550549 // "foo" module every time it is called. In a full-fledged module system, a
551550 // cache would probably be used to avoid duplicated modules.
552551
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 });
552+ const moduleMap = new Map ([
553+ [' root' , rootModule],
554+ ]);
555+
556+ function linker (module ) {
557+ const requestedModules = module .moduleRequests .map ((request ) => {
558+ // In a full-fledged module system, the linker would resolve the
559+ // 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+ linker (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+ linker (rootModule);
567582
568583 // Step 3
569584 //
570585 // Evaluate the Module. The evaluate() method returns a promise which will
571586 // resolve after the module has finished evaluating.
572587
573588 // Prints 42.
574- await bar .evaluate ();
589+ await rootModule .evaluate ();
575590})();
576591```
577592
@@ -660,6 +675,10 @@ changes:
660675Link module dependencies . This method must be called before evaluation, and
661676can only be called once per module .
662677
678+ Use [` sourceTextModule.linkRequests(modules)` ][] and
679+ [` sourceTextModule.instantiate()` ][] to link modules either synchronously or
680+ asynchronously.
681+
663682The function is expected to return a `Module` object or a `Promise` that
664683eventually resolves to a `Module` object. The returned `Module` must satisfy the
665684following two invariants:
@@ -898,6 +917,52 @@ to disallow any changes to it.
898917Corresponds to the `[[RequestedModules]]` field of [Cyclic Module Record][]s in
899918the ECMAScript specification.
900919
920+ ### `sourceTextModule.instantiate()`
921+
922+ <!-- YAML
923+ added: REPLACEME
924+ -->
925+
926+ * Returns: {undefined}
927+
928+ Instantiate the module with the linked requested modules.
929+
930+ This resolves the imported bindings of the module, including re-exported
931+ binding names.
932+
933+ If the requested modules include cyclic dependencies, the
934+ [`sourceTextModule.linkRequests(modules)`][] method must be called on all
935+ modules in the cycle before calling this method.
936+
937+ ### `sourceTextModule.linkRequests(modules)`
938+
939+ <!-- YAML
940+ added: REPLACEME
941+ -->
942+
943+ * `modules` {vm.Module\[ ]} Array of `vm.Module` objects that this module depends on.
944+ The order of the modules in the array is the order of
945+ [`sourceTextModule.moduleRequests`][].
946+ * Returns: {undefined}
947+
948+ Link module dependencies. This method must be called before evaluation, and
949+ can only be called once per module.
950+
951+ The order of the module instances in the `modules` array should correspond to the order of
952+ [`sourceTextModule.moduleRequests`][] being resolved.
953+
954+ If the module has no dependencies, the `modules` array can be empty.
955+
956+ Composing `sourceTextModule.moduleRequests` and `sourceTextModule.link()`,
957+ this acts similar to [HostLoadImportedModule][] and [FinishLoadingImportedModule][]
958+ abstract operations in the ECMAScript specification, respectively.
959+
960+ It' s up to the creator of the ` SourceTextModule` to determine if the resolution
961+ of the dependencies is synchronous or asynchronous.
962+
963+ After each module in the ` modules` array is linked, call
964+ [` sourceTextModule.instantiate()` ][].
965+
901966### ` sourceTextModule.moduleRequests`
902967
903968<!-- YAML
@@ -1017,14 +1082,17 @@ the module to access information outside the specified `context`. Use
10171082added:
10181083 - v13.0 .0
10191084 - v12.16 .0
1085+ changes:
1086+ - version: REPLACEME
1087+ pr- url: https: // github.com/nodejs/node/pull/59000
1088+ description: No longer need to call ` syntheticModule.link()` before
1089+ calling this method.
10201090-->
10211091
10221092* ` name` {string} Name of the export to set .
10231093* ` value` {any} The value to set the export to .
10241094
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.
1095+ This method sets the module export binding slots with the given value .
10281096
10291097` ` ` mjs
10301098import vm from 'node:vm';
@@ -1033,7 +1101,6 @@ const m = new vm.SyntheticModule(['x'], () => {
10331101 m.setExport('x', 1);
10341102});
10351103
1036- await m.link(() => {});
10371104await m.evaluate();
10381105
10391106assert.strictEqual(m.namespace.x, 1);
@@ -1045,7 +1112,6 @@ const vm = require('node:vm');
10451112 const m = new vm.SyntheticModule(['x'], () => {
10461113 m.setExport('x', 1);
10471114 });
1048- await m.link(() => {});
10491115 await m.evaluate();
10501116 assert.strictEqual(m.namespace.x, 1);
10511117})();
@@ -2037,7 +2103,9 @@ const { Script, SyntheticModule } = require('node:vm');
20372103[Cyclic Module Record]: https://tc39.es/ecma262/#sec-cyclic-module-records
20382104[ECMAScript Module Loader]: esm.md#modules-ecmascript-modules
20392105[Evaluate() concrete method]: https://tc39.es/ecma262/#sec-moduleevaluation
2106+ [FinishLoadingImportedModule]: https://tc39.es/ecma262/#sec-FinishLoadingImportedModule
20402107[GetModuleNamespace]: https://tc39.es/ecma262/#sec-getmodulenamespace
2108+ [HostLoadImportedModule]: https://tc39.es/ecma262/#sec-HostLoadImportedModule
20412109[HostResolveImportedModule]: https://tc39.es/ecma262/#sec-hostresolveimportedmodule
20422110[ImportDeclaration]: https://tc39.es/ecma262/#prod-ImportDeclaration
20432111[Link() concrete method]: https://tc39.es/ecma262/#sec-moduledeclarationlinking
@@ -2049,13 +2117,14 @@ const { Script, SyntheticModule } = require('node:vm');
20492117[WithClause]: https://tc39.es/ecma262/#prod-WithClause
20502118[` ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG ` ]: errors.md#err_vm_dynamic_import_callback_missing_flag
20512119[` 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
20532120[` Error ` ]: errors.md#class-error
20542121[` URL ` ]: url.md#class-url
20552122[` eval ()` ]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
20562123[` optionsExpression` ]: https://tc39.es/proposal-import-attributes/#sec-evaluate-import-call
20572124[` script .runInContext ()` ]: #scriptrunincontextcontextifiedobject-options
20582125[` script .runInThisContext ()` ]: #scriptruninthiscontextoptions
2126+ [` sourceTextModule .instantiate ()` ]: #sourcetextmoduleinstantiate
2127+ [` sourceTextModule .linkRequests (modules)` ]: #sourcetextmodulelinkrequestsmodules
20592128[` sourceTextModule .moduleRequests ` ]: #sourcetextmodulemodulerequests
20602129[` url .origin ` ]: url.md#urlorigin
20612130[` vm .compileFunction ()` ]: #vmcompilefunctioncode-params-options
0 commit comments