Skip to content

Commit 6929649

Browse files
guybedfordBethGriggs
authored andcommitted
module: custom --conditions flag option
PR-URL: #34637 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Geoffrey Booth <[email protected]> Reviewed-By: Jan Krems <[email protected]>
1 parent 201d3d7 commit 6929649

File tree

10 files changed

+93
-38
lines changed

10 files changed

+93
-38
lines changed

doc/api/cli.md

+16
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ $ node --completion-bash > node_bash_completion
7676
$ source node_bash_completion
7777
```
7878

79+
### `-u`, `--conditions=condition`
80+
<!-- YAML
81+
added: REPLACEME
82+
-->
83+
84+
> Stability: 1 - Experimental
85+
86+
Enable experimental support for custom conditional exports resolution
87+
conditions.
88+
89+
Any number of custom string condition names are permitted.
90+
91+
The default Node.js conditions of `"node"`, `"default"`, `"import"`, and
92+
`"require"` will always apply as defined.
93+
7994
### `--cpu-prof`
8095
<!-- YAML
8196
added: v12.0.0
@@ -1199,6 +1214,7 @@ node --require "./a.js" --require "./b.js"
11991214

12001215
Node.js options that are allowed are:
12011216
<!-- node-options-node start -->
1217+
* `--conditions`, `-u`
12021218
* `--diagnostic-dir`
12031219
* `--disable-proto`
12041220
* `--enable-fips`

doc/api/esm.md

+15
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,21 @@ a nested conditional does not have any mapping it will continue checking
501501
the remaining conditions of the parent condition. In this way nested
502502
conditions behave analogously to nested JavaScript `if` statements.
503503

504+
#### Resolving user conditions
505+
506+
When running Node.js, custom user conditions can be added with the
507+
`--conditions` or `-u` flag:
508+
509+
```bash
510+
node --conditions=development main.js
511+
```
512+
513+
which would then resolve the `"development"` condition in package imports and
514+
exports, while resolving the existing `"node"`, `"default"`, `"import"`, and
515+
`"require"` conditions as appropriate.
516+
517+
Any number of custom conditions can be set with repeat flags.
518+
504519
#### Self-referencing a package using its name
505520

506521
Within a package, the values defined in the package’s

doc/node.1

+4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ Aborting instead of exiting causes a core file to be generated for analysis.
7878
.It Fl -completion-bash
7979
Print source-able bash completion script for Node.js.
8080
.
81+
.It Fl u , Fl -conditions Ar string
82+
Use custom conditional exports conditions
83+
.Ar string
84+
.
8185
.It Fl -cpu-prof
8286
Start the V8 CPU profiler on start up, and write the CPU profile to disk
8387
before exit. If

lib/internal/modules/cjs/loader.js

+33-31
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const manifest = getOptionValue('--experimental-policy') ?
8484
require('internal/process/policy').manifest :
8585
null;
8686
const { compileFunction } = internalBinding('contextify');
87+
const userConditions = getOptionValue('--conditions');
8788

8889
// Whether any user-provided CJS modules had been loaded (executed).
8990
// Used for internal assertions.
@@ -477,8 +478,12 @@ function applyExports(basePath, expansion) {
477478
if (typeof pkgExports === 'object') {
478479
if (ObjectPrototypeHasOwnProperty(pkgExports, mappingKey)) {
479480
const mapping = pkgExports[mappingKey];
480-
return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping, '',
481-
mappingKey);
481+
const resolved = resolveExportsTarget(
482+
pathToFileURL(basePath + '/'), mapping, '', mappingKey);
483+
if (resolved === null || resolved === undefined)
484+
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
485+
basePath, mappingKey);
486+
return resolved;
482487
}
483488

484489
let dirMatch = '';
@@ -495,6 +500,9 @@ function applyExports(basePath, expansion) {
495500
const subpath = StringPrototypeSlice(mappingKey, dirMatch.length);
496501
const resolved = resolveExportsTarget(pathToFileURL(basePath + '/'),
497502
mapping, subpath, mappingKey);
503+
if (resolved === null || resolved === undefined)
504+
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
505+
basePath, mappingKey + subpath);
498506
// Extension searching for folder exports only
499507
const rc = stat(resolved);
500508
if (rc === 0) return resolved;
@@ -582,21 +590,29 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) {
582590
throw new ERR_INVALID_MODULE_SPECIFIER(mappingKey + subpath, reason);
583591
} else if (ArrayIsArray(target)) {
584592
if (target.length === 0)
585-
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
586-
baseUrl.pathname, mappingKey + subpath);
593+
return null;
587594
let lastException;
588595
for (const targetValue of target) {
596+
let resolved;
589597
try {
590-
return resolveExportsTarget(baseUrl, targetValue, subpath, mappingKey);
598+
resolved = resolveExportsTarget(baseUrl, targetValue, subpath,
599+
mappingKey);
591600
} catch (e) {
592601
lastException = e;
593-
if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED' &&
594-
e.code !== 'ERR_INVALID_PACKAGE_TARGET')
602+
if (e.code !== 'ERR_INVALID_PACKAGE_TARGET')
595603
throw e;
596604
}
605+
if (resolved === undefined)
606+
continue;
607+
if (resolved === null) {
608+
lastException = null;
609+
continue;
610+
}
611+
return resolved;
597612
}
598613
// Throw last fallback error
599-
assert(lastException !== undefined);
614+
if (lastException === undefined || lastException === null)
615+
return lastException;
600616
throw lastException;
601617
} else if (typeof target === 'object' && target !== null) {
602618
const keys = ObjectKeys(target);
@@ -605,30 +621,17 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) {
605621
'contain numeric property keys.');
606622
}
607623
for (const p of keys) {
608-
switch (p) {
609-
case 'node':
610-
case 'require':
611-
try {
612-
return resolveExportsTarget(baseUrl, target[p], subpath,
613-
mappingKey);
614-
} catch (e) {
615-
if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw e;
616-
}
617-
break;
618-
case 'default':
619-
try {
620-
return resolveExportsTarget(baseUrl, target.default, subpath,
621-
mappingKey);
622-
} catch (e) {
623-
if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw e;
624-
}
624+
if (cjsConditions.has(p) || p === 'default') {
625+
const resolved = resolveExportsTarget(baseUrl, target[p], subpath,
626+
mappingKey);
627+
if (resolved === undefined)
628+
continue;
629+
return resolved;
625630
}
626631
}
627-
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
628-
baseUrl.pathname, mappingKey + subpath);
632+
return undefined;
629633
} else if (target === null) {
630-
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
631-
baseUrl.pathname, mappingKey + subpath);
634+
return null;
632635
}
633636
throw new ERR_INVALID_PACKAGE_TARGET(baseUrl.pathname, mappingKey, target);
634637
}
@@ -985,8 +988,7 @@ Module._load = function(request, parent, isMain) {
985988
return module.exports;
986989
};
987990

988-
// TODO: Use this set when resolving pkg#exports conditions.
989-
const cjsConditions = new SafeSet(['require', 'node']);
991+
const cjsConditions = new SafeSet(['require', 'node', ...userConditions]);
990992
Module._resolveFilename = function(request, parent, isMain, options) {
991993
if (NativeModule.canBeRequiredByUsers(request)) {
992994
return request;

lib/internal/modules/esm/resolve.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ const {
5151
const { Module: CJSModule } = require('internal/modules/cjs/loader');
5252

5353
const packageJsonReader = require('internal/modules/package_json_reader');
54-
const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import']);
54+
const userConditions = getOptionValue('--conditions');
55+
const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import', ...userConditions]);
5556
const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);
5657

5758

@@ -359,12 +360,9 @@ function isArrayIndex(key) {
359360
function resolvePackageTarget(
360361
packageJSONUrl, target, subpath, packageSubpath, base, internal, conditions) {
361362
if (typeof target === 'string') {
362-
const resolved = resolvePackageTargetString(
363+
return finalizeResolution(resolvePackageTargetString(
363364
target, subpath, packageSubpath, packageJSONUrl, base, internal,
364-
conditions);
365-
if (resolved === null)
366-
return null;
367-
return finalizeResolution(resolved, base);
365+
conditions), base);
368366
} else if (ArrayIsArray(target)) {
369367
if (target.length === 0)
370368
return null;

src/node_options.cc

+5
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,11 @@ DebugOptionsParser::DebugOptionsParser() {
281281
}
282282

283283
EnvironmentOptionsParser::EnvironmentOptionsParser() {
284+
AddOption("--conditions",
285+
"additional user conditions for conditional exports and imports",
286+
&EnvironmentOptions::conditions,
287+
kAllowedInEnvironment);
288+
AddAlias("-u", "--conditions");
284289
AddOption("--diagnostic-dir",
285290
"set dir for all output files"
286291
" (default: current working directory)",

src/node_options.h

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ class DebugOptions : public Options {
100100
class EnvironmentOptions : public Options {
101101
public:
102102
bool abort_on_uncaught_exception = false;
103+
std::vector<std::string> conditions;
103104
bool enable_source_maps = false;
104105
bool experimental_json_modules = false;
105106
bool experimental_modules = false;
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Flags: --conditions=custom-condition -u another
2+
import { mustCall } from '../common/index.mjs';
3+
import { strictEqual } from 'assert';
4+
import { requireFixture, importFixture } from '../fixtures/pkgexports.mjs';
5+
[requireFixture, importFixture].forEach((loadFixture) => {
6+
loadFixture('pkgexports/condition')
7+
.then(mustCall((actual) => {
8+
strictEqual(actual.default, 'from custom condition');
9+
}));
10+
});

test/fixtures/node_modules/pkgexports/custom-condition.js

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

test/fixtures/node_modules/pkgexports/package.json

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

0 commit comments

Comments
 (0)