@@ -1398,6 +1398,25 @@ constexpr std::array<std::string_view, 3> esm_syntax_error_messages = {
1398
1398
" Unexpected token 'export'" , // `export` statements
1399
1399
" Cannot use 'import.meta' outside a module" }; // `import.meta` references
1400
1400
1401
+ // Another class of error messages that we need to check for are syntax errors
1402
+ // where the syntax throws when parsed as CommonJS but succeeds when parsed as
1403
+ // ESM. So far, the cases we've found are:
1404
+ // - CommonJS module variables (`module`, `exports`, `require`, `__filename`,
1405
+ // `__dirname`): if the user writes code such as `const module =` in the top
1406
+ // level of a CommonJS module, it will throw a syntax error; but the same
1407
+ // code is valid in ESM.
1408
+ // - Top-level `await`: if the user writes `await` at the top level of a
1409
+ // CommonJS module, it will throw a syntax error; but the same code is valid
1410
+ // in ESM.
1411
+ constexpr std::array<std::string_view, 6 > throws_only_in_cjs_error_messages = {
1412
+ " Identifier 'module' has already been declared" ,
1413
+ " Identifier 'exports' has already been declared" ,
1414
+ " Identifier 'require' has already been declared" ,
1415
+ " Identifier '__filename' has already been declared" ,
1416
+ " Identifier '__dirname' has already been declared" ,
1417
+ " await is only valid in async functions and "
1418
+ " the top level bodies of modules" };
1419
+
1401
1420
void ContextifyContext::ContainsModuleSyntax (
1402
1421
const FunctionCallbackInfo<Value>& args) {
1403
1422
Environment* env = Environment::GetCurrent (args);
@@ -1470,19 +1489,51 @@ void ContextifyContext::ContainsModuleSyntax(
1470
1489
id_symbol,
1471
1490
try_catch);
1472
1491
1473
- bool found_error_message_caused_by_module_syntax = false ;
1492
+ bool should_retry_as_esm = false ;
1474
1493
if (try_catch.HasCaught () && !try_catch.HasTerminated ()) {
1475
1494
Utf8Value message_value (env->isolate (), try_catch.Message ()->Get ());
1476
1495
auto message = message_value.ToStringView ();
1477
1496
1478
1497
for (const auto & error_message : esm_syntax_error_messages) {
1479
1498
if (message.find (error_message) != std::string_view::npos) {
1480
- found_error_message_caused_by_module_syntax = true ;
1499
+ should_retry_as_esm = true ;
1500
+ break ;
1501
+ }
1502
+ }
1503
+
1504
+ for (const auto & error_message : throws_only_in_cjs_error_messages) {
1505
+ if (message.find (error_message) != std::string_view::npos) {
1506
+ // Try parsing again where the user's code is wrapped within an async
1507
+ // function. If the new parse succeeds, then the error was caused by
1508
+ // either a top-level declaration of one of the CommonJS module
1509
+ // variables, or a top-level `await`.
1510
+ TryCatchScope second_parse_try_catch (env);
1511
+ Local<String> wrapped_code = String::Concat (isolate,
1512
+ String::NewFromUtf8 (isolate, " (async function() {" ).ToLocalChecked (),
1513
+ code);
1514
+ wrapped_code = String::Concat (isolate, wrapped_code,
1515
+ String::NewFromUtf8 (isolate, " })();" ).ToLocalChecked ());
1516
+ ScriptCompiler::Source wrapped_source = GetCommonJSSourceInstance (
1517
+ isolate, wrapped_code, filename, 0 , 0 , host_defined_options,
1518
+ nullptr );
1519
+ ContextifyContext::CompileFunctionAndCacheResult (env,
1520
+ context,
1521
+ &wrapped_source,
1522
+ std::move (params),
1523
+ std::vector<Local<Object>>(),
1524
+ options,
1525
+ true ,
1526
+ id_symbol,
1527
+ second_parse_try_catch);
1528
+ if (!second_parse_try_catch.HasCaught () &&
1529
+ !second_parse_try_catch.HasTerminated ()) {
1530
+ should_retry_as_esm = true ;
1531
+ }
1481
1532
break ;
1482
1533
}
1483
1534
}
1484
1535
}
1485
- args.GetReturnValue ().Set (found_error_message_caused_by_module_syntax );
1536
+ args.GetReturnValue ().Set (should_retry_as_esm );
1486
1537
}
1487
1538
1488
1539
static void StartSigintWatchdog (const FunctionCallbackInfo<Value>& args) {
0 commit comments