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