diff --git a/lib/internal/errors.js b/lib/internal/errors.js index a14c0f9511251e..f31bac27e3fd40 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1652,6 +1652,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) { diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 262006f8b3a542..c5594e07d667c3 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -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.`; diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index ece30c3864d6a7..8fba05e7b8f699 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -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 }; diff --git a/src/module_wrap.cc b/src/module_wrap.cc index e2252639cf4518..6f8c9d10955b88 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -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& args) { obj->contextify_context_ = contextify_context; - that->SetIntegrityLevel(context, IntegrityLevel::kFrozen); args.GetReturnValue().Set(that); } @@ -635,13 +633,9 @@ void ModuleWrap::InstantiateSync(const FunctionCallbackInfo& 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& args) { diff --git a/test/es-module/test-require-module-tla-retry-require.js b/test/es-module/test-require-module-tla-retry-require.js new file mode 100644 index 00000000000000..d440698fc22b52 --- /dev/null +++ b/test/es-module/test-require-module-tla-retry-require.js @@ -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' +});