module: improve error message from asynchronicity in require(esm)

- Improve the error message that shows up when there is a race
  from doing require(esm) and import(esm) at the same time.
- Improve error message of ERR_REQUIRE_ASYNC_MODULE by showing
  parent and target file names, if available.

Drive-by: split the require(tla) tests since we are modifying
the tests already.

PR-URL: https://github.com/nodejs/node/pull/57126
Refs: https://github.com/fisker/prettier-issue-17139
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
Joyee Cheung 2025-02-17 19:44:50 +01:00 committed by Node.js GitHub Bot
parent d601c7d104
commit 8d10bc7b09
13 changed files with 190 additions and 79 deletions

View file

@ -338,20 +338,37 @@ class ModuleLoader {
// TODO(joyeecheung): ensure that imported synchronous graphs are evaluated
// synchronously so that any previously imported synchronous graph is already
// evaluated at this point.
// TODO(joyeecheung): add something similar to CJS loader's requireStack to help
// debugging the the problematic links in the graph for import.
if (job !== undefined) {
mod[kRequiredModuleSymbol] = job.module;
if (job.module.async) {
throw new ERR_REQUIRE_ASYNC_MODULE();
const parentFilename = urlToFilename(parent?.filename);
// TODO(node:55782): this race may stop to happen when the ESM resolution and loading become synchronous.
if (!job.module) {
let message = `Cannot require() ES Module ${filename} because it is not yet fully loaded. `;
message += 'This may be caused by a race condition if the module is simultaneously dynamically ';
message += 'import()-ed via Promise.all(). Try await-ing the import() sequentially in a loop instead.';
if (parentFilename) {
message += ` (from ${parentFilename})`;
}
assert(job.module, message);
}
if (job.module.async) {
throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename);
}
// job.module may be undefined if it's asynchronously loaded. Which means
// there is likely a cycle.
if (job.module.getStatus() !== kEvaluated) {
const parentFilename = urlToFilename(parent?.filename);
let message = `Cannot require() ES Module ${filename} in a cycle.`;
if (parentFilename) {
message += ` (from ${parentFilename})`;
}
message += 'A cycle involving require(esm) is disallowed to maintain ';
message += 'invariants madated by the ECMAScript specification';
message += 'Try making at least part of the dependency in the graph lazily loaded.';
throw new ERR_REQUIRE_CYCLE_MODULE(message);
}
return { wrap: job.module, namespace: job.module.getNamespaceSync() };
return { wrap: job.module, namespace: job.module.getNamespaceSync(filename, parentFilename) };
}
// TODO(joyeecheung): refactor this so that we pre-parse in C++ and hit the
// cache here, or use a carrier object to carry the compiled module script
@ -363,7 +380,7 @@ class ModuleLoader {
job = new ModuleJobSync(this, url, kEmptyObject, wrap, isMain, inspectBrk);
this.loadCache.set(url, kImplicitTypeAttribute, job);
mod[kRequiredModuleSymbol] = job.module;
return { wrap: job.module, namespace: job.runSync().namespace };
return { wrap: job.module, namespace: job.runSync(parent).namespace };
}
/**