Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: --dev flag for development exports resolution condition #33171

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ added: v12.0.0

Specify the file name of the CPU profile generated by `--cpu-prof`.

### `--dev`
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

Enables the `"development"` [conditional export][] in package resolution.

### `--disable-proto=mode`
<!--YAML
added: v13.12.0
Expand Down Expand Up @@ -1150,6 +1159,7 @@ node --require "./a.js" --require "./b.js"

Node.js options that are allowed are:
<!-- node-options-node start -->
* `--dev`
* `--disable-proto`
* `--enable-fips`
* `--enable-source-maps`
Expand Down Expand Up @@ -1495,3 +1505,4 @@ $ node --max-old-space-size=1536 index.js
[jitless]: https://v8.dev/blog/jitless
[libuv threadpool documentation]: http://docs.libuv.org/en/latest/threadpool.html
[remote code execution]: https://www.owasp.org/index.php/Code_Injection
[conditional export]: esm.html##esm_conditional_exports
13 changes: 10 additions & 3 deletions doc/api/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,8 @@ Node.js supports the following conditions:
* `"node"` - matched for any Node.js environment. Can be a CommonJS or ES
module file. _This condition should always come after `"import"` or
`"require"`._
* `"development"` - enabled by the `--dev` flag in Node.js, this allows code
paths to be loaded that provide additional debugging information.
* `"default"` - the generic fallback that will always match. Can be a CommonJS
or ES module file. _This condition should always come last._

Expand All @@ -383,6 +385,10 @@ Conditional exports can also be extended to exports subpaths, for example:
"exports": {
".": "./main.js",
"./feature": {
"development": {
"browser": "./feature-browser-dev.js",
"default": "./feature-dev.js"
},
"browser": "./feature-browser.js",
"default": "./feature.js"
}
Expand All @@ -392,7 +398,8 @@ Conditional exports can also be extended to exports subpaths, for example:

Defines a package where `require('pkg/feature')` and `import 'pkg/feature'`
could provide different implementations between the browser and Node.js,
given third-party tool support for a `"browser"` condition.
given third-party tool support for a `"browser"` condition, as well as
loading different code between development and production environments.

#### Nested conditions

Expand Down Expand Up @@ -1471,8 +1478,8 @@ future updates.
In the following algorithms, all subroutine errors are propagated as errors
of these top-level routines unless stated otherwise.

_defaultEnv_ is the conditional environment name priority array,
`["node", "import"]`.
_defaultEnv_ is the conditional environment array, `["node", "import"]`,
including the `"development"` condition if the `--dev` flag is set.

The resolver can throw the following errors:
* _Invalid Module Specifier_: Module specifier is an invalid URL, package name
Expand Down
3 changes: 3 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ The default is
File name of the V8 CPU profile generated with
.Fl -cpu-prof
.
.It Fl -dev
Enables the development mode conditional exports resolution.
.
.It Fl -disable-proto Ns = Ns Ar mode
Disable the `Object.prototype.__proto__` property. If
.Ar mode
Expand Down
11 changes: 11 additions & 0 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const {
loadNativeModule
} = require('internal/modules/cjs/helpers');
const { getOptionValue } = require('internal/options');
const development = getOptionValue('--dev');
const enableSourceMaps = getOptionValue('--enable-source-maps');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
Expand Down Expand Up @@ -575,6 +576,16 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) {
}
for (const p of keys) {
switch (p) {
case 'development':
if (development) {
try {
return resolveExportsTarget(baseUrl, target[p], subpath,
mappingKey);
} catch (e) {
if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw e;
}
}
break;
case 'node':
case 'require':
try {
Expand Down
7 changes: 6 additions & 1 deletion lib/internal/modules/esm/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const { sep } = require('path');

const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const development = getOptionValue('--dev');
const typeFlag = getOptionValue('--input-type');
const { URL, pathToFileURL, fileURLToPath } = require('internal/url');
const {
Expand All @@ -46,7 +47,11 @@ const {
ERR_UNSUPPORTED_ESM_URL_SCHEME,
} = require('internal/errors').codes;

const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import']);
const DEFAULT_CONDITIONS = ObjectFreeze([
'node',
'import',
...development ? ['development'] : []
]);
const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);

function getConditionsSet(conditions) {
Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ DebugOptionsParser::DebugOptionsParser() {
}

EnvironmentOptionsParser::EnvironmentOptionsParser() {
AddOption("--dev",
"experimental development mode",
&EnvironmentOptions::development,
kAllowedInEnvironment);
AddOption("--enable-source-maps",
"experimental Source Map V3 support",
&EnvironmentOptions::enable_source_maps,
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class DebugOptions : public Options {
class EnvironmentOptions : public Options {
public:
bool abort_on_uncaught_exception = false;
bool development = false;
bool enable_source_maps = false;
bool experimental_json_modules = false;
bool experimental_modules = false;
Expand Down
16 changes: 16 additions & 0 deletions test/es-module/test-esm-exports-dev.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import '../common/index.mjs';
import { path } from '../common/fixtures.mjs';
import { strictEqual } from 'assert';
import { spawnSync } from 'child_process';

{
const output = spawnSync(process.execPath, [path('/pkgexports-dev.mjs')]);
console.log(output.stderr.toString());
strictEqual(output.stdout.toString().trim(), 'production');
}

{
const output = spawnSync(process.execPath,
['--dev', path('/pkgexports-dev.mjs')]);
strictEqual(output.stdout.toString().trim(), 'development');
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import {ok, deepStrictEqual} from 'assert';

const dev = process.env.NODE_ENV === 'development';

export async function resolve(specifier, context, defaultResolve) {
ok(Array.isArray(context.conditions), 'loader receives conditions array');
deepStrictEqual([...context.conditions].sort(), ['import', 'node']);
deepStrictEqual(
[...context.conditions].filter(c => c !== 'development').sort(),
['import', 'node']
);
return defaultResolve(specifier, {
...context,
conditions: ['custom-condition', ...context.conditions],
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/node_modules/pkgexports-dev/dev.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/node_modules/pkgexports-dev/dev.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions test/fixtures/node_modules/pkgexports-dev/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/node_modules/pkgexports-dev/prod.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/node_modules/pkgexports-dev/prod.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions test/fixtures/pkgexports-dev.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { fileURLToPath } from 'url';
import { createRequire } from 'module';
import { strictEqual, AssertionError } from 'assert';

const require = createRequire(fileURLToPath(import.meta.url));
const requireVal = require('pkgexports-dev');

(async () => {
const { default: importVal } = await import('pkgexports-dev');
strictEqual(requireVal, importVal);
console.log(importVal);
})();