src: add support for TLA

PR-URL: https://github.com/nodejs/node/pull/30370
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
This commit is contained in:
Gus Caplan
2019-11-11 22:30:02 -08:00
parent 241ed44a0b
commit 5ae5262f44
29 changed files with 243 additions and 124 deletions

View File

@@ -889,6 +889,11 @@ provided.
Encoding provided to `TextDecoder()` API was not one of the
[WHATWG Supported Encodings][].
<a id="ERR_EVAL_ESM_CANNOT_PRINT"></a>
### `ERR_EVAL_ESM_CANNOT_PRINT`
`--print` cannot be used with ESM input.
<a id="ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE"></a>
### `ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE`

View File

@@ -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
<!-- eslint-skip -->
```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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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');

View File

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

View File

@@ -375,7 +375,13 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& 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<Value>& args) {
@@ -387,13 +393,17 @@ void ModuleWrap::GetNamespace(const FunctionCallbackInfo<Value>& args) {
Local<Module> 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<Value> result = module->GetModuleNamespace();
@@ -616,19 +626,19 @@ MaybeLocal<Value> ModuleWrap::SyntheticModuleEvaluationStepsCallback(
TryCatchScope try_catch(env);
Local<Function> synthetic_evaluation_steps =
obj->synthetic_evaluation_steps_.Get(isolate);
obj->synthetic_evaluation_steps_.Reset();
MaybeLocal<Value> 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<Value>();
}
return ret;
return Undefined(isolate);
}
void ModuleWrap::SetSyntheticExport(const FunctionCallbackInfo<Value>& args) {

View File

@@ -138,10 +138,9 @@ void OptionsParser<Options>::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 <typename Options>
@@ -150,9 +149,8 @@ void OptionsParser<Options>::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 <typename Options>
@@ -196,9 +194,11 @@ template <typename ChildOptions>
auto OptionsParser<Options>::Convert(
typename OptionsParser<ChildOptions>::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<Options>::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<bool>(options) =
it->second.target_value;
if (it->second.type == kV8Option) {
v8_args->push_back(it->second.name);
} else {
*it->second.target_field->template Lookup<bool>(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) {

View File

@@ -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);
}

View File

@@ -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<std::string>* 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<BaseOptionField> target_field;
bool target_value;
};

View File

@@ -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']);
});

7
test/fixtures/es-modules/tla/a.mjs vendored Normal file
View File

@@ -0,0 +1,7 @@
import order from './order.mjs';
await new Promise((resolve) => {
setTimeout(resolve, 200);
});
order.push('a');

3
test/fixtures/es-modules/tla/b.mjs vendored Normal file
View File

@@ -0,0 +1,3 @@
import order from './order.mjs';
order.push('b');

3
test/fixtures/es-modules/tla/c.mjs vendored Normal file
View File

@@ -0,0 +1,3 @@
import order from './order.mjs';
order.push('c');

6
test/fixtures/es-modules/tla/d.mjs vendored Normal file
View File

@@ -0,0 +1,6 @@
import order from './order.mjs';
const end = Date.now() + 500;
while (end < Date.now()) {}
order.push('d');

View File

@@ -0,0 +1 @@
export default ['order'];

View File

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

View File

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

View File

@@ -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);
})();

View File

@@ -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 () => {

View File

@@ -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;
}),

View File

@@ -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());

View File

@@ -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');

View File

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

View File

@@ -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() {

View File

@@ -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;
}
{

View File

@@ -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(() => {});