Skip to content

Commit 289a1cd

Browse files
committed
module: support detection when require()ing implicit ESM
1 parent 80678e1 commit 289a1cd

11 files changed

+113
-12
lines changed

.eslintignore

+2
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ doc/changelogs/CHANGELOG_v1*.md
1313
!doc/changelogs/CHANGELOG_v18.md
1414
!doc/api_assets/*.js
1515
!.eslintrc.js
16+
test/es-module/test-require-module-detect-entry-point.js
17+
test/es-module/test-require-module-detect-entry-point-aou.js

doc/api/cli.md

+14
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,19 @@ Supports loading a synchronous ES module graph in `require()`.
883883

884884
See [Loading ECMAScript modules using `require()`][].
885885

886+
### `--experimental-require-module-with-detection`
887+
888+
<!-- YAML
889+
added: REPLACEME
890+
-->
891+
892+
> Stability: 1.1 - Active Developement
893+
894+
In addition to what `--experimental-require-module` supports, when the module
895+
being `require()`'d is not explicitly marked as an ES Module using the
896+
`"type": "module"` field in `package.json` or the `.mjs` extension, it will
897+
try to detect module type based on the syntax of the module.
898+
886899
### `--experimental-sea-config`
887900

888901
<!-- YAML
@@ -2537,6 +2550,7 @@ Node.js options that are allowed are:
25372550
* `--experimental-permission`
25382551
* `--experimental-policy`
25392552
* `--experimental-print-required-tla`
2553+
* `--experimental-require-module-with-detection`
25402554
* `--experimental-require-module`
25412555
* `--experimental-shadow-realm`
25422556
* `--experimental-specifier-resolution`

lib/internal/modules/cjs/loader.js

+3
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,9 @@ Module.prototype._compile = function(content, filename, loadAsESM = false) {
13701370
if (!loadAsESM) {
13711371
const result = wrapSafe(filename, content, this);
13721372
compiledWrapper = result.function;
1373+
// If C++ land confirms it looks like ESM and we should retry here (because fallback
1374+
// detection is enabled), retry.
1375+
loadAsESM = result.retryAsESM;
13731376
}
13741377

13751378
if (loadAsESM) {

src/node_contextify.cc

+35-8
Original file line numberDiff line numberDiff line change
@@ -1510,6 +1510,13 @@ const char* require_esm_warning =
15101510
"or the module should use the .mjs extension.\n"
15111511
"- If it's loaded using require(), use --experimental-require-module";
15121512

1513+
static bool warned_about_require_esm_detection = false;
1514+
const char* require_esm_warning_without_detection =
1515+
"The module being require()d looks like an ES module, but it is not "
1516+
"explicitly marked with \"type\": \"module\" in the package.json or "
1517+
"with a .mjs extention. To enable automatic detection of module syntax "
1518+
"in require(), use --experimental-require-module-with-detection.";
1519+
15131520
static void CompileFunctionForCJSLoader(
15141521
const FunctionCallbackInfo<Value>& args) {
15151522
CHECK(args[0]->IsString());
@@ -1553,6 +1560,7 @@ static void CompileFunctionForCJSLoader(
15531560

15541561
std::vector<Local<String>> params = GetCJSParameters(env->isolate_data());
15551562

1563+
bool retry_as_esm = false;
15561564
Local<Function> fn;
15571565

15581566
{
@@ -1597,13 +1605,29 @@ static void CompileFunctionForCJSLoader(
15971605
return;
15981606
}
15991607

1600-
if (!warned_about_require_esm) {
1601-
USE(ProcessEmitWarningSync(env, require_esm_warning));
1602-
warned_about_require_esm = true;
1608+
if (!env->options()->require_module) {
1609+
if (!warned_about_require_esm) {
1610+
USE(ProcessEmitWarningSync(env, require_esm_warning));
1611+
warned_about_require_esm = true;
1612+
}
1613+
errors::DecorateErrorStack(env, try_catch);
1614+
try_catch.ReThrow();
1615+
return;
16031616
}
1604-
errors::DecorateErrorStack(env, try_catch);
1605-
try_catch.ReThrow();
1606-
return;
1617+
1618+
if (!env->options()->require_module_with_detection) {
1619+
if (!warned_about_require_esm_detection) {
1620+
USE(ProcessEmitWarningSync(env,
1621+
require_esm_warning_without_detection));
1622+
warned_about_require_esm_detection = true;
1623+
}
1624+
errors::DecorateErrorStack(env, try_catch);
1625+
try_catch.ReThrow();
1626+
return;
1627+
}
1628+
1629+
// The file being compiled is likely ESM.
1630+
retry_as_esm = true;
16071631
}
16081632
}
16091633
}
@@ -1623,11 +1647,14 @@ static void CompileFunctionForCJSLoader(
16231647
env->cached_data_rejected_string(),
16241648
env->source_map_url_string(),
16251649
env->function_string(),
1650+
FIXED_ONE_BYTE_STRING(isolate, "retryAsESM"),
16261651
};
16271652
std::vector<Local<Value>> values = {
16281653
Boolean::New(isolate, cache_rejected),
1629-
fn->GetScriptOrigin().SourceMapUrl(),
1630-
fn.As<Value>(),
1654+
retry_as_esm ? v8::Undefined(isolate).As<Value>()
1655+
: fn->GetScriptOrigin().SourceMapUrl(),
1656+
retry_as_esm ? v8::Undefined(isolate).As<Value>() : fn.As<Value>(),
1657+
Boolean::New(isolate, retry_as_esm),
16311658
};
16321659
Local<Object> result = Object::New(
16331660
isolate, v8::Null(isolate), names.data(), values.data(), names.size());

src/node_options.cc

+8
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,14 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
363363
"Allow loading explicit ES Modules in require().",
364364
&EnvironmentOptions::require_module,
365365
kAllowedInEnvvar);
366+
AddOption("--experimental-require-module-with-detection",
367+
"Allow loading ES Modules with syntax-based detection of module "
368+
"types for implicit ES modules in require().",
369+
&EnvironmentOptions::require_module_with_detection,
370+
kAllowedInEnvvar);
371+
Implies("--experimental-require-module-with-detection",
372+
"--experimental-require-module");
373+
366374
AddOption("--diagnostic-dir",
367375
"set dir for all output files"
368376
" (default: current working directory)",

src/node_options.h

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ class EnvironmentOptions : public Options {
107107
bool detect_module = false;
108108
bool print_required_tla = false;
109109
bool require_module = false;
110+
bool require_module_with_detection = false;
110111
std::string dns_result_order;
111112
bool enable_source_maps = false;
112113
bool experimental_fetch = true;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Flags: --experimental-require-module-with-detection --abort-on-uncaught-exception
2+
'use strict';
3+
4+
import { mustCall } from '../common/index.mjs';
5+
const fn = mustCall(() => {
6+
console.log('hello');
7+
});
8+
fn();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Flags: --experimental-require-module-with-detection
2+
'use strict';
3+
4+
import { mustCall } from '../common/index.mjs';
5+
const fn = mustCall(() => {
6+
console.log('hello');
7+
});
8+
fn();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Flags: --experimental-require-module-with-detection
2+
'use strict';
3+
4+
require('../common');
5+
const assert = require('assert');
6+
7+
assert.throws(() => {
8+
require('../fixtures/es-modules/es-note-unexpected-export-1.cjs');
9+
}, {
10+
message: /Unexpected token 'export'/
11+
});

test/es-module/test-require-module-implicit.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ const assert = require('assert');
88

99
common.expectWarning(
1010
'Warning',
11-
'To load an ES module:\n' +
12-
'- Either the nearest package.json should set "type":"module", ' +
13-
'or the module should use the .mjs extension.\n' +
14-
'- If it\'s loaded using require(), use --experimental-require-module'
11+
'The module being require()d looks like an ES module, but it is ' +
12+
'not explicitly marked with "type": "module" in the package.json ' +
13+
'or with a .mjs extention. To enable automatic detection of module ' +
14+
'syntax in require(), use --experimental-require-module-with-detection.'
1515
);
16+
1617
common.expectWarning(
1718
'ExperimentalWarning',
1819
'Support for loading ES Module in require() is an experimental feature ' +
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Flags: --experimental-require-module-with-detection
2+
'use strict';
3+
4+
require('../common');
5+
const assert = require('assert');
6+
const { isModuleNamespaceObject } = require('util/types');
7+
8+
{
9+
const mod = require('../fixtures/es-modules/loose.js');
10+
assert.deepStrictEqual({ ...mod }, { default: 'module' });
11+
assert(isModuleNamespaceObject(mod));
12+
}
13+
14+
{
15+
const mod = require('../fixtures/es-modules/package-without-type/noext-esm');
16+
assert.deepStrictEqual({ ...mod }, { default: 'module' });
17+
assert(isModuleNamespaceObject(mod));
18+
}

0 commit comments

Comments
 (0)