@@ -417,9 +417,7 @@ class that closely mirrors [Module Record][]s as defined in the ECMAScript
417417specification.
418418
419419Unlike ` vm.Script ` however, every ` vm.Module ` object is bound to a context from
420- its creation. Operations on ` vm.Module ` objects are intrinsically asynchronous,
421- in contrast with the synchronous nature of ` vm.Script ` objects. The use of
422- 'async' functions can help with manipulating ` vm.Module ` objects.
420+ its creation.
423421
424422Using a ` vm.Module ` object requires three distinct steps: creation/parsing,
425423linking, and evaluation. These three steps are illustrated in the following
@@ -447,7 +445,7 @@ const contextifiedObject = vm.createContext({
447445// Here, we attempt to obtain the default export from the module "foo", and
448446// put it into local binding "secret".
449447
450- const bar = new vm.SourceTextModule (`
448+ const rootModule = new vm.SourceTextModule (`
451449 import s from 'foo';
452450 s;
453451 print(s);
@@ -457,47 +455,56 @@ const bar = new vm.SourceTextModule(`
457455//
458456// "Link" the imported dependencies of this Module to it.
459457//
460- // The provided linking callback (the "linker") accepts two arguments: the
461- // parent module (`bar` in this case) and the string that is the specifier of
462- // the imported module. The callback is expected to return a Module that
463- // corresponds to the provided specifier, with certain requirements documented
464- // in `module.link()`.
465- //
466- // If linking has not started for the returned Module, the same linker
467- // callback will be called on the returned Module.
458+ // Obtain the requested dependencies of a SourceTextModule by
459+ // `sourceTextModule.moduleRequests` and resolve them.
468460//
469461// Even top-level Modules without dependencies must be explicitly linked. The
470- // callback provided would never be called, however.
471- //
472- // The link() method returns a Promise that will be resolved when all the
473- // Promises returned by the linker resolve.
462+ // array passed to `sourceTextModule.linkRequests(modules)` can be
463+ // empty, however.
474464//
475- // Note: This is a contrived example in that the linker function creates a new
476- // "foo" module every time it is called. In a full-fledged module system, a
477- // cache would probably be used to avoid duplicated modules.
478-
479- async function linker (specifier , referencingModule ) {
480- if (specifier === ' foo' ) {
481- return new vm.SourceTextModule (`
482- // The "secret" variable refers to the global variable we added to
483- // "contextifiedObject" when creating the context.
484- export default secret;
485- ` , { context: referencingModule .context });
486-
487- // Using `contextifiedObject` instead of `referencingModule.context`
488- // here would work as well.
489- }
490- throw new Error (` Unable to resolve dependency: ${ specifier} ` );
465+ // Note: This is a contrived example in that the resolveAndLinkDependencies
466+ // creates a new "foo" module every time it is called. In a full-fledged
467+ // module system, a cache would probably be used to avoid duplicated modules.
468+
469+ const moduleMap = new Map ([
470+ [' root' , rootModule],
471+ ]);
472+
473+ function resolveAndLinkDependencies (module ) {
474+ const requestedModules = module .moduleRequests .map ((request ) => {
475+ // In a full-fledged module system, the resolveAndLinkDependencies would
476+ // resolve the module with the module cache key `[specifier, attributes]`.
477+ // In this example, we just use the specifier as the key.
478+ const specifier = request .specifier ;
479+
480+ let requestedModule = moduleMap .get (specifier);
481+ if (requestedModule === undefined ) {
482+ requestedModule = new vm.SourceTextModule (`
483+ // The "secret" variable refers to the global variable we added to
484+ // "contextifiedObject" when creating the context.
485+ export default secret;
486+ ` , { context: referencingModule .context });
487+ moduleMap .set (specifier, linkedModule);
488+ // Resolve the dependencies of the new module as well.
489+ resolveAndLinkDependencies (requestedModule);
490+ }
491+
492+ return requestedModule;
493+ });
494+
495+ module .linkRequests (requestedModules);
491496}
492- await bar .link (linker);
497+
498+ resolveAndLinkDependencies (rootModule);
499+ rootModule .instantiate ();
493500
494501// Step 3
495502//
496503// Evaluate the Module. The evaluate() method returns a promise which will
497504// resolve after the module has finished evaluating.
498505
499506// Prints 42.
500- await bar .evaluate ();
507+ await rootModule .evaluate ();
501508```
502509
503510``` cjs
@@ -519,7 +526,7 @@ const contextifiedObject = vm.createContext({
519526 // Here, we attempt to obtain the default export from the module "foo", and
520527 // put it into local binding "secret".
521528
522- const bar = new vm.SourceTextModule (`
529+ const rootModule = new vm.SourceTextModule (`
523530 import s from 'foo';
524531 s;
525532 print(s);
@@ -529,47 +536,56 @@ const contextifiedObject = vm.createContext({
529536 //
530537 // "Link" the imported dependencies of this Module to it.
531538 //
532- // The provided linking callback (the "linker") accepts two arguments: the
533- // parent module (`bar` in this case) and the string that is the specifier of
534- // the imported module. The callback is expected to return a Module that
535- // corresponds to the provided specifier, with certain requirements documented
536- // in `module.link()`.
537- //
538- // If linking has not started for the returned Module, the same linker
539- // callback will be called on the returned Module.
539+ // Obtain the requested dependencies of a SourceTextModule by
540+ // `sourceTextModule.moduleRequests` and resolve them.
540541 //
541542 // Even top-level Modules without dependencies must be explicitly linked. The
542- // callback provided would never be called, however.
543- //
544- // The link() method returns a Promise that will be resolved when all the
545- // Promises returned by the linker resolve.
543+ // array passed to `sourceTextModule.linkRequests(modules)` can be
544+ // empty, however.
546545 //
547- // Note: This is a contrived example in that the linker function creates a new
548- // "foo" module every time it is called. In a full-fledged module system, a
549- // cache would probably be used to avoid duplicated modules.
550-
551- async function linker (specifier , referencingModule ) {
552- if (specifier === ' foo' ) {
553- return new vm.SourceTextModule (`
554- // The "secret" variable refers to the global variable we added to
555- // "contextifiedObject" when creating the context.
556- export default secret;
557- ` , { context: referencingModule .context });
546+ // Note: This is a contrived example in that the resolveAndLinkDependencies
547+ // creates a new "foo" module every time it is called. In a full-fledged
548+ // module system, a cache would probably be used to avoid duplicated modules.
549+
550+ const moduleMap = new Map ([
551+ [' root' , rootModule],
552+ ]);
553+
554+ function resolveAndLinkDependencies (module ) {
555+ const requestedModules = module .moduleRequests .map ((request ) => {
556+ // In a full-fledged module system, the resolveAndLinkDependencies would
557+ // resolve the module with the module cache key `[specifier, attributes]`.
558+ // In this example, we just use the specifier as the key.
559+ const specifier = request .specifier ;
560+
561+ let requestedModule = moduleMap .get (specifier);
562+ if (requestedModule === undefined ) {
563+ requestedModule = new vm.SourceTextModule (`
564+ // The "secret" variable refers to the global variable we added to
565+ // "contextifiedObject" when creating the context.
566+ export default secret;
567+ ` , { context: referencingModule .context });
568+ moduleMap .set (specifier, linkedModule);
569+ // Resolve the dependencies of the new module as well.
570+ resolveAndLinkDependencies (requestedModule);
571+ }
572+
573+ return requestedModule;
574+ });
558575
559- // Using `contextifiedObject` instead of `referencingModule.context`
560- // here would work as well.
561- }
562- throw new Error (` Unable to resolve dependency: ${ specifier} ` );
576+ module .linkRequests (requestedModules);
563577 }
564- await bar .link (linker);
578+
579+ resolveAndLinkDependencies (rootModule);
580+ rootModule .instantiate ();
565581
566582 // Step 3
567583 //
568584 // Evaluate the Module. The evaluate() method returns a promise which will
569585 // resolve after the module has finished evaluating.
570586
571587 // Prints 42.
572- await bar .evaluate ();
588+ await rootModule .evaluate ();
573589})();
574590```
575591
@@ -658,6 +674,10 @@ changes:
658674Link module dependencies . This method must be called before evaluation, and
659675can only be called once per module .
660676
677+ Use [` sourceTextModule.linkRequests(modules)` ][] and
678+ [` sourceTextModule.instantiate()` ][] to link modules either synchronously or
679+ asynchronously.
680+
661681The function is expected to return a `Module` object or a `Promise` that
662682eventually resolves to a `Module` object. The returned `Module` must satisfy the
663683following two invariants:
@@ -803,8 +823,9 @@ const module = new vm.SourceTextModule(
803823 meta.prop = {};
804824 },
805825 });
806- // Since module has no dependencies, the linker function will never be called.
807- await module.link(() => {});
826+ // The module has an empty `moduleRequests` array.
827+ module.linkRequests([]);
828+ module.instantiate();
808829await module.evaluate();
809830
810831// Now, Object.prototype.secret will be equal to 42.
@@ -830,8 +851,9 @@ const contextifiedObject = vm.createContext({ secret: 42 });
830851 meta.prop = {};
831852 },
832853 });
833- // Since module has no dependencies, the linker function will never be called.
834- await module.link(() => {});
854+ // The module has an empty `moduleRequests` array.
855+ module.linkRequests([]);
856+ module.instantiate();
835857 await module.evaluate();
836858 // Now, Object.prototype.secret will be equal to 42.
837859 //
@@ -896,6 +918,69 @@ to disallow any changes to it.
896918Corresponds to the `[[RequestedModules]]` field of [Cyclic Module Record][]s in
897919the ECMAScript specification.
898920
921+ ### `sourceTextModule.instantiate()`
922+
923+ <!-- YAML
924+ added: REPLACEME
925+ -->
926+
927+ * Returns: {undefined}
928+
929+ Instantiate the module with the linked requested modules.
930+
931+ This resolves the imported bindings of the module, including re-exported
932+ binding names. When there are any bindings that cannot be resolved,
933+ an error would be thrown synchronously.
934+
935+ If the requested modules include cyclic dependencies, the
936+ [`sourceTextModule.linkRequests(modules)`][] method must be called on all
937+ modules in the cycle before calling this method.
938+
939+ ### `sourceTextModule.linkRequests(modules)`
940+
941+ <!-- YAML
942+ added: REPLACEME
943+ -->
944+
945+ * `modules` {vm.Module\[ ]} Array of `vm.Module` objects that this module depends on.
946+ The order of the modules in the array is the order of
947+ [`sourceTextModule.moduleRequests`][].
948+ * Returns: {undefined}
949+
950+ Link module dependencies. This method must be called before evaluation, and
951+ can only be called once per module.
952+
953+ The order of the module instances in the `modules` array should correspond to the order of
954+ [`sourceTextModule.moduleRequests`][] being resolved. If two module requests have the same
955+ specifier and import attributes, they must be resolved with the same module instance or an
956+ `ERR_MODULE_LINK_MISMATCH` would be thrown. For example, when linking requests for this
957+ module:
958+
959+ <!-- eslint-disable no-duplicate-imports -->
960+
961+ ```mjs
962+ import foo from ' foo' ;
963+ import source Foo from ' foo' ;
964+ ```
965+
966+ <!-- eslint-enable no-duplicate-imports -->
967+
968+ The `modules` array must contain two references to the same instance, because the two
969+ module requests are identical but in two phases.
970+
971+ If the module has no dependencies, the `modules` array can be empty.
972+
973+ Users can use `sourceTextModule.moduleRequests` to implement the host-defined
974+ [HostLoadImportedModule][] abstract operation in the ECMAScript specification,
975+ and using `sourceTextModule.linkRequests()` to invoke specification defined
976+ [FinishLoadingImportedModule][], on the module with all dependencies in a batch.
977+
978+ It' s up to the creator of the ` SourceTextModule` to determine if the resolution
979+ of the dependencies is synchronous or asynchronous.
980+
981+ After each module in the ` modules` array is linked, call
982+ [` sourceTextModule.instantiate()` ][].
983+
899984### ` sourceTextModule.moduleRequests`
900985
901986<!-- YAML
@@ -1005,14 +1090,17 @@ the module to access information outside the specified `context`. Use
10051090added:
10061091 - v13.0 .0
10071092 - v12.16 .0
1093+ changes:
1094+ - version: REPLACEME
1095+ pr- url: https: // github.com/nodejs/node/pull/59000
1096+ description: No longer need to call ` syntheticModule.link()` before
1097+ calling this method.
10081098-->
10091099
10101100* ` name` {string} Name of the export to set .
10111101* ` value` {any} The value to set the export to .
10121102
1013- This method is used after the module is linked to set the values of exports. If
1014- it is called before the module is linked, an [`ERR_VM_MODULE_STATUS`][] error
1015- will be thrown.
1103+ This method sets the module export binding slots with the given value .
10161104
10171105` ` ` mjs
10181106import vm from 'node:vm';
@@ -1021,7 +1109,6 @@ const m = new vm.SyntheticModule(['x'], () => {
10211109 m.setExport('x', 1);
10221110});
10231111
1024- await m.link(() => {});
10251112await m.evaluate();
10261113
10271114assert.strictEqual(m.namespace.x, 1);
@@ -1033,7 +1120,6 @@ const vm = require('node:vm');
10331120 const m = new vm.SyntheticModule(['x'], () => {
10341121 m.setExport('x', 1);
10351122 });
1036- await m.link(() => {});
10371123 await m.evaluate();
10381124 assert.strictEqual(m.namespace.x, 1);
10391125})();
@@ -2083,7 +2169,9 @@ const { Script, SyntheticModule } = require('node:vm');
20832169[Cyclic Module Record]: https://tc39.es/ecma262/#sec-cyclic-module-records
20842170[ECMAScript Module Loader]: esm.md#modules-ecmascript-modules
20852171[Evaluate() concrete method]: https://tc39.es/ecma262/#sec-moduleevaluation
2172+ [FinishLoadingImportedModule]: https://tc39.es/ecma262/#sec-FinishLoadingImportedModule
20862173[GetModuleNamespace]: https://tc39.es/ecma262/#sec-getmodulenamespace
2174+ [HostLoadImportedModule]: https://tc39.es/ecma262/#sec-HostLoadImportedModule
20872175[HostResolveImportedModule]: https://tc39.es/ecma262/#sec-hostresolveimportedmodule
20882176[ImportDeclaration]: https://tc39.es/ecma262/#prod-ImportDeclaration
20892177[Link() concrete method]: https://tc39.es/ecma262/#sec-moduledeclarationlinking
@@ -2095,13 +2183,14 @@ const { Script, SyntheticModule } = require('node:vm');
20952183[WithClause]: https://tc39.es/ecma262/#prod-WithClause
20962184[` ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG ` ]: errors.md#err_vm_dynamic_import_callback_missing_flag
20972185[` ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING ` ]: errors.md#err_vm_dynamic_import_callback_missing
2098- [` ERR_VM_MODULE_STATUS ` ]: errors.md#err_vm_module_status
20992186[` Error ` ]: errors.md#class-error
21002187[` URL ` ]: url.md#class-url
21012188[` eval ()` ]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
21022189[` optionsExpression` ]: https://tc39.es/proposal-import-attributes/#sec-evaluate-import-call
21032190[` script .runInContext ()` ]: #scriptrunincontextcontextifiedobject-options
21042191[` script .runInThisContext ()` ]: #scriptruninthiscontextoptions
2192+ [` sourceTextModule .instantiate ()` ]: #sourcetextmoduleinstantiate
2193+ [` sourceTextModule .linkRequests (modules)` ]: #sourcetextmodulelinkrequestsmodules
21052194[` sourceTextModule .moduleRequests ` ]: #sourcetextmodulemodulerequests
21062195[` url .origin ` ]: url.md#urlorigin
21072196[` vm .compileFunction ()` ]: #vmcompilefunctioncode-params-options
0 commit comments