@@ -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