Skip to content

Commit e61f4ea

Browse files
guybedfordMylesBorins
authored andcommitted
module: conditional exports import condition
PR-URL: #30799 Reviewed-By: Jan Krems <[email protected]> Reviewed-By: Myles Borins <[email protected]>
1 parent 1b534d5 commit e61f4ea

File tree

11 files changed

+67
-30
lines changed

11 files changed

+67
-30
lines changed

doc/api/esm.md

+28-21
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,8 @@ Node.js and the browser can be written:
329329
"main": "./index.js",
330330
"exports": {
331331
"./feature": {
332-
"browser": "./feature-browser.js",
333-
"default": "./feature-default.js"
332+
"import": "./feature-default.js",
333+
"browser": "./feature-browser.js"
334334
}
335335
}
336336
}
@@ -341,16 +341,24 @@ will be used as the final fallback.
341341

342342
The conditions supported in Node.js are matched in the following order:
343343

344-
1. `"require"` - matched when the package is loaded via `require()`.
345-
_This is currently only supported behind the
346-
`--experimental-conditional-exports` flag._
347-
2. `"node"` - matched for any Node.js environment. Can be a CommonJS or ES
344+
1. `"node"` - matched for any Node.js environment. Can be a CommonJS or ES
348345
module file. _This is currently only supported behind the
349-
`--experimental-conditional-exports` flag._
350-
3. `"default"` - the generic fallback that will always match if no other
346+
`--experimental-conditional-exports` flag._
347+
2. `"require"` - matched when the package is loaded via `require()`.
348+
_This is currently only supported behind the
349+
`--experimental-conditional-exports` flag._
350+
3. `"import"` - matched when the package is loaded via `import` or
351+
`import()`. Can be any module format, this field does not set the type
352+
interpretation. _This is currently only supported behind the
353+
`--experimental-conditional-exports` flag._
354+
4. `"default"` - the generic fallback that will always match if no other
351355
more specific condition is matched first. Can be a CommonJS or ES module
352356
file.
353357

358+
> Setting any of the above flagged conditions for a published package is not
359+
> recommended until they are unflagged to avoid breaking changes to packages in
360+
> future.
361+
354362
Using the `"require"` condition it is possible to define a package that will
355363
have a different exported value for CommonJS and ES modules, which can be a
356364
hazard in that it can result in having two separate instances of the same
@@ -394,8 +402,8 @@ from exports subpaths.
394402
{
395403
"exports": {
396404
".": {
397-
"require": "./main.cjs",
398-
"default": "./main.js"
405+
"import": "./main.js",
406+
"require": "./main.cjs"
399407
}
400408
}
401409
}
@@ -407,8 +415,8 @@ can be written:
407415
```js
408416
{
409417
"exports": {
410-
"require": "./main.cjs",
411-
"default": "./main.js"
418+
"import": "./main.js",
419+
"require": "./main.cjs"
412420
}
413421
}
414422
```
@@ -422,8 +430,8 @@ thrown:
422430
// Throws on resolution!
423431
"exports": {
424432
"./feature": "./lib/feature.js",
425-
"require": "./main.cjs",
426-
"default": "./main.js"
433+
"import": "./main.js",
434+
"require": "./main.cjs"
427435
}
428436
}
429437
```
@@ -508,9 +516,8 @@ ES module wrapper is used for `import` and the CommonJS entry point for
508516
`require`.
509517

510518
> Note: While `--experimental-conditional-exports` is flagged, a package
511-
> using this pattern will throw when loaded via `require()` in modern
512-
> Node.js, unless package consumers use the `--experimental-conditional-exports`
513-
> flag.
519+
> using this pattern will throw when loaded unless package consumers use the
520+
> `--experimental-conditional-exports` flag.
514521
515522
<!-- eslint-skip -->
516523
```js
@@ -520,7 +527,7 @@ ES module wrapper is used for `import` and the CommonJS entry point for
520527
"main": "./index.cjs",
521528
"exports": {
522529
"require": "./index.cjs",
523-
"default": "./wrapper.mjs"
530+
"import": "./wrapper.mjs"
524531
}
525532
}
526533
```
@@ -605,8 +612,8 @@ CommonJS and ES module entry points directly (requires
605612
"type": "module",
606613
"main": "./index.cjs",
607614
"exports": {
608-
"require": "./index.cjs",
609-
"default": "./index.mjs"
615+
"import": "./index.mjs",
616+
"require": "./index.cjs"
610617
}
611618
}
612619
```
@@ -1149,7 +1156,7 @@ of these top-level routines unless stated otherwise.
11491156
_isMain_ is **true** when resolving the Node.js application entry point.
11501157
11511158
_defaultEnv_ is the conditional environment name priority array,
1152-
`["node", "default"]`.
1159+
`["node", "import"]`.
11531160
11541161
<details>
11551162
<summary>Resolver algorithm specification</summary>

