Skip to content

Commit 5b4db8e

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 15d48f9 commit 5b4db8e

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
@@ -335,8 +335,8 @@ Node.js and the browser can be written:
335335
"main": "./index.js",
336336
"exports": {
337337
"./feature": {
338-
"browser": "./feature-browser.js",
339-
"default": "./feature-default.js"
338+
"import": "./feature-default.js",
339+
"browser": "./feature-browser.js"
340340
}
341341
}
342342
}
@@ -347,16 +347,24 @@ will be used as the final fallback.
347347

348348
The conditions supported in Node.js are matched in the following order:
349349

350-
1. `"require"` - matched when the package is loaded via `require()`.
351-
_This is currently only supported behind the
352-
`--experimental-conditional-exports` flag._
353-
2. `"node"` - matched for any Node.js environment. Can be a CommonJS or ES
350+
1. `"node"` - matched for any Node.js environment. Can be a CommonJS or ES
354351
module file. _This is currently only supported behind the
355-
`--experimental-conditional-exports` flag._
356-
3. `"default"` - the generic fallback that will always match if no other
352+
`--experimental-conditional-exports` flag._
353+
2. `"require"` - matched when the package is loaded via `require()`.
354+
_This is currently only supported behind the
355+
`--experimental-conditional-exports` flag._
356+
3. `"import"` - matched when the package is loaded via `import` or
357+
`import()`. Can be any module format, this field does not set the type
358+
interpretation. _This is currently only supported behind the
359+
`--experimental-conditional-exports` flag._
360+
4. `"default"` - the generic fallback that will always match if no other
357361
more specific condition is matched first. Can be a CommonJS or ES module
358362
file.
359363

364+
> Setting any of the above flagged conditions for a published package is not
365+
> recommended until they are unflagged to avoid breaking changes to packages in
366+
> future.
367+
360368
Using the `"require"` condition it is possible to define a package that will
361369
have a different exported value for CommonJS and ES modules, which can be a
362370
hazard in that it can result in having two separate instances of the same
@@ -400,8 +408,8 @@ from exports subpaths.
400408
{
401409
"exports": {
402410
".": {
403-
"require": "./main.cjs",
404-
"default": "./main.js"
411+
"import": "./main.js",
412+
"require": "./main.cjs"
405413
}
406414
}
407415
}
@@ -413,8 +421,8 @@ can be written:
413421
```js
414422
{
415423
"exports": {
416-
"require": "./main.cjs",
417-
"default": "./main.js"
424+
"import": "./main.js",
425+
"require": "./main.cjs"
418426
}
419427
}
420428
```
@@ -428,8 +436,8 @@ thrown:
428436
// Throws on resolution!
429437
"exports": {
430438
"./feature": "./lib/feature.js",
431-
"require": "./main.cjs",
432-
"default": "./main.js"
439+
"import": "./main.js",
440+
"require": "./main.cjs"
433441
}
434442
}
435443
```
@@ -514,9 +522,8 @@ ES module wrapper is used for `import` and the CommonJS entry point for
514522
`require`.
515523

516524
> Note: While `--experimental-conditional-exports` is flagged, a package
517-
> using this pattern will throw when loaded via `require()` in modern
518-
> Node.js, unless package consumers use the `--experimental-conditional-exports`
519-
> flag.
525+
> using this pattern will throw when loaded unless package consumers use the
526+
> `--experimental-conditional-exports` flag.
520527
521528
<!-- eslint-skip -->
522529
```js
@@ -526,7 +533,7 @@ ES module wrapper is used for `import` and the CommonJS entry point for
526533
"main": "./index.cjs",
527534
"exports": {
528535
"require": "./index.cjs",
529-
"default": "./wrapper.mjs"
536+
"import": "./wrapper.mjs"
530537
}
531538
}
532539
```
@@ -611,8 +618,8 @@ CommonJS and ES module entry points directly (requires
611618
"type": "module",
612619
"main": "./index.cjs",
613620
"exports": {
614-
"require": "./index.cjs",
615-
"default": "./index.mjs"
621+
"import": "./index.mjs",
622+
"require": "./index.cjs"
616623
}
617624
}
618625
```
@@ -1152,7 +1159,7 @@ of these top-level routines unless stated otherwise.
11521159
_isMain_ is **true** when resolving the Node.js application entry point.
11531160
11541161
_defaultEnv_ is the conditional environment name priority array,
1155-
`["node", "default"]`.
1162+
`["node", "import"]`.
11561163
11571164
<details>
11581165
<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
@@ -574,9 +574,9 @@ function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) {
574574
}
575575
} else if (typeof target === 'object' && target !== null) {
576576
if (experimentalConditionalExports &&
577-
ObjectPrototype.hasOwnProperty(target, 'require')) {
577+
ObjectPrototype.hasOwnProperty(target, 'node')) {
578578
try {
579-
const result = resolveExportsTarget(pkgPath, target.require, subpath,
579+
const result = resolveExportsTarget(pkgPath, target.node, subpath,
580580
basePath, mappingKey);
581581
emitExperimentalWarning('Conditional exports');
582582
return result;
@@ -585,9 +585,9 @@ function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) {
585585
}
586586
}
587587
if (experimentalConditionalExports &&
588-
ObjectPrototype.hasOwnProperty(target, 'node')) {
588+
ObjectPrototype.hasOwnProperty(target, 'require')) {
589589
try {
590-
const result = resolveExportsTarget(pkgPath, target.node, subpath,
590+
const result = resolveExportsTarget(pkgPath, target.require, subpath,
591591
basePath, mappingKey);
592592
emitExperimentalWarning('Conditional exports');
593593
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
@@ -32,7 +32,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
3232
['pkgexports-sugar', { default: 'main' }],
3333
// Conditional object exports sugar
3434
['pkgexports-sugar2', isRequire ? { default: 'not-exported' } :
35-
{ default: 'main' }]
35+
{ default: 'main' }],
3636
]);
3737

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

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

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