Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/internal/bootstrap/realm.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,10 @@ const {
setInternalLoaders,
} = internalBinding('builtins');

const { ModuleWrap } = internalBinding('module_wrap');
const {
ModuleWrap,
kEvaluationPhase,
} = internalBinding('module_wrap');
ObjectSetPrototypeOf(ModuleWrap.prototype, null);

const getOwn = (target, property, receiver) => {
Expand Down Expand Up @@ -402,7 +405,7 @@ class BuiltinModule {
});
// Ensure immediate sync execution to capture exports now
this.module.instantiate();
this.module.evaluate(-1, false);
this.module.evaluate(-1, false, kEvaluationPhase);
return this.module;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/internal/modules/esm/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ class ModuleLoader {
return module.getModuleSourceObject();
}
const { module } = await moduleJob.run(isEntryPoint);
return module.getNamespace();
return module.getNamespace(phase);
}, {
__proto__: null,
parentURL,
Expand Down
21 changes: 11 additions & 10 deletions lib/internal/modules/esm/module_job.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ class ModuleJobBase {
debug(`ModuleJobBase.syncLink() ${this.url} -> ${request.specifier}`, job);
assert(!isPromise(job));
assert(job.module instanceof ModuleWrap);
if (this.shouldRunModule(request.phase)) {
if (this.shouldEvaluateModule(request.phase)) {
ArrayPrototypePush(evaluationDepJobs, job);
}
modules[idx] = job.module;
Expand Down Expand Up @@ -383,7 +383,7 @@ class ModuleJobBase {
shouldLinkModule(phase) {
return phase >= kDeferPhase;
}
shouldRunModule(phase) {
shouldEvaluateModule(phase) {
return phase === kEvaluationPhase;
}
}
Expand Down Expand Up @@ -466,7 +466,7 @@ class ModuleJob extends ModuleJobBase {
const dependencyJobPromise = this.loader.getOrCreateModuleJob(this.url, request, requestType);
const modulePromise = PromisePrototypeThen(dependencyJobPromise, (job) => {
debug(`ModuleJob.asyncLink() ${this.url} -> ${request.specifier}`, job);
if (this.shouldRunModule(request.phase)) {
if (this.shouldEvaluateModule(request.phase)) {
ArrayPrototypePush(evaluationDepJobs, job);
}
return job.modulePromise;
Expand Down Expand Up @@ -521,7 +521,7 @@ class ModuleJob extends ModuleJobBase {
}

runSync(parent) {
assert(this.shouldRunModule(this.phase));
assert(this.shouldEvaluateModule(this.phase));
assert(this.module instanceof ModuleWrap);
let status = this.module.getStatus();

Expand Down Expand Up @@ -561,17 +561,17 @@ class ModuleJob extends ModuleJobBase {
}

async run(isEntryPoint = false) {
debug('ModuleJob.run()', this.module);
assert(this.shouldRunModule(this.phase));
debug('ModuleJob.run(); phase: ', this.phase, this.module);
await this.#instantiate();
if (isEntryPoint) {
globalThis[entry_point_module_private_symbol] = this.module;
}

const timeout = -1;
const breakOnSigint = false;
setHasStartedUserESMExecution();
try {
await this.module.evaluate(timeout, breakOnSigint);
await this.module.evaluate(timeout, breakOnSigint, this.phase);
} catch (e) {
explainCommonJSGlobalLikeNotDefinedError(e, this.module.url, this.module.hasTopLevelAwait);
throw e;
Expand Down Expand Up @@ -641,7 +641,8 @@ class ModuleJobSync extends ModuleJobBase {
}

async run(isEntryPoint = false) {
assert(this.shouldRunModule(this.phase));
assert(this.shouldEvaluateModule(this.phase));
// This path is hit by a require'd module that is imported again.
const status = this.module.getStatus();
debug('ModuleJobSync.run()', status, this.module);
// If the module was previously required and errored, reject from import() again.
Expand Down Expand Up @@ -676,7 +677,7 @@ class ModuleJobSync extends ModuleJobBase {
const { callAndPauseOnStart } = internalBinding('inspector');
this.evaluationPromise = callAndPauseOnStart(this.module.evaluate, this.module, timeout, breakOnSigint);
} else {
this.evaluationPromise = this.module.evaluate(timeout, breakOnSigint);
this.evaluationPromise = this.module.evaluate(timeout, breakOnSigint, this.phase);
}
await this.evaluationPromise;
this.evaluationPromise = undefined;
Expand Down Expand Up @@ -704,7 +705,7 @@ class ModuleJobSync extends ModuleJobBase {

runSync(parent) {
debug('ModuleJobSync.runSync()', this.module);
assert(this.shouldRunModule(this.phase));
assert(this.shouldEvaluateModule(this.phase));
// TODO(joyeecheung): Reject graphs with top-level await _before_ instantiation, so that the
// async graph error supersedes instantiation (mismatch export) errors in the graph.
this.instantiateSync();
Expand Down
3 changes: 2 additions & 1 deletion lib/internal/modules/esm/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const {

const {
ModuleWrap,
kEvaluationPhase,
setImportModuleDynamicallyCallback,
setInitializeImportMetaObjectCallback,
} = internalBinding('module_wrap');
Expand Down Expand Up @@ -383,7 +384,7 @@ function compileSourceTextModule(url, source, type, context = kEmptyObject) {
const modules = requests.map(({ specifier }) => getBuiltinModuleWrapForEmbedder(specifier));
wrap.link(modules);
wrap.instantiate();
wrap.evaluate(-1, false);
wrap.evaluate(-1, false, kEvaluationPhase);
}
return wrap;
}
Expand Down
35 changes: 31 additions & 4 deletions src/module_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,17 @@ ModulePhase to_phase_constant(ModuleImportPhase phase) {
}
}

ModuleImportPhase to_module_import_phase(ModulePhase phase) {
switch (phase) {
case kEvaluationPhase:
return ModuleImportPhase::kEvaluation;
case kDeferPhase:
return ModuleImportPhase::kDefer;
default:
UNREACHABLE();
}
}

static Local<Object> createImportAttributesContainer(
Realm* realm,
Isolate* isolate,
Expand Down Expand Up @@ -774,8 +785,8 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
if (contextify_context != nullptr)
microtask_queue = contextify_context->microtask_queue();

// module.evaluate(timeout, breakOnSigint)
CHECK_EQ(args.Length(), 2);
// module.evaluate(timeout, breakOnSigint, phase)
CHECK_EQ(args.Length(), 3);

CHECK(args[0]->IsNumber());
int64_t timeout;
Expand All @@ -786,6 +797,9 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
CHECK(args[1]->IsBoolean());
bool break_on_sigint = args[1]->IsTrue();

CHECK(args[2]->IsNumber());
ModulePhase phase = static_cast<ModulePhase>(args[2].As<Int32>()->Value());

ShouldNotAbortOnUncaughtScope no_abort_scope(realm->env());
TryCatchScope try_catch(realm->env());

Expand All @@ -800,7 +814,13 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
isolate, &received_signal)
: std::nullopt;

result = module->Evaluate(context);
CHECK_NE(phase, kSourcePhase);
if (phase == kDeferPhase) {
result = module->EvaluateForImportDefer(context);
} else {
CHECK_EQ(phase, kEvaluationPhase);
result = module->Evaluate(context);
}

Local<Value> res;
if (result.ToLocal(&res) && microtask_queue) {
Expand Down Expand Up @@ -954,12 +974,19 @@ void ModuleWrap::GetNamespace(const FunctionCallbackInfo<Value>& args) {
ModuleWrap* obj;
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());

ModulePhase phase = kEvaluationPhase;
if (args.Length() > 0) {
CHECK(args[0]->IsInt32());
phase = static_cast<ModulePhase>(args[0].As<Int32>()->Value());
}

Local<Module> module = obj->module_.Get(isolate);
if (module->GetStatus() < Module::kInstantiated) {
return THROW_ERR_MODULE_NOT_INSTANTIATED(realm->env());
}

Local<Value> result = module->GetModuleNamespace();
Local<Value> result =
module->GetModuleNamespace(to_module_import_phase(phase));
args.GetReturnValue().Set(result);
}

Expand Down
22 changes: 22 additions & 0 deletions test/es-module/test-defer-dynamic-import.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Flags: --js-defer-import-eval

// Tests that defer import of a dynamically imported module
// defers the execution of the module until any of its properties
// is accessed for the first time.

import * as common from '../common/index.mjs';
import * as assert from 'assert';

globalThis.eval_list ||= [];

console.log('main module loading...');

await import.defer('../fixtures/es-modules/module-deferred-eval.mjs').then((module) => {
// Check that the module has been evaluated exactly once.
assert.strictEqual(globalThis.eval_list.length, 1);
assert.strictEqual(module.foo, 42);
assert.partialDeepStrictEqual(['defer-1'], globalThis.eval_list);

// Clean-up
delete globalThis.eval_list;
}).then(common.mustCall());
9 changes: 6 additions & 3 deletions test/parallel/test-internal-module-wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ const common = require('../common');
const assert = require('assert');

const { internalBinding } = require('internal/test/binding');
const { ModuleWrap } = internalBinding('module_wrap');
const {
ModuleWrap,
kEvaluationPhase,
} = internalBinding('module_wrap');

async function testModuleWrap() {
const unlinked = new ModuleWrap('unlinked', undefined, 'export * from "bar";', 0, 0);
Expand Down Expand Up @@ -43,7 +46,7 @@ async function testModuleWrap() {
assert.strictEqual(bar.hasAsyncGraph, false);
assert.strictEqual(foo.hasAsyncGraph, false);

assert.strictEqual(await foo.evaluate(-1, false), undefined);
assert.strictEqual(await foo.evaluate(-1, false, kEvaluationPhase), undefined);
assert.strictEqual(foo.getNamespace().five, 5);

// Check that the module requests are the same after linking, instantiate, and evaluation.
Expand All @@ -68,7 +71,7 @@ async function testAsyncGraph() {
assert.strictEqual(bar.hasAsyncGraph, true);
assert.strictEqual(foo.hasAsyncGraph, true);

const evalPromise = foo.evaluate(-1, false);
const evalPromise = foo.evaluate(-1, false, kEvaluationPhase);
assert.throws(() => foo.getNamespace().five, ReferenceError);
assert.strictEqual(await evalPromise, undefined);
assert.strictEqual(foo.getNamespace().five, 5);
Expand Down
Loading