@@ -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.linkRequestedModules(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 .linkRequestedModules (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.linkRequestedModules(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 .linkRequestedModules (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
@@ -635,6 +650,9 @@ changes:
635650 former name is still provided for backward compatibility.
636651-->
637652
653+ > Stability: 0 - Deprecated: Use [ ` sourceTextModule.linkRequestedModules(modules) ` ] [ ] and
654+ > [ ` sourceTextModule.instantiate() ` ] [ ] instead.
655+
638656* ` linker ` {Function}
639657 * ` specifier ` {string} The specifier of the requested module:
640658 ``` mjs
@@ -898,6 +916,53 @@ to disallow any changes to it.
898916Corresponds to the `[[RequestedModules]]` field of [Cyclic Module Record][]s in
899917the ECMAScript specification.
900918
919+ ### `sourceTextModule.instantiate()`
920+
921+ <!-- YAML
922+ added: REPLACEME
923+ -->
924+
925+ * Returns: {void}
926+
927+ Instantiate the module with the linked requested modules.
928+
929+ This resolves the imported bindings of the module, including re-exported
930+ binding names.
931+
932+ If the requested modules include cyclic dependencies, the
933+ [`sourceTextModule.linkRequestedModules(modules)`][] method must be called on all
934+ modules in the cycle before calling this method.
935+
936+ ### `sourceTextModule.linkRequestedModules(modules)`
937+
938+ <!-- YAML
939+ added: REPLACEME
940+ -->
941+
942+ * `modules` {vm.Module\[ ]} Array of `vm.Module` objects that this module depends on.
943+ The order of the modules in the array is the order of
944+ [`sourceTextModule.moduleRequests`][].
945+ * Returns: {void}
946+
947+ Link module dependencies. This method must be called before evaluation, and
948+ can only be called once per module.
949+
950+ The order of the `modules` array should respect the order of
951+ [`sourceTextModule.moduleRequests`][].
952+
953+ If the module has no dependencies, the `modules` array can be empty, or skip this
954+ method call.
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/XXXXX
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 .linkRequestedModules (modules)` ]: #sourcetextmodulelinkrequestedmodulesmodules
20592128[` sourceTextModule .moduleRequests ` ]: #sourcetextmodulemodulerequests
20602129[` url .origin ` ]: url.md#urlorigin
20612130[` vm .compileFunction ()` ]: #vmcompilefunctioncode-params-options
0 commit comments