@@ -419,9 +419,7 @@ class that closely mirrors [Module Record][]s as defined in the ECMAScript
419
419
specification.
420
420
421
421
Unlike ` 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.
425
423
426
424
Using a ` vm.Module ` object requires three distinct steps: creation/parsing,
427
425
linking, and evaluation. These three steps are illustrated in the following
@@ -449,7 +447,7 @@ const contextifiedObject = vm.createContext({
449
447
// Here, we attempt to obtain the default export from the module "foo", and
450
448
// put it into local binding "secret".
451
449
452
- const bar = new vm.SourceTextModule (`
450
+ const rootModule = new vm.SourceTextModule (`
453
451
import s from 'foo';
454
452
s;
455
453
print(s);
@@ -459,47 +457,56 @@ const bar = new vm.SourceTextModule(`
459
457
//
460
458
// "Link" the imported dependencies of this Module to it.
461
459
//
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.
470
462
//
471
463
// 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.
476
466
//
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);
493
498
}
494
- await bar .link (linker);
499
+
500
+ resolveAndLinkDependencies (rootModule);
501
+ rootModule .instantiate ();
495
502
496
503
// Step 3
497
504
//
498
505
// Evaluate the Module. The evaluate() method returns a promise which will
499
506
// resolve after the module has finished evaluating.
500
507
501
508
// Prints 42.
502
- await bar .evaluate ();
509
+ await rootModule .evaluate ();
503
510
```
504
511
505
512
``` cjs
@@ -521,7 +528,7 @@ const contextifiedObject = vm.createContext({
521
528
// Here, we attempt to obtain the default export from the module "foo", and
522
529
// put it into local binding "secret".
523
530
524
- const bar = new vm.SourceTextModule (`
531
+ const rootModule = new vm.SourceTextModule (`
525
532
import s from 'foo';
526
533
s;
527
534
print(s);
@@ -531,47 +538,56 @@ const contextifiedObject = vm.createContext({
531
538
//
532
539
// "Link" the imported dependencies of this Module to it.
533
540
//
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.
542
543
//
543
544
// 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.
548
547
//
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
+ });
560
577
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);
565
579
}
566
- await bar .link (linker);
580
+
581
+ resolveAndLinkDependencies (rootModule);
582
+ rootModule .instantiate ();
567
583
568
584
// Step 3
569
585
//
570
586
// Evaluate the Module. The evaluate() method returns a promise which will
571
587
// resolve after the module has finished evaluating.
572
588
573
589
// Prints 42.
574
- await bar .evaluate ();
590
+ await rootModule .evaluate ();
575
591
})();
576
592
```
577
593
@@ -660,6 +676,10 @@ changes:
660
676
Link module dependencies . This method must be called before evaluation, and
661
677
can only be called once per module .
662
678
679
+ Use [` sourceTextModule.linkRequests(modules)` ][] and
680
+ [` sourceTextModule.instantiate()` ][] to link modules either synchronously or
681
+ asynchronously.
682
+
663
683
The function is expected to return a `Module` object or a `Promise` that
664
684
eventually resolves to a `Module` object. The returned `Module` must satisfy the
665
685
following two invariants:
@@ -805,8 +825,9 @@ const module = new vm.SourceTextModule(
805
825
meta.prop = {};
806
826
},
807
827
});
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();
810
831
await module.evaluate();
811
832
812
833
// Now, Object.prototype.secret will be equal to 42.
@@ -832,8 +853,9 @@ const contextifiedObject = vm.createContext({ secret: 42 });
832
853
meta.prop = {};
833
854
},
834
855
});
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();
837
859
await module.evaluate();
838
860
// Now, Object.prototype.secret will be equal to 42.
839
861
//
@@ -898,6 +920,69 @@ to disallow any changes to it.
898
920
Corresponds to the `[[RequestedModules]]` field of [Cyclic Module Record][]s in
899
921
the ECMAScript specification.
900
922
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
+
901
986
### ` sourceTextModule.moduleRequests`
902
987
903
988
<!-- YAML
@@ -1017,14 +1102,17 @@ the module to access information outside the specified `context`. Use
1017
1102
added:
1018
1103
- v13.0 .0
1019
1104
- 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.
1020
1110
-->
1021
1111
1022
1112
* ` name` {string} Name of the export to set .
1023
1113
* ` value` {any} The value to set the export to .
1024
1114
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 .
1028
1116
1029
1117
` ` ` mjs
1030
1118
import vm from 'node:vm';
@@ -1033,7 +1121,6 @@ const m = new vm.SyntheticModule(['x'], () => {
1033
1121
m.setExport('x', 1);
1034
1122
});
1035
1123
1036
- await m.link(() => {});
1037
1124
await m.evaluate();
1038
1125
1039
1126
assert.strictEqual(m.namespace.x, 1);
@@ -1045,7 +1132,6 @@ const vm = require('node:vm');
1045
1132
const m = new vm.SyntheticModule(['x'], () => {
1046
1133
m.setExport('x', 1);
1047
1134
});
1048
- await m.link(() => {});
1049
1135
await m.evaluate();
1050
1136
assert.strictEqual(m.namespace.x, 1);
1051
1137
})();
@@ -2037,7 +2123,9 @@ const { Script, SyntheticModule } = require('node:vm');
2037
2123
[Cyclic Module Record]: https://tc39.es/ecma262/#sec-cyclic-module-records
2038
2124
[ECMAScript Module Loader]: esm.md#modules-ecmascript-modules
2039
2125
[Evaluate() concrete method]: https://tc39.es/ecma262/#sec-moduleevaluation
2126
+ [FinishLoadingImportedModule]: https://tc39.es/ecma262/#sec-FinishLoadingImportedModule
2040
2127
[GetModuleNamespace]: https://tc39.es/ecma262/#sec-getmodulenamespace
2128
+ [HostLoadImportedModule]: https://tc39.es/ecma262/#sec-HostLoadImportedModule
2041
2129
[HostResolveImportedModule]: https://tc39.es/ecma262/#sec-hostresolveimportedmodule
2042
2130
[ImportDeclaration]: https://tc39.es/ecma262/#prod-ImportDeclaration
2043
2131
[Link() concrete method]: https://tc39.es/ecma262/#sec-moduledeclarationlinking
@@ -2049,13 +2137,14 @@ const { Script, SyntheticModule } = require('node:vm');
2049
2137
[WithClause]: https://tc39.es/ecma262/#prod-WithClause
2050
2138
[` ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG ` ]: errors.md#err_vm_dynamic_import_callback_missing_flag
2051
2139
[` 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
2053
2140
[` Error ` ]: errors.md#class-error
2054
2141
[` URL ` ]: url.md#class-url
2055
2142
[` eval ()` ]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
2056
2143
[` optionsExpression` ]: https://tc39.es/proposal-import-attributes/#sec-evaluate-import-call
2057
2144
[` script .runInContext ()` ]: #scriptrunincontextcontextifiedobject-options
2058
2145
[` script .runInThisContext ()` ]: #scriptruninthiscontextoptions
2146
+ [` sourceTextModule .instantiate ()` ]: #sourcetextmoduleinstantiate
2147
+ [` sourceTextModule .linkRequests (modules)` ]: #sourcetextmodulelinkrequestsmodules
2059
2148
[` sourceTextModule .moduleRequests ` ]: #sourcetextmodulemodulerequests
2060
2149
[` url .origin ` ]: url.md#urlorigin
2061
2150
[` vm .compileFunction ()` ]: #vmcompilefunctioncode-params-options
0 commit comments