diff --git a/doc/api/errors.md b/doc/api/errors.md index 82f59190bbb9e9..6353228f444c37 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -889,6 +889,11 @@ provided. Encoding provided to `TextDecoder()` API was not one of the [WHATWG Supported Encodings][]. + +### `ERR_EVAL_ESM_CANNOT_PRINT` + +`--print` cannot be used with ESM input. + ### `ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE` diff --git a/doc/api/esm.md b/doc/api/esm.md index 1c1317b6df7cb7..30534a5cdaa49b 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -36,8 +36,8 @@ initial input, or when referenced by `import` statements within ES module code: * Files ending in `.js` when the nearest parent `package.json` file contains a top-level field `"type"` with a value of `"module"`. -* Strings passed in as an argument to `--eval` or `--print`, or piped to - `node` via `STDIN`, with the flag `--input-type=module`. +* Strings passed in as an argument to `--eval`, or piped to `node` via `STDIN`, + with the flag `--input-type=module`. Node.js will treat as CommonJS all other forms of input, such as `.js` files where the nearest parent `package.json` file contains no top-level `"type"` @@ -52,8 +52,8 @@ or when referenced by `import` statements within ES module code: * Files ending in `.js` when the nearest parent `package.json` file contains a top-level field `"type"` with a value of `"commonjs"`. -* Strings passed in as an argument to `--eval` or `--print`, or piped to - `node` via `STDIN`, with the flag `--input-type=commonjs`. +* Strings passed in as an argument to `--eval` or `--print`, or piped to `node` + via `STDIN`, with the flag `--input-type=commonjs`. ### `package.json` `"type"` field @@ -159,9 +159,9 @@ package scope: ### `--input-type` flag -Strings passed in as an argument to `--eval` or `--print` (or `-e` or `-p`), or -piped to `node` via `STDIN`, will be treated as ES modules when the -`--input-type=module` flag is set. +Strings passed in as an argument to `--eval` (or `-e`), or piped to `node` via +`STDIN`, will be treated as ES modules when the `--input-type=module` flag is +set. ```sh node --input-type=module --eval "import { sep } from 'path'; console.log(sep);" @@ -1076,6 +1076,32 @@ node --experimental-wasm-modules index.mjs would provide the exports interface for the instantiation of `module.wasm`. +## Experimental Top-Level `await` + +When the `--experimental-top-level-await` flag is provided, `await` may be used +in the top level (outside of async functions) within modules. This implements +the [ECMAScript Top-Level `await` proposal][]. + +Assuming an `a.mjs` with + + +```js +export const five = await Promise.resolve(5); +``` + +And a `b.mjs` with + +```js +import { five } from './a.mjs'; + +console.log(five); // Logs `5` +``` + +```bash +node b.mjs # fails +node --experimental-top-level-await b.mjs # works +``` + ## Experimental Loaders **Note: This API is currently being redesigned and will still change.** @@ -1779,6 +1805,7 @@ success! [Conditional Exports]: #esm_conditional_exports [Dynamic `import()`]: https://wiki.developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports [ECMAScript-modules implementation]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md +[ECMAScript Top-Level `await` proposal]: https://github.com/tc39/proposal-top-level-await/ [ES Module Integration Proposal for Web Assembly]: https://github.com/webassembly/esm-integration [Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md [Terminology]: #esm_terminology diff --git a/doc/api/vm.md b/doc/api/vm.md index 56e779cfb87be6..a6cd7a08831ae7 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -402,7 +402,10 @@ support is planned. ```js const vm = require('vm'); -const contextifiedObject = vm.createContext({ secret: 42 }); +const contextifiedObject = vm.createContext({ + secret: 42, + print: console.log, +}); (async () => { // Step 1 @@ -418,6 +421,7 @@ const contextifiedObject = vm.createContext({ secret: 42 }); const bar = new vm.SourceTextModule(` import s from 'foo'; s; + print(s); `, { context: contextifiedObject }); // Step 2 @@ -460,16 +464,11 @@ const contextifiedObject = vm.createContext({ secret: 42 }); // Step 3 // - // Evaluate the Module. The evaluate() method returns a Promise with a single - // property "result" that contains the result of the very last statement - // executed in the Module. In the case of `bar`, it is `s;`, which refers to - // the default export of the `foo` module, the `secret` we set in the - // beginning to 42. + // Evaluate the Module. The evaluate() method returns a promise which will + // resolve after the module has finished evaluating. - const { result } = await bar.evaluate(); - - console.log(result); // Prints 42. + await bar.evaluate(); })(); ``` @@ -512,17 +511,14 @@ in the ECMAScript specification. Evaluate the module. -This must be called after the module has been linked; otherwise it will -throw an error. It could be called also when the module has already been -evaluated, in which case it will do one of the following two things: - -* return `undefined` if the initial evaluation ended in success (`module.status` - is `'evaluated'`) -* rethrow the same exception the initial evaluation threw if the initial - evaluation ended in an error (`module.status` is `'errored'`) +This must be called after the module has been linked; otherwise it will reject. +It could be called also when the module has already been evaluated, in which +case it will either do nothing if the initial evaluation ended in success +(`module.status` is `'evaluated'`) or it will re-throw the exception that the +initial evaluation resulted in (`module.status` is `'errored'`). This method cannot be called while the module is being evaluated -(`module.status` is `'evaluating'`) to prevent infinite recursion. +(`module.status` is `'evaluating'`). Corresponds to the [Evaluate() concrete method][] field of [Cyclic Module Record][]s in the ECMAScript specification. diff --git a/lib/internal/errors.js b/lib/internal/errors.js index c4e9fb3d59b4a3..b60643ba3b9e27 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -805,6 +805,7 @@ E('ERR_ENCODING_INVALID_ENCODED_DATA', function(encoding, ret) { }, TypeError); E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported', RangeError); +E('ERR_EVAL_ESM_CANNOT_PRINT', '--print cannot be used with ESM input', Error); E('ERR_FALSY_VALUE_REJECTION', function(reason) { this.reason = reason; return 'Promise was rejected with falsy value'; diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index be5868553fa8df..135c541813d49c 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -167,10 +167,9 @@ class Loader { }; const job = new ModuleJob(this, url, evalInstance, false, false); this.moduleMap.set(url, job); - const { module, result } = await job.run(); + const { module } = await job.run(); return { namespace: module.getNamespace(), - result }; } diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 3b79ac2df5ceef..b00a4fb2a58a7f 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -31,21 +31,26 @@ class ModuleJob { this.isMain = isMain; this.inspectBrk = inspectBrk; - // This is a Promise<{ module, reflect }>, whose fields will be copied - // onto `this` by `link()` below once it has been resolved. - this.modulePromise = moduleProvider.call(loader, url, isMain); this.module = undefined; + // Expose the promise to the ModuleWrap directly for linking below. + // `this.module` is also filled in below. + this.modulePromise = moduleProvider.call(loader, url, isMain); // Wait for the ModuleWrap instance being linked with all dependencies. const link = async () => { this.module = await this.modulePromise; assert(this.module instanceof ModuleWrap); + // Explicitly keeping track of dependency jobs is needed in order + // to flatten out the dependency graph below in `_instantiate()`, + // so that circular dependencies can't cause a deadlock by two of + // these `link` callbacks depending on each other. const dependencyJobs = []; const promises = this.module.link(async (specifier) => { const jobPromise = this.loader.getModuleJob(specifier, url); dependencyJobs.push(jobPromise); - return (await jobPromise).modulePromise; + const job = await jobPromise; + return job.modulePromise; }); if (promises !== undefined) @@ -59,25 +64,20 @@ class ModuleJob { // 'unhandled rejection' warnings. this.linked.catch(noop); - // instantiated == deep dependency jobs wrappers instantiated, - // module wrapper instantiated + // instantiated == deep dependency jobs wrappers are instantiated, + // and module wrapper is instantiated. this.instantiated = undefined; } - async instantiate() { - if (!this.instantiated) { - return this.instantiated = this._instantiate(); + instantiate() { + if (this.instantiated === undefined) { + this.instantiated = this._instantiate(); } - await this.instantiated; - return this.module; + return this.instantiated; } - // This method instantiates the module associated with this job and its - // entire dependency graph, i.e. creates all the module namespaces and the - // exported/imported variables. async _instantiate() { const jobsInGraph = new SafeSet(); - const addJobsToDependencyGraph = async (moduleJob) => { if (jobsInGraph.has(moduleJob)) { return; @@ -87,6 +87,7 @@ class ModuleJob { return PromiseAll(dependencyJobs.map(addJobsToDependencyGraph)); }; await addJobsToDependencyGraph(this); + try { if (!hasPausedEntry && this.inspectBrk) { hasPausedEntry = true; @@ -122,19 +123,20 @@ class ModuleJob { } throw e; } + for (const dependencyJob of jobsInGraph) { // Calling `this.module.instantiate()` instantiates not only the // ModuleWrap in this module, but all modules in the graph. dependencyJob.instantiated = resolvedPromise; } - return this.module; } async run() { - const module = await this.instantiate(); + await this.instantiate(); const timeout = -1; const breakOnSigint = false; - return { module, result: module.evaluate(timeout, breakOnSigint) }; + await this.module.evaluate(timeout, breakOnSigint); + return { module: this.module }; } } ObjectSetPrototypeOf(ModuleJob.prototype, null); diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index a6d6d2325a7f79..bbefcd47c74617 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -10,8 +10,9 @@ const path = require('path'); const { codes: { ERR_INVALID_ARG_TYPE, - ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET - } + ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET, + ERR_EVAL_ESM_CANNOT_PRINT, + }, } = require('internal/errors'); const { @@ -39,6 +40,9 @@ function tryGetCwd() { } function evalModule(source, print) { + if (print) { + throw new ERR_EVAL_ESM_CANNOT_PRINT(); + } const { log, error } = require('internal/console/global'); const { decorateErrorStack } = require('internal/util'); const asyncESM = require('internal/process/esm_loader'); diff --git a/lib/internal/vm/module.js b/lib/internal/vm/module.js index 992753ef680dbb..1d26e7a601b5bc 100644 --- a/lib/internal/vm/module.js +++ b/lib/internal/vm/module.js @@ -219,8 +219,7 @@ class Module { 'must be one of linked, evaluated, or errored' ); } - const result = this[kWrap].evaluate(timeout, breakOnSigint); - return { __proto__: null, result }; + await this[kWrap].evaluate(timeout, breakOnSigint); } [customInspectSymbol](depth, options) { diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 13dd331e6b23e1..59ef9daf727ed5 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -375,7 +375,13 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo& args) { return; } - args.GetReturnValue().Set(result.ToLocalChecked()); + // If TLA is enabled, `result` is the evaluation's promise. + // Otherwise, `result` is the last evaluated value of the module, + // which could be a promise, which would result in it being incorrectly + // unwrapped when the higher level code awaits the evaluation. + if (env->isolate_data()->options()->experimental_top_level_await) { + args.GetReturnValue().Set(result.ToLocalChecked()); + } } void ModuleWrap::GetNamespace(const FunctionCallbackInfo& args) { @@ -387,13 +393,17 @@ void ModuleWrap::GetNamespace(const FunctionCallbackInfo& args) { Local module = obj->module_.Get(isolate); switch (module->GetStatus()) { - default: + case v8::Module::Status::kUninstantiated: + case v8::Module::Status::kInstantiating: return env->ThrowError( - "cannot get namespace, Module has not been instantiated"); + "cannot get namespace, module has not been instantiated"); case v8::Module::Status::kInstantiated: case v8::Module::Status::kEvaluating: case v8::Module::Status::kEvaluated: + case v8::Module::Status::kErrored: break; + default: + UNREACHABLE(); } Local result = module->GetModuleNamespace(); @@ -616,19 +626,19 @@ MaybeLocal ModuleWrap::SyntheticModuleEvaluationStepsCallback( TryCatchScope try_catch(env); Local synthetic_evaluation_steps = obj->synthetic_evaluation_steps_.Get(isolate); + obj->synthetic_evaluation_steps_.Reset(); MaybeLocal ret = synthetic_evaluation_steps->Call(context, obj->object(), 0, nullptr); if (ret.IsEmpty()) { CHECK(try_catch.HasCaught()); } - obj->synthetic_evaluation_steps_.Reset(); if (try_catch.HasCaught() && !try_catch.HasTerminated()) { CHECK(!try_catch.Message().IsEmpty()); CHECK(!try_catch.Exception().IsEmpty()); try_catch.ReThrow(); return MaybeLocal(); } - return ret; + return Undefined(isolate); } void ModuleWrap::SetSyntheticExport(const FunctionCallbackInfo& args) { diff --git a/src/node_options-inl.h b/src/node_options-inl.h index 682a1c6c47d020..4e1a12296bc77e 100644 --- a/src/node_options-inl.h +++ b/src/node_options-inl.h @@ -138,10 +138,9 @@ void OptionsParser::Implies(const char* from, const char* to) { auto it = options_.find(to); CHECK_NE(it, options_.end()); - CHECK_EQ(it->second.type, kBoolean); - implications_.emplace(from, Implication { - it->second.field, true - }); + CHECK(it->second.type == kBoolean || it->second.type == kV8Option); + implications_.emplace( + from, Implication{it->second.type, to, it->second.field, true}); } template @@ -150,9 +149,8 @@ void OptionsParser::ImpliesNot(const char* from, auto it = options_.find(to); CHECK_NE(it, options_.end()); CHECK_EQ(it->second.type, kBoolean); - implications_.emplace(from, Implication { - it->second.field, false - }); + implications_.emplace( + from, Implication{it->second.type, to, it->second.field, false}); } template @@ -196,9 +194,11 @@ template auto OptionsParser::Convert( typename OptionsParser::Implication original, ChildOptions* (Options::* get_child)()) { - return Implication { - Convert(original.target_field, get_child), - original.target_value + return Implication{ + original.type, + original.name, + Convert(original.target_field, get_child), + original.target_value, }; } @@ -366,19 +366,23 @@ void OptionsParser::Parse( break; } - if (it == options_.end()) { - v8_args->push_back(arg); - continue; - } - { auto implications = implications_.equal_range(name); for (auto it = implications.first; it != implications.second; ++it) { - *it->second.target_field->template Lookup(options) = - it->second.target_value; + if (it->second.type == kV8Option) { + v8_args->push_back(it->second.name); + } else { + *it->second.target_field->template Lookup(options) = + it->second.target_value; + } } } + if (it == options_.end()) { + v8_args->push_back(arg); + continue; + } + const OptionInfo& info = it->second; std::string value; if (info.type != kBoolean && info.type != kNoOp && info.type != kV8Option) { diff --git a/src/node_options.cc b/src/node_options.cc index 3b9142c19e98a8..b3e7f403b05854 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -593,6 +593,13 @@ PerIsolateOptionsParser::PerIsolateOptionsParser( kAllowedInEnvironment); Implies("--report-signal", "--report-on-signal"); + AddOption("--experimental-top-level-await", + "enable experimental support for ECMAScript Top-Level Await", + &PerIsolateOptions::experimental_top_level_await); + AddOption("--harmony-top-level-await", "", V8Option{}); + Implies("--experimental-top-level-await", "--harmony-top-level-await"); + Implies("--harmony-top-level-await", "--experimental-top-level-await"); + Insert(eop, &PerIsolateOptions::get_per_env_options); } diff --git a/src/node_options.h b/src/node_options.h index 539e41e67ac6ee..bbe5617cdc832d 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -186,6 +186,7 @@ class PerIsolateOptions : public Options { bool no_node_snapshot = false; bool report_uncaught_exception = false; bool report_on_signal = false; + bool experimental_top_level_await = false; std::string report_signal = "SIGUSR2"; inline EnvironmentOptions* get_per_env_options(); void CheckOptions(std::vector* errors) override; @@ -418,6 +419,8 @@ class OptionsParser { // An implied option is composed of the information on where to store a // specific boolean value (if another specific option is encountered). struct Implication { + OptionType type; + std::string name; std::shared_ptr target_field; bool target_value; }; diff --git a/test/es-module/test-esm-tla.mjs b/test/es-module/test-esm-tla.mjs new file mode 100644 index 00000000000000..816f88dd80a74e --- /dev/null +++ b/test/es-module/test-esm-tla.mjs @@ -0,0 +1,11 @@ +// Flags: --experimental-top-level-await + +import '../common/index.mjs'; +import fixtures from '../common/fixtures.js'; +import assert from 'assert'; +import { pathToFileURL } from 'url'; + +import(pathToFileURL(fixtures.path('/es-modules/tla/parent.mjs'))) + .then(({ default: order }) => { + assert.deepStrictEqual(order, ['order', 'b', 'c', 'd', 'a', 'parent']); + }); diff --git a/test/fixtures/es-modules/tla/a.mjs b/test/fixtures/es-modules/tla/a.mjs new file mode 100644 index 00000000000000..c899a1c658c385 --- /dev/null +++ b/test/fixtures/es-modules/tla/a.mjs @@ -0,0 +1,7 @@ +import order from './order.mjs'; + +await new Promise((resolve) => { + setTimeout(resolve, 200); +}); + +order.push('a'); diff --git a/test/fixtures/es-modules/tla/b.mjs b/test/fixtures/es-modules/tla/b.mjs new file mode 100644 index 00000000000000..5149f5fda9361e --- /dev/null +++ b/test/fixtures/es-modules/tla/b.mjs @@ -0,0 +1,3 @@ +import order from './order.mjs'; + +order.push('b'); diff --git a/test/fixtures/es-modules/tla/c.mjs b/test/fixtures/es-modules/tla/c.mjs new file mode 100644 index 00000000000000..ab0479f12b8eac --- /dev/null +++ b/test/fixtures/es-modules/tla/c.mjs @@ -0,0 +1,3 @@ +import order from './order.mjs'; + +order.push('c'); diff --git a/test/fixtures/es-modules/tla/d.mjs b/test/fixtures/es-modules/tla/d.mjs new file mode 100644 index 00000000000000..4d6e7d496fc88a --- /dev/null +++ b/test/fixtures/es-modules/tla/d.mjs @@ -0,0 +1,6 @@ +import order from './order.mjs'; + +const end = Date.now() + 500; +while (end < Date.now()) {} + +order.push('d'); diff --git a/test/fixtures/es-modules/tla/order.mjs b/test/fixtures/es-modules/tla/order.mjs new file mode 100644 index 00000000000000..3258bf986eae36 --- /dev/null +++ b/test/fixtures/es-modules/tla/order.mjs @@ -0,0 +1 @@ +export default ['order']; diff --git a/test/fixtures/es-modules/tla/parent.mjs b/test/fixtures/es-modules/tla/parent.mjs new file mode 100644 index 00000000000000..ed17e102267696 --- /dev/null +++ b/test/fixtures/es-modules/tla/parent.mjs @@ -0,0 +1,9 @@ +import order from './order.mjs'; +import './a.mjs'; +import './b.mjs'; +import './c.mjs'; +import './d.mjs'; + +order.push('parent'); + +export default order; diff --git a/test/parallel/test-cli-eval.js b/test/parallel/test-cli-eval.js index 65d31642055627..cbe0a09887f8eb 100644 --- a/test/parallel/test-cli-eval.js +++ b/test/parallel/test-cli-eval.js @@ -245,9 +245,10 @@ child.exec( // Assert that "42\n" is written to stdout with print option. child.exec( `${nodejs} ${execOptions} --print --eval "42"`, - common.mustCall((err, stdout) => { - assert.ifError(err); - assert.strictEqual(stdout, '42\n'); + common.mustCall((err, stdout, stderr) => { + assert.ok(err); + assert.strictEqual(stdout, ''); + assert.ok(stderr.includes('--print cannot be used with ESM input')); })); // Assert that error is written to stderr on invalid input. diff --git a/test/parallel/test-internal-module-wrap.js b/test/parallel/test-internal-module-wrap.js index b0cc3a1cf02d8a..09cccba76de004 100644 --- a/test/parallel/test-internal-module-wrap.js +++ b/test/parallel/test-internal-module-wrap.js @@ -10,7 +10,7 @@ const { ModuleWrap } = internalBinding('module_wrap'); const { getPromiseDetails, isPromise } = internalBinding('util'); const setTimeoutAsync = require('util').promisify(setTimeout); -const foo = new ModuleWrap('foo', undefined, 'export * from "bar"; 6;', 0, 0); +const foo = new ModuleWrap('foo', undefined, 'export * from "bar";', 0, 0); const bar = new ModuleWrap('bar', undefined, 'export const five = 5', 0, 0); (async () => { @@ -24,6 +24,6 @@ const bar = new ModuleWrap('bar', undefined, 'export const five = 5', 0, 0); foo.instantiate(); - assert.strictEqual(await foo.evaluate(-1, false), 6); + assert.strictEqual(await foo.evaluate(-1, false), undefined); assert.strictEqual(foo.getNamespace().five, 5); })(); diff --git a/test/parallel/test-vm-module-basic.js b/test/parallel/test-vm-module-basic.js index 26751fd0b58e31..c842ac01fb8d93 100644 --- a/test/parallel/test-vm-module-basic.js +++ b/test/parallel/test-vm-module-basic.js @@ -26,26 +26,26 @@ const util = require('util'); assert.strictEqual(m.status, 'unlinked'); await m.link(common.mustNotCall()); assert.strictEqual(m.status, 'linked'); - const result = await m.evaluate(); + assert.strictEqual(await m.evaluate(), undefined); assert.strictEqual(m.status, 'evaluated'); - assert.strictEqual(Object.getPrototypeOf(result), null); assert.deepStrictEqual(context, { foo: 'bar', baz: 'bar', typeofProcess: 'undefined' }); - assert.strictEqual(result.result, 'function'); }()); (async () => { - const m = new SourceTextModule( - 'global.vmResult = "foo"; Object.prototype.toString.call(process);' - ); + const m = new SourceTextModule(` + global.vmResultFoo = "foo"; + global.vmResultTypeofProcess = Object.prototype.toString.call(process); + `); await m.link(common.mustNotCall()); - const { result } = await m.evaluate(); - assert.strictEqual(global.vmResult, 'foo'); - assert.strictEqual(result, '[object process]'); - delete global.vmResult; + await m.evaluate(); + assert.strictEqual(global.vmResultFoo, 'foo'); + assert.strictEqual(global.vmResultTypeofProcess, '[object process]'); + delete global.vmResultFoo; + delete global.vmResultTypeofProcess; })(); (async () => { diff --git a/test/parallel/test-vm-module-dynamic-import.js b/test/parallel/test-vm-module-dynamic-import.js index 70229b3897874b..9d04cf96e6f979 100644 --- a/test/parallel/test-vm-module-dynamic-import.js +++ b/test/parallel/test-vm-module-dynamic-import.js @@ -5,19 +5,23 @@ const common = require('../common'); const assert = require('assert'); -const { Script, SourceTextModule, createContext } = require('vm'); +const { Script, SourceTextModule } = require('vm'); async function testNoCallback() { - const m = new SourceTextModule('import("foo")', { context: createContext() }); + const m = new SourceTextModule(` + globalThis.importResult = import("foo"); + globalThis.importResult.catch(() => {}); + `); await m.link(common.mustNotCall()); - const { result } = await m.evaluate(); + await m.evaluate(); let threw = false; try { - await result; + await globalThis.importResult; } catch (err) { threw = true; assert.strictEqual(err.code, 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING'); } + delete globalThis.importResult; assert(threw); } @@ -40,7 +44,7 @@ async function test() { } { - const m = new SourceTextModule('import("foo")', { + const m = new SourceTextModule('globalThis.fooResult = import("foo")', { importModuleDynamically: common.mustCall((specifier, wrap) => { assert.strictEqual(specifier, 'foo'); assert.strictEqual(wrap, m); @@ -48,24 +52,26 @@ async function test() { }), }); await m.link(common.mustNotCall()); - const { result } = await m.evaluate(); - assert.strictEqual(foo.namespace, await result); + await m.evaluate(); + assert.strictEqual(foo.namespace, await globalThis.fooResult); + delete globalThis.fooResult; } } async function testInvalid() { - const m = new SourceTextModule('import("foo")', { + const m = new SourceTextModule('globalThis.fooResult = import("foo")', { importModuleDynamically: common.mustCall((specifier, wrap) => { return 5; }), }); await m.link(common.mustNotCall()); - const { result } = await m.evaluate(); - await result.catch(common.mustCall((e) => { + await m.evaluate(); + await globalThis.fooResult.catch(common.mustCall((e) => { assert.strictEqual(e.code, 'ERR_VM_MODULE_NOT_MODULE'); })); + delete globalThis.fooResult; - const s = new Script('import("foo")', { + const s = new Script('import("bar")', { importModuleDynamically: common.mustCall((specifier, wrap) => { return undefined; }), diff --git a/test/parallel/test-vm-module-dynamic-namespace.js b/test/parallel/test-vm-module-dynamic-namespace.js index 987b21f2ec1151..84937cd78de4af 100644 --- a/test/parallel/test-vm-module-dynamic-namespace.js +++ b/test/parallel/test-vm-module-dynamic-namespace.js @@ -9,22 +9,18 @@ const assert = require('assert'); const { types } = require('util'); const { SourceTextModule } = require('vm'); -async function getNamespace() { - const m = new SourceTextModule(''); - await m.link(() => 0); - await m.evaluate(); - return m.namespace; -} - (async () => { - const namespace = await getNamespace(); - const m = new SourceTextModule('export const A = "A"; import("");', { - importModuleDynamically: common.mustCall((specifier, wrap) => { - return namespace; - }) + const m = new SourceTextModule('globalThis.importResult = import("");', { + importModuleDynamically: common.mustCall(async (specifier, wrap) => { + const m = new SourceTextModule(''); + await m.link(() => 0); + await m.evaluate(); + return m.namespace; + }), }); await m.link(() => 0); - const { result } = await m.evaluate(); - const ns = await result; + await m.evaluate(); + const ns = await globalThis.importResult; + delete globalThis.importResult; assert.ok(types.isModuleNamespaceObject(ns)); })().then(common.mustCall()); diff --git a/test/parallel/test-vm-module-errors.js b/test/parallel/test-vm-module-errors.js index d14296b1e9ebde..fee243f101113b 100644 --- a/test/parallel/test-vm-module-errors.js +++ b/test/parallel/test-vm-module-errors.js @@ -182,13 +182,11 @@ async function checkExecution() { await (async () => { const m = new SourceTextModule('throw new Error();'); await m.link(common.mustNotCall()); - const evaluatePromise = m.evaluate(); - await evaluatePromise.catch(() => {}); - assert.strictEqual(m.status, 'errored'); try { - await evaluatePromise; + await m.evaluate(); } catch (err) { assert.strictEqual(m.error, err); + assert.strictEqual(m.status, 'errored'); return; } assert.fail('Missing expected exception'); diff --git a/test/parallel/test-vm-module-import-meta.js b/test/parallel/test-vm-module-import-meta.js index 0f86f2fa646914..baf6c5b37d1b3b 100644 --- a/test/parallel/test-vm-module-import-meta.js +++ b/test/parallel/test-vm-module-import-meta.js @@ -7,14 +7,16 @@ const assert = require('assert'); const { SourceTextModule } = require('vm'); async function testBasic() { - const m = new SourceTextModule('import.meta;', { + const m = new SourceTextModule('globalThis.importMeta = import.meta;', { initializeImportMeta: common.mustCall((meta, module) => { assert.strictEqual(module, m); meta.prop = 42; }) }); await m.link(common.mustNotCall()); - const { result } = await m.evaluate(); + await m.evaluate(); + const result = globalThis.importMeta; + delete globalThis.importMeta; assert.strictEqual(typeof result, 'object'); assert.strictEqual(Object.getPrototypeOf(result), null); assert.strictEqual(result.prop, 42); diff --git a/test/parallel/test-vm-module-link.js b/test/parallel/test-vm-module-link.js index 56443e3e1a73bc..48f06439b6dacf 100644 --- a/test/parallel/test-vm-module-link.js +++ b/test/parallel/test-vm-module-link.js @@ -13,7 +13,8 @@ async function simple() { const foo = new SourceTextModule('export default 5;'); await foo.link(common.mustNotCall()); - const bar = new SourceTextModule('import five from "foo"; five'); + globalThis.fiveResult = undefined; + const bar = new SourceTextModule('import five from "foo"; fiveResult = five'); assert.deepStrictEqual(bar.dependencySpecifiers, ['foo']); @@ -23,7 +24,9 @@ async function simple() { return foo; })); - assert.strictEqual((await bar.evaluate()).result, 5); + await bar.evaluate(); + assert.strictEqual(globalThis.fiveResult, 5); + delete globalThis.fiveResult; } async function depth() { diff --git a/test/parallel/test-vm-module-reevaluate.js b/test/parallel/test-vm-module-reevaluate.js index 4767541dd24c37..6208f140ffab88 100644 --- a/test/parallel/test-vm-module-reevaluate.js +++ b/test/parallel/test-vm-module-reevaluate.js @@ -12,11 +12,16 @@ const finished = common.mustCall(); (async function main() { { - const m = new SourceTextModule('1'); + globalThis.count = 0; + const m = new SourceTextModule('count += 1;'); await m.link(common.mustNotCall()); - assert.strictEqual((await m.evaluate()).result, 1); - assert.strictEqual((await m.evaluate()).result, undefined); - assert.strictEqual((await m.evaluate()).result, undefined); + assert.strictEqual(await m.evaluate(), undefined); + assert.strictEqual(globalThis.count, 1); + assert.strictEqual(await m.evaluate(), undefined); + assert.strictEqual(globalThis.count, 1); + assert.strictEqual(await m.evaluate(), undefined); + assert.strictEqual(globalThis.count, 1); + delete globalThis.count; } { diff --git a/test/parallel/test-vm-module-synthetic.js b/test/parallel/test-vm-module-synthetic.js index 83a78f2f3f5ba2..a3ca6630f7eab4 100644 --- a/test/parallel/test-vm-module-synthetic.js +++ b/test/parallel/test-vm-module-synthetic.js @@ -2,7 +2,7 @@ // Flags: --experimental-vm-modules -require('../common'); +const common = require('../common'); const { SyntheticModule, SourceTextModule } = require('vm'); const assert = require('assert'); @@ -26,6 +26,17 @@ const assert = require('assert'); assert.strictEqual(m.namespace.getX(), 42); } + { + const s = new SyntheticModule([], () => { + const p = Promise.reject(); + p.catch(() => {}); + return p; + }); + + await s.link(common.mustNotCall()); + assert.strictEqual(await s.evaluate(), undefined); + } + for (const invalidName of [1, Symbol.iterator, {}, [], null, true, 0]) { const s = new SyntheticModule([], () => {}); await s.link(() => {});