@@ -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.linkRequestedModules(modules)` can be
465
+ // empty, however.
476
466
//
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
478
468
// "foo" module every time it is called. In a full-fledged module system, a
479
469
// cache would probably be used to avoid duplicated modules.
480
470
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
+ ]);
488
474
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);
493
498
}
494
- await bar .link (linker);
499
+
500
+ linker (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,55 @@ 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.linkRequestedModules(modules)` can be
546
+ // empty, however.
548
547
//
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
550
549
// "foo" module every time it is called. In a full-fledged module system, a
551
550
// cache would probably be used to avoid duplicated modules.
552
551
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
+ });
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 .linkRequestedModules (requestedModules);
565
579
}
566
- await bar .link (linker);
580
+
581
+ linker (rootModule);
567
582
568
583
// Step 3
569
584
//
570
585
// Evaluate the Module. The evaluate() method returns a promise which will
571
586
// resolve after the module has finished evaluating.
572
587
573
588
// Prints 42.
574
- await bar .evaluate ();
589
+ await rootModule .evaluate ();
575
590
})();
576
591
```
577
592
@@ -635,6 +650,9 @@ changes:
635
650
former name is still provided for backward compatibility.
636
651
-->
637
652
653
+ > Stability: 0 - Deprecated: Use [ ` sourceTextModule.linkRequestedModules(modules) ` ] [ ] and
654
+ > [ ` sourceTextModule.instantiate() ` ] [ ] instead.
655
+
638
656
* ` linker ` {Function}
639
657
* ` specifier ` {string} The specifier of the requested module:
640
658
``` mjs
@@ -898,6 +916,53 @@ to disallow any changes to it.
898
916
Corresponds to the `[[RequestedModules]]` field of [Cyclic Module Record][]s in
899
917
the ECMAScript specification.
900
918
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
+
901
966
### ` sourceTextModule.moduleRequests`
902
967
903
968
<!-- YAML
@@ -1017,14 +1082,17 @@ the module to access information outside the specified `context`. Use
1017
1082
added:
1018
1083
- v13.0 .0
1019
1084
- 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.
1020
1090
-->
1021
1091
1022
1092
* ` name` {string} Name of the export to set .
1023
1093
* ` value` {any} The value to set the export to .
1024
1094
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 .
1028
1096
1029
1097
` ` ` mjs
1030
1098
import vm from 'node:vm';
@@ -1033,7 +1101,6 @@ const m = new vm.SyntheticModule(['x'], () => {
1033
1101
m.setExport('x', 1);
1034
1102
});
1035
1103
1036
- await m.link(() => {});
1037
1104
await m.evaluate();
1038
1105
1039
1106
assert.strictEqual(m.namespace.x, 1);
@@ -1045,7 +1112,6 @@ const vm = require('node:vm');
1045
1112
const m = new vm.SyntheticModule(['x'], () => {
1046
1113
m.setExport('x', 1);
1047
1114
});
1048
- await m.link(() => {});
1049
1115
await m.evaluate();
1050
1116
assert.strictEqual(m.namespace.x, 1);
1051
1117
})();
@@ -2037,7 +2103,9 @@ const { Script, SyntheticModule } = require('node:vm');
2037
2103
[Cyclic Module Record]: https://tc39.es/ecma262/#sec-cyclic-module-records
2038
2104
[ECMAScript Module Loader]: esm.md#modules-ecmascript-modules
2039
2105
[Evaluate() concrete method]: https://tc39.es/ecma262/#sec-moduleevaluation
2106
+ [FinishLoadingImportedModule]: https://tc39.es/ecma262/#sec-FinishLoadingImportedModule
2040
2107
[GetModuleNamespace]: https://tc39.es/ecma262/#sec-getmodulenamespace
2108
+ [HostLoadImportedModule]: https://tc39.es/ecma262/#sec-HostLoadImportedModule
2041
2109
[HostResolveImportedModule]: https://tc39.es/ecma262/#sec-hostresolveimportedmodule
2042
2110
[ImportDeclaration]: https://tc39.es/ecma262/#prod-ImportDeclaration
2043
2111
[Link() concrete method]: https://tc39.es/ecma262/#sec-moduledeclarationlinking
@@ -2049,13 +2117,14 @@ const { Script, SyntheticModule } = require('node:vm');
2049
2117
[WithClause]: https://tc39.es/ecma262/#prod-WithClause
2050
2118
[` ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG ` ]: errors.md#err_vm_dynamic_import_callback_missing_flag
2051
2119
[` 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
2120
[` Error ` ]: errors.md#class-error
2054
2121
[` URL ` ]: url.md#class-url
2055
2122
[` eval ()` ]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
2056
2123
[` optionsExpression` ]: https://tc39.es/proposal-import-attributes/#sec-evaluate-import-call
2057
2124
[` script .runInContext ()` ]: #scriptrunincontextcontextifiedobject-options
2058
2125
[` script .runInThisContext ()` ]: #scriptruninthiscontextoptions
2126
+ [` sourceTextModule .instantiate ()` ]: #sourcetextmoduleinstantiate
2127
+ [` sourceTextModule .linkRequestedModules (modules)` ]: #sourcetextmodulelinkrequestedmodulesmodules
2059
2128
[` sourceTextModule .moduleRequests ` ]: #sourcetextmodulemodulerequests
2060
2129
[` url .origin ` ]: url.md#urlorigin
2061
2130
[` vm .compileFunction ()` ]: #vmcompilefunctioncode-params-options
0 commit comments