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