Skip to content

module: handle instantiated async module jobs in require(esm) #58067

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2025
Merged
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
2 changes: 1 addition & 1 deletion lib/internal/modules/esm/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ class ModuleLoader {
throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename);
}
const status = job.module.getStatus();
debug('Module status', filename, status);
debug('Module status', job, status);
if (status === kEvaluated) {
return { wrap: job.module, namespace: job.module.getNamespaceSync(filename, parentFilename) };
} else if (status === kInstantiated) {
Expand Down
44 changes: 34 additions & 10 deletions lib/internal/modules/esm/module_job.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
debug = fn;
});

const { ModuleWrap, kInstantiated, kEvaluationPhase } = internalBinding('module_wrap');
const {
ModuleWrap,
kErrored,
kEvaluated,
kEvaluationPhase,
kInstantiated,
kUninstantiated,
} = internalBinding('module_wrap');
const {
privateSymbols: {
entry_point_module_private_symbol,
Expand Down Expand Up @@ -277,17 +284,34 @@ class ModuleJob extends ModuleJobBase {
runSync(parent) {
assert(this.phase === kEvaluationPhase);
assert(this.module instanceof ModuleWrap);
if (this.instantiated !== undefined) {
return { __proto__: null, module: this.module };
let status = this.module.getStatus();

debug('ModuleJob.runSync', this.module);
// FIXME(joyeecheung): this cannot fully handle < kInstantiated. Make the linking
// fully synchronous instead.
if (status === kUninstantiated) {
this.module.async = this.module.instantiateSync();
status = this.module.getStatus();
}
if (status === kInstantiated || status === kErrored) {
const filename = urlToFilename(this.url);
const parentFilename = urlToFilename(parent?.filename);
this.module.async ??= this.module.isGraphAsync();

this.module.instantiate();
this.instantiated = PromiseResolve();
setHasStartedUserESMExecution();
const filename = urlToFilename(this.url);
const parentFilename = urlToFilename(parent?.filename);
const namespace = this.module.evaluateSync(filename, parentFilename);
return { __proto__: null, module: this.module, namespace };
if (this.module.async && !getOptionValue('--experimental-print-required-tla')) {
throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename);
}
if (status === kInstantiated) {
setHasStartedUserESMExecution();
const namespace = this.module.evaluateSync(filename, parentFilename);
return { __proto__: null, module: this.module, namespace };
}
throw this.module.getError();

} else if (status === kEvaluated) {
return { __proto__: null, module: this.module, namespace: this.module.getNamespaceSync() };
}
assert.fail(`Unexpected module status ${status}.`);
}

async run(isEntryPoint = false) {
Expand Down
1 change: 1 addition & 0 deletions src/debug_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ void NODE_EXTERN_PRIVATE FWrite(FILE* file, const std::string& str);
V(NGTCP2_DEBUG) \
V(SEA) \
V(WASI) \
V(MODULE) \
V(MKSNAPSHOT) \
V(SNAPSHOT_SERDES) \
V(PERMISSION_MODEL) \
Expand Down
12 changes: 12 additions & 0 deletions src/module_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,16 @@ void ModuleWrap::GetStatus(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(module->GetStatus());
}

void ModuleWrap::IsGraphAsync(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
ModuleWrap* obj;
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());

Local<Module> module = obj->module_.Get(isolate);

args.GetReturnValue().Set(module->IsGraphAsync());
}

void ModuleWrap::GetError(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
ModuleWrap* obj;
Expand Down Expand Up @@ -1282,6 +1292,7 @@ void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data,
isolate, tpl, "createCachedData", CreateCachedData);
SetProtoMethodNoSideEffect(isolate, tpl, "getNamespace", GetNamespace);
SetProtoMethodNoSideEffect(isolate, tpl, "getStatus", GetStatus);
SetProtoMethodNoSideEffect(isolate, tpl, "isGraphAsync", IsGraphAsync);
SetProtoMethodNoSideEffect(isolate, tpl, "getError", GetError);
SetConstructorFunction(isolate, target, "ModuleWrap", tpl);
isolate_data->set_module_wrap_constructor_template(tpl);
Expand Down Expand Up @@ -1343,6 +1354,7 @@ void ModuleWrap::RegisterExternalReferences(
registry->Register(GetNamespace);
registry->Register(GetStatus);
registry->Register(GetError);
registry->Register(IsGraphAsync);

registry->Register(CreateRequiredModuleFacade);

Expand Down
1 change: 1 addition & 0 deletions src/module_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ class ModuleWrap : public BaseObject {
static void Instantiate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Evaluate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetNamespace(const v8::FunctionCallbackInfo<v8::Value>& args);
static void IsGraphAsync(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetStatus(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetError(const v8::FunctionCallbackInfo<v8::Value>& args);

Expand Down
4 changes: 4 additions & 0 deletions test/es-module/test-require-module-instantiated.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import '../common/index.mjs';
import assert from 'node:assert';
import { b, c } from '../fixtures/es-modules/require-module-instantiated/a.mjs';
assert.strictEqual(b, c);
2 changes: 2 additions & 0 deletions test/fixtures/es-modules/require-module-instantiated/a.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as b } from './b.cjs';
export { default as c } from './c.mjs';
1 change: 1 addition & 0 deletions test/fixtures/es-modules/require-module-instantiated/b.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./c.mjs');
3 changes: 3 additions & 0 deletions test/fixtures/es-modules/require-module-instantiated/c.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const foo = 1;
export default foo;
export { foo as 'module.exports' };
Loading