doc/api/modules.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,8 @@ RESOLVE_BARE_SPECIFIER(DIR, X)
241241
g. If no such key can be found, throw "not found".
242242
h. let RESOLVED_URL =
243243
PACKAGE_EXPORTS_TARGET_RESOLVE(pathToFileURL(DIR/name), exports[key],
244-
subpath.slice(key.length)), as defined in the ESM resolver.
244+
subpath.slice(key.length), ["node", "require"]), as defined in the ESM
245+
resolver.
245246
i. return fileURLToPath(RESOLVED_URL)
246247
3. return DIR/X
247248
```

lib/internal/modules/cjs/loader.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -582,9 +582,9 @@ function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) {
582582
}
583583
} else if (typeof target === 'object' && target !== null) {
584584
if (experimentalConditionalExports &&
585-
ObjectPrototypeHasOwnProperty(target, 'require')) {
585+
ObjectPrototypeHasOwnProperty(target, 'node')) {
586586
try {
587-
const result = resolveExportsTarget(pkgPath, target.require, subpath,
587+
const result = resolveExportsTarget(pkgPath, target.node, subpath,
588588
basePath, mappingKey);
589589
emitExperimentalWarning('Conditional exports');
590590
return result;
@@ -593,9 +593,9 @@ function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) {
593593
}
594594
}
595595
if (experimentalConditionalExports &&
596-
ObjectPrototypeHasOwnProperty(target, 'node')) {
596+
ObjectPrototypeHasOwnProperty(target, 'require')) {
597597
try {
598-
const result = resolveExportsTarget(pkgPath, target.node, subpath,
598+
const result = resolveExportsTarget(pkgPath, target.require, subpath,
599599
basePath, mappingKey);
600600
emitExperimentalWarning('Conditional exports');
601601
return result;

src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ constexpr size_t kFsStatsBufferLength =
255255
V(hostmaster_string, "hostmaster") \
256256
V(http_1_1_string, "http/1.1") \
257257
V(ignore_string, "ignore") \
258+
V(import_string, "import") \
258259
V(infoaccess_string, "infoAccess") \
259260
V(inherit_string, "inherit") \
260261
V(input_string, "input") \

src/module_wrap.cc

+11
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,17 @@ Maybe<URL> ResolveExportsTarget(Environment* env,
967967
return resolved;
968968
}
969969
}
970+
if (env->options()->experimental_conditional_exports &&
971+
target_obj->HasOwnProperty(context, env->import_string()).FromJust()) {
972+
matched = true;
973+
conditionalTarget =
974+
target_obj->Get(context, env->import_string()).ToLocalChecked();
975+
Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url,
976+
conditionalTarget, subpath, pkg_subpath, base, false);
977+
if (!resolved.IsNothing()) {
978+
return resolved;
979+
}
980+
}
970981
if (target_obj->HasOwnProperty(context, env->default_string()).FromJust()) {
971982
matched = true;
972983
conditionalTarget =

test/es-module/test-esm-exports.mjs

+11-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
3131
['pkgexports-sugar', { default: 'main' }],
3232
// Conditional object exports sugar
3333
['pkgexports-sugar2', isRequire ? { default: 'not-exported' } :
34-
{ default: 'main' }]
34+
{ default: 'main' }],
3535
]);
3636

3737
for (const [validSpecifier, expected] of validSpecifiers) {
@@ -51,7 +51,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
5151
['pkgexports-number/hidden.js', './hidden.js'],
5252
// Sugar cases still encapsulate
5353
['pkgexports-sugar/not-exported.js', './not-exported.js'],
54-
['pkgexports-sugar2/not-exported.js', './not-exported.js']
54+
['pkgexports-sugar2/not-exported.js', './not-exported.js'],
5555
]);
5656

5757
const invalidExports = new Map([
@@ -97,6 +97,15 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
9797
}));
9898
}
9999

100+
// Conditional export, even with no match, should still be used instead
101+
// of falling back to main
102+
if (isRequire) {
103+
loadFixture('pkgexports-main').catch(mustCall((err) => {
104+
strictEqual(err.code, 'MODULE_NOT_FOUND');
105+
assertStartsWith(err.message, 'No valid export');
106+
}));
107+
}
108+
100109
// Covering out bases - not a file is still not a file after dir mapping.
101110
loadFixture('pkgexports/sub/not-a-file.js').catch(mustCall((err) => {
102111
strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND');

test/fixtures/node_modules/pkgexports-main/main.cjs

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/node_modules/pkgexports-main/module.mjs

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/node_modules/pkgexports-main/package.json

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/node_modules/pkgexports-sugar-fail/package.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/node_modules/pkgexports-sugar2/package.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)