mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
module: fix error thrown from require(esm) hitting TLA repeatedly
This tracks the asynchronicity in the ModuleWraps when they turn out to contain TLA after instantiation, and throw the right error (ERR_REQUIRE_ASYNC_MODULE) when it's required again. It removes the freezing of ModuleWraps since it's not meaningful to freeze this when the rest of the module loader is mutable, and we can record the asynchronicity in the ModuleWrap right after compilation after we get a V8 upgrade that contains v8::Module::HasTopLevelAwait() instead of searching through the module graph repeatedly which can be slow. PR-URL: https://github.com/nodejs/node/pull/55520 Fixes: https://github.com/nodejs/node/issues/55516 Refs: https://github.com/nodejs/node/issues/52697 Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Jacob Smith <jacob@frende.me>
This commit is contained in:
parent
3b3a95ac0c
commit
8aac7da7d6
@ -1650,6 +1650,9 @@ E('ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS', '%s', TypeError);
|
||||
E('ERR_QUIC_CONNECTION_FAILED', 'QUIC connection failed', Error);
|
||||
E('ERR_QUIC_ENDPOINT_CLOSED', 'QUIC endpoint closed: %s (%d)', Error);
|
||||
E('ERR_QUIC_OPEN_STREAM_FAILED', 'Failed to open QUIC stream', Error);
|
||||
E('ERR_REQUIRE_ASYNC_MODULE', 'require() cannot be used on an ESM ' +
|
||||
'graph with top-level await. Use import() instead. To see where the' +
|
||||
' top-level await comes from, use --experimental-print-required-tla.', Error);
|
||||
E('ERR_REQUIRE_CYCLE_MODULE', '%s', Error);
|
||||
E('ERR_REQUIRE_ESM',
|
||||
function(filename, hasEsmSyntax, parentPath = null, packageJsonPath = null) {
|
||||
|
@ -23,6 +23,7 @@ const { imported_cjs_symbol } = internalBinding('symbols');
|
||||
|
||||
const assert = require('internal/assert');
|
||||
const {
|
||||
ERR_REQUIRE_ASYNC_MODULE,
|
||||
ERR_REQUIRE_CYCLE_MODULE,
|
||||
ERR_REQUIRE_ESM,
|
||||
ERR_NETWORK_IMPORT_DISALLOWED,
|
||||
@ -302,6 +303,9 @@ class ModuleLoader {
|
||||
// evaluated at this point.
|
||||
if (job !== undefined) {
|
||||
mod[kRequiredModuleSymbol] = job.module;
|
||||
if (job.module.async) {
|
||||
throw new ERR_REQUIRE_ASYNC_MODULE();
|
||||
}
|
||||
if (job.module.getStatus() !== kEvaluated) {
|
||||
const parentFilename = urlToFilename(parent?.filename);
|
||||
let message = `Cannot require() ES Module ${filename} in a cycle.`;
|
||||
|
@ -37,8 +37,11 @@ const resolvedPromise = PromiseResolve();
|
||||
const {
|
||||
setHasStartedUserESMExecution,
|
||||
} = require('internal/modules/helpers');
|
||||
const { getOptionValue } = require('internal/options');
|
||||
const noop = FunctionPrototype;
|
||||
|
||||
const {
|
||||
ERR_REQUIRE_ASYNC_MODULE,
|
||||
} = require('internal/errors').codes;
|
||||
let hasPausedEntry = false;
|
||||
|
||||
const CJSGlobalLike = [
|
||||
@ -378,7 +381,16 @@ class ModuleJobSync extends ModuleJobBase {
|
||||
|
||||
runSync() {
|
||||
// TODO(joyeecheung): add the error decoration logic from the async instantiate.
|
||||
this.module.instantiateSync();
|
||||
this.module.async = this.module.instantiateSync();
|
||||
// If --experimental-print-required-tla is true, proceeds to evaluation even
|
||||
// if it's async because we want to search for the TLA and help users locate
|
||||
// them.
|
||||
// TODO(joyeecheung): track the asynchroniticy using v8::Module::HasTopLevelAwait()
|
||||
// and we'll be able to throw right after compilation of the modules, using acron
|
||||
// to find and print the TLA.
|
||||
if (this.module.async && !getOptionValue('--experimental-print-required-tla')) {
|
||||
throw new ERR_REQUIRE_ASYNC_MODULE();
|
||||
}
|
||||
setHasStartedUserESMExecution();
|
||||
const namespace = this.module.evaluateSync();
|
||||
return { __proto__: null, module: this.module, namespace };
|
||||
|
@ -31,7 +31,6 @@ using v8::FunctionTemplate;
|
||||
using v8::HandleScope;
|
||||
using v8::Int32;
|
||||
using v8::Integer;
|
||||
using v8::IntegrityLevel;
|
||||
using v8::Isolate;
|
||||
using v8::Local;
|
||||
using v8::MaybeLocal;
|
||||
@ -332,7 +331,6 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
obj->contextify_context_ = contextify_context;
|
||||
|
||||
that->SetIntegrityLevel(context, IntegrityLevel::kFrozen);
|
||||
args.GetReturnValue().Set(that);
|
||||
}
|
||||
|
||||
@ -635,13 +633,9 @@ void ModuleWrap::InstantiateSync(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
}
|
||||
|
||||
// If --experimental-print-required-tla is true, proceeds to evaluation even
|
||||
// if it's async because we want to search for the TLA and help users locate
|
||||
// them.
|
||||
if (module->IsGraphAsync() && !env->options()->print_required_tla) {
|
||||
THROW_ERR_REQUIRE_ASYNC_MODULE(env);
|
||||
return;
|
||||
}
|
||||
// TODO(joyeecheung): record Module::HasTopLevelAwait() in every ModuleWrap
|
||||
// and infer the asynchronicity from a module's children during linking.
|
||||
args.GetReturnValue().Set(module->IsGraphAsync());
|
||||
}
|
||||
|
||||
void ModuleWrap::EvaluateSync(const FunctionCallbackInfo<Value>& args) {
|
||||
|
16
test/es-module/test-require-module-tla-retry-require.js
Normal file
16
test/es-module/test-require-module-tla-retry-require.js
Normal file
@ -0,0 +1,16 @@
|
||||
// This tests that after failing to require an ESM that contains TLA,
|
||||
// retrying with require() still throws, and produces consistent results.
|
||||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
assert.throws(() => {
|
||||
require('../fixtures/es-modules/tla/resolved.mjs');
|
||||
}, {
|
||||
code: 'ERR_REQUIRE_ASYNC_MODULE'
|
||||
});
|
||||
assert.throws(() => {
|
||||
require('../fixtures/es-modules/tla/resolved.mjs');
|
||||
}, {
|
||||
code: 'ERR_REQUIRE_ASYNC_MODULE'
|
||||
});
|
Loading…
Reference in New Issue
Block a user