mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
module: conditional exports with flagged conditions
PR-URL: https://github.com/nodejs/node/pull/29978 Reviewed-By: Jan Krems <jan.krems@gmail.com> Reviewed-By: Myles Borins <myles.borins@gmail.com>
This commit is contained in:
@@ -170,6 +170,15 @@ the ability to import a directory that has an index file.
|
||||
|
||||
Please see [customizing esm specifier resolution][] for example usage.
|
||||
|
||||
### `--experimental-conditional-exports
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Enable experimental support for the `"require"` and `"node"` conditional
|
||||
package export resolutions.
|
||||
See [Conditional Exports][] for more information.
|
||||
|
||||
### `--experimental-json-modules`
|
||||
<!-- YAML
|
||||
added: v12.9.0
|
||||
@@ -1021,6 +1030,7 @@ Node.js options that are allowed are:
|
||||
* `--enable-fips`
|
||||
* `--enable-source-maps`
|
||||
* `--es-module-specifier-resolution`
|
||||
* `--experimental-conditional-exports`
|
||||
* `--experimental-json-modules`
|
||||
* `--experimental-loader`
|
||||
* `--experimental-modules`
|
||||
@@ -1324,3 +1334,4 @@ greater than `4` (its current default value). For more information, see the
|
||||
[libuv threadpool documentation]: http://docs.libuv.org/en/latest/threadpool.html
|
||||
[remote code execution]: https://www.owasp.org/index.php/Code_Injection
|
||||
[context-aware]: addons.html#addons_context_aware_addons
|
||||
[Conditional Exports]: esm.html#esm_conditional_exports
|
||||
|
||||
208
doc/api/esm.md
208
doc/api/esm.md
@@ -260,6 +260,9 @@ that would only be supported in ES module-supporting versions of Node.js (and
|
||||
other runtimes). New packages could be published containing only ES module
|
||||
sources, and would be compatible only with ES module-supporting runtimes.
|
||||
|
||||
To define separate package entry points for use by `require` and by `import`,
|
||||
see [Conditional Exports][].
|
||||
|
||||
### Package Exports
|
||||
|
||||
By default, all subpaths from a package can be imported (`import 'pkg/x.js'`).
|
||||
@@ -313,38 +316,11 @@ If a package has no exports, setting `"exports": false` can be used instead of
|
||||
`"exports": {}` to indicate the package does not intend for submodules to be
|
||||
exposed.
|
||||
|
||||
Exports can also be used to map the main entry point of a package:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
```js
|
||||
// ./node_modules/es-module-package/package.json
|
||||
{
|
||||
"exports": {
|
||||
".": "./main.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
where the "." indicates loading the package without any subpath. Exports will
|
||||
always override any existing `"main"` value for both CommonJS and
|
||||
ES module packages.
|
||||
|
||||
For packages with only a main entry point, an `"exports"` value of just
|
||||
a string is also supported:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
```js
|
||||
// ./node_modules/es-module-package/package.json
|
||||
{
|
||||
"exports": "./main.js"
|
||||
}
|
||||
```
|
||||
|
||||
Any invalid exports entries will be ignored. This includes exports not
|
||||
starting with `"./"` or a missing trailing `"/"` for directory exports.
|
||||
|
||||
Array fallback support is provided for exports, similarly to import maps
|
||||
in order to be forward-compatible with fallback workflows in future:
|
||||
in order to be forwards-compatible with possible fallback workflows in future:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
```js
|
||||
@@ -358,6 +334,137 @@ in order to be forward-compatible with fallback workflows in future:
|
||||
Since `"not:valid"` is not a supported target, `"./submodule.js"` is used
|
||||
instead as the fallback, as if it were the only target.
|
||||
|
||||
Defining a `"."` export will define the main entry point for the package,
|
||||
and will always take precedence over the `"main"` field in the `package.json`.
|
||||
|
||||
This allows defining a different entry point for Node.js versions that support
|
||||
ECMAScript modules and versions that don't, for example:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
```js
|
||||
{
|
||||
"main": "./main-legacy.cjs",
|
||||
"exports": {
|
||||
".": "./main-modern.cjs"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Conditional Exports
|
||||
|
||||
Conditional exports provide a way to map to different paths depending on
|
||||
certain conditions. They are supported for both CommonJS and ES module imports.
|
||||
|
||||
For example, a package that wants to provide different ES module exports for
|
||||
Node.js and the browser can be written:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
```js
|
||||
// ./node_modules/pkg/package.json
|
||||
{
|
||||
"type": "module",
|
||||
"main": "./index.js",
|
||||
"exports": {
|
||||
"./feature": {
|
||||
"browser": "./feature-browser.js",
|
||||
"default": "./feature-default.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When resolving the `"."` export, if no matching target is found, the `"main"`
|
||||
will be used as the final fallback.
|
||||
|
||||
The conditions supported in Node.js are matched in the following order:
|
||||
|
||||
1. `"require"` - matched when the package is loaded via `require()`.
|
||||
_This is currently only supported behind the
|
||||
`--experimental-conditional-exports` flag._
|
||||
2. `"node"` - matched for any Node.js environment. Can be a CommonJS or ES
|
||||
module file. _This is currently only supported behind the
|
||||
`--experimental-conditional-exports` flag._
|
||||
3. `"default"` - the generic fallback that will always match if no other
|
||||
more specific condition is matched first. Can be a CommonJS or ES module
|
||||
file.
|
||||
|
||||
Using the `"require"` condition it is possible to define a package that will
|
||||
have a different exported value for CommonJS and ES modules, which can be a
|
||||
hazard in that it can result in having two separate instances of the same
|
||||
package in use in an application, which can cause a number of bugs.
|
||||
|
||||
Other conditions such as `"browser"`, `"electron"`, `"deno"`, `"react-native"`,
|
||||
etc. could be defined in other runtimes or tools.
|
||||
|
||||
#### Exports Sugar
|
||||
|
||||
If the `"."` export is the only export, the `"exports"` field provides sugar
|
||||
for this case being the direct `"exports"` field value.
|
||||
|
||||
If the `"."` export has a fallback array or string value, then the `"exports"`
|
||||
field can be set to this value directly.
|
||||
|
||||
<!-- eslint-skip -->
|
||||
```js
|
||||
{
|
||||
"exports": {
|
||||
".": "./main.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
can be written:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
```js
|
||||
{
|
||||
"exports": "./main.js"
|
||||
}
|
||||
```
|
||||
|
||||
When using conditional exports, the rule is that all keys in the object mapping
|
||||
must not start with a `"."` otherwise they would be indistinguishable from
|
||||
exports subpaths.
|
||||
|
||||
<!-- eslint-skip -->
|
||||
```js
|
||||
{
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./main.cjs",
|
||||
"default": "./main.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
can be written:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
```js
|
||||
{
|
||||
"exports": {
|
||||
"require": "./main.cjs",
|
||||
"default": "./main.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If writing any exports value that mixes up these two forms, an error will be
|
||||
thrown:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
```js
|
||||
{
|
||||
// Throws on resolution!
|
||||
"exports": {
|
||||
"./feature": "./lib/feature.js",
|
||||
"require": "./main.cjs",
|
||||
"default": "./main.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## <code>import</code> Specifiers
|
||||
|
||||
### Terminology
|
||||
@@ -806,6 +913,9 @@ of these top-level routines unless stated otherwise.
|
||||
|
||||
_isMain_ is **true** when resolving the Node.js application entry point.
|
||||
|
||||
_defaultEnv_ is the conditional environment name priority array,
|
||||
`["node", "default"]`.
|
||||
|
||||
<details>
|
||||
<summary>Resolver algorithm specification</summary>
|
||||
|
||||
@@ -905,14 +1015,16 @@ _isMain_ is **true** when resolving the Node.js application entry point.
|
||||
> 1. If _pjson_ is **null**, then
|
||||
> 1. Throw a _Module Not Found_ error.
|
||||
> 1. If _pjson.exports_ is not **null** or **undefined**, then
|
||||
> 1. If _pjson.exports_ is a String or Array, then
|
||||
> 1. If _exports_ is an Object with both a key starting with _"."_ and a key
|
||||
> not starting with _"."_, throw a "Invalid Package Configuration" error.
|
||||
> 1. If _pjson.exports_ is a String or Array, or an Object containing no
|
||||
> keys starting with _"."_, then
|
||||
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
|
||||
> _pjson.exports_, "")_.
|
||||
> 1. If _pjson.exports is an Object, then
|
||||
> 1. If _pjson.exports_ contains a _"."_ property, then
|
||||
> 1. Let _mainExport_ be the _"."_ property in _pjson.exports_.
|
||||
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
|
||||
> _mainExport_, "")_.
|
||||
> _pjson.exports_, _""_).
|
||||
> 1. If _pjson.exports_ is an Object containing a _"."_ property, then
|
||||
> 1. Let _mainExport_ be the _"."_ property in _pjson.exports_.
|
||||
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
|
||||
> _mainExport_, _""_).
|
||||
> 1. If _pjson.main_ is a String, then
|
||||
> 1. Let _resolvedMain_ be the URL resolution of _packageURL_, "/", and
|
||||
> _pjson.main_.
|
||||
@@ -926,13 +1038,14 @@ _isMain_ is **true** when resolving the Node.js application entry point.
|
||||
> 1. Return _legacyMainURL_.
|
||||
|
||||
**PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _packagePath_, _exports_)
|
||||
|
||||
> 1. If _exports_ is an Object, then
|
||||
> 1. If _exports_ is an Object with both a key starting with _"."_ and a key not
|
||||
> starting with _"."_, throw an "Invalid Package Configuration" error.
|
||||
> 1. If _exports_ is an Object and all keys of _exports_ start with _"."_, then
|
||||
> 1. Set _packagePath_ to _"./"_ concatenated with _packagePath_.
|
||||
> 1. If _packagePath_ is a key of _exports_, then
|
||||
> 1. Let _target_ be the value of _exports\[packagePath\]_.
|
||||
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_,
|
||||
> _""_).
|
||||
> _""_, _defaultEnv_).
|
||||
> 1. Let _directoryKeys_ be the list of keys of _exports_ ending in
|
||||
> _"/"_, sorted by length descending.
|
||||
> 1. For each key _directory_ in _directoryKeys_, do
|
||||
@@ -941,10 +1054,10 @@ _isMain_ is **true** when resolving the Node.js application entry point.
|
||||
> 1. Let _subpath_ be the substring of _target_ starting at the index
|
||||
> of the length of _directory_.
|
||||
> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_,
|
||||
> _subpath_).
|
||||
> _subpath_, _defaultEnv_).
|
||||
> 1. Throw a _Module Not Found_ error.
|
||||
|
||||
**PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_)
|
||||
**PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _env_)
|
||||
|
||||
> 1. If _target_ is a String, then
|
||||
> 1. If _target_ does not start with _"./"_, throw a _Module Not Found_
|
||||
@@ -960,12 +1073,20 @@ _isMain_ is **true** when resolving the Node.js application entry point.
|
||||
> _subpath_ and _resolvedTarget_.
|
||||
> 1. If _resolved_ is contained in _resolvedTarget_, then
|
||||
> 1. Return _resolved_.
|
||||
> 1. Otherwise, if _target_ is a non-null Object, then
|
||||
> 1. If _target_ has an object key matching one of the names in _env_, then
|
||||
> 1. Let _targetValue_ be the corresponding value of the first object key
|
||||
> of _target_ in _env_.
|
||||
> 1. Let _resolved_ be the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**
|
||||
> (_packageURL_, _targetValue_, _subpath_, _env_).
|
||||
> 1. Assert: _resolved_ is a String.
|
||||
> 1. Return _resolved_.
|
||||
> 1. Otherwise, if _target_ is an Array, then
|
||||
> 1. For each item _targetValue_ in _target_, do
|
||||
> 1. If _targetValue_ is not a String, continue the loop.
|
||||
> 1. If _targetValue_ is an Array, continue the loop.
|
||||
> 1. Let _resolved_ be the result of
|
||||
> **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _targetValue_,
|
||||
> _subpath_), continuing the loop on abrupt completion.
|
||||
> _subpath_, _env_), continuing the loop on abrupt completion.
|
||||
> 1. Assert: _resolved_ is a String.
|
||||
> 1. Return _resolved_.
|
||||
> 1. Throw a _Module Not Found_ error.
|
||||
@@ -1033,6 +1154,7 @@ success!
|
||||
```
|
||||
|
||||
[CommonJS]: modules.html
|
||||
[Conditional Exports]: #esm_conditional_exports
|
||||
[ECMAScript-modules implementation]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md
|
||||
[ES Module Integration Proposal for Web Assembly]: https://github.com/webassembly/esm-integration
|
||||
[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
|
||||
@@ -1045,7 +1167,7 @@ success!
|
||||
[`import`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
|
||||
[`module.createRequire()`]: modules.html#modules_module_createrequire_filename
|
||||
[`module.syncBuiltinESMExports()`]: modules.html#modules_module_syncbuiltinesmexports
|
||||
[dynamic instantiate hook]: #esm_dynamic_instantiate_hook
|
||||
[package exports]: #esm_package_exports
|
||||
[dynamic instantiate hook]: #esm_dynamic_instantiate_hook
|
||||
[special scheme]: https://url.spec.whatwg.org/#special-scheme
|
||||
[the official standard format]: https://tc39.github.io/ecma262/#sec-modules
|
||||
|
||||
@@ -232,12 +232,17 @@ RESOLVE_BARE_SPECIFIER(DIR, X)
|
||||
2. If X matches this pattern and DIR/name/package.json is a file:
|
||||
a. Parse DIR/name/package.json, and look for "exports" field.
|
||||
b. If "exports" is null or undefined, GOTO 3.
|
||||
c. Find the longest key in "exports" that the subpath starts with.
|
||||
d. If no such key can be found, throw "not found".
|
||||
e. let RESOLVED_URL =
|
||||
c. If "exports" is an object with some keys starting with "." and some keys
|
||||
not starting with ".", throw "invalid config".
|
||||
c. If "exports" is a string, or object with no keys starting with ".", treat
|
||||
it as having that value as its "." object property.
|
||||
d. If subpath is "." and "exports" does not have a "." entry, GOTO 3.
|
||||
e. Find the longest key in "exports" that the subpath starts with.
|
||||
f. If no such key can be found, throw "not found".
|
||||
g. let RESOLVED_URL =
|
||||
PACKAGE_EXPORTS_TARGET_RESOLVE(pathToFileURL(DIR/name), exports[key],
|
||||
subpath.slice(key.length)), as defined in the esm resolver.
|
||||
f. return fileURLToPath(RESOLVED_URL)
|
||||
h. return fileURLToPath(RESOLVED_URL)
|
||||
3. return DIR/X
|
||||
```
|
||||
|
||||
|
||||
@@ -113,6 +113,9 @@ Requires Node.js to be built with
|
||||
.It Fl -es-module-specifier-resolution
|
||||
Select extension resolution algorithm for ES Modules; either 'explicit' (default) or 'node'
|
||||
.
|
||||
.It Fl -experimental-conditional-exports
|
||||
Enable experimental support for "require" and "node" conditional export targets.
|
||||
.
|
||||
.It Fl -experimental-json-modules
|
||||
Enable experimental JSON interop support for the ES Module loader.
|
||||
.
|
||||
|
||||
@@ -981,7 +981,7 @@ E('ERR_INVALID_OPT_VALUE', (name, value) =>
|
||||
E('ERR_INVALID_OPT_VALUE_ENCODING',
|
||||
'The value "%s" is invalid for option "encoding"', TypeError);
|
||||
E('ERR_INVALID_PACKAGE_CONFIG',
|
||||
'Invalid package config in \'%s\' imported from %s', Error);
|
||||
'Invalid package config for \'%s\', %s', Error);
|
||||
E('ERR_INVALID_PERFORMANCE_MARK',
|
||||
'The "%s" performance mark has not been set', Error);
|
||||
E('ERR_INVALID_PROTOCOL',
|
||||
|
||||
@@ -59,6 +59,8 @@ const preserveSymlinks = getOptionValue('--preserve-symlinks');
|
||||
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
|
||||
const experimentalModules = getOptionValue('--experimental-modules');
|
||||
const experimentalSelf = getOptionValue('--experimental-resolve-self');
|
||||
const experimentalConditionalExports =
|
||||
getOptionValue('--experimental-conditional-exports');
|
||||
const manifest = getOptionValue('--experimental-policy') ?
|
||||
require('internal/process/policy').manifest :
|
||||
null;
|
||||
@@ -67,6 +69,7 @@ const { compileFunction } = internalBinding('contextify');
|
||||
const {
|
||||
ERR_INVALID_ARG_VALUE,
|
||||
ERR_INVALID_OPT_VALUE,
|
||||
ERR_INVALID_PACKAGE_CONFIG,
|
||||
ERR_REQUIRE_ESM
|
||||
} = require('internal/errors').codes;
|
||||
const { validateString } = require('internal/validators');
|
||||
@@ -441,7 +444,6 @@ function trySelf(paths, exts, isMain, trailingSlash, request) {
|
||||
if (expansion) {
|
||||
// Use exports
|
||||
const fromExports = applyExports(basePath, expansion);
|
||||
if (!fromExports) return false;
|
||||
return resolveBasePath(fromExports, exts, isMain, trailingSlash, request);
|
||||
} else {
|
||||
// Use main field
|
||||
@@ -449,17 +451,51 @@ function trySelf(paths, exts, isMain, trailingSlash, request) {
|
||||
}
|
||||
}
|
||||
|
||||
function isConditionalDotExportSugar(exports, basePath) {
|
||||
if (typeof exports === 'string')
|
||||
return true;
|
||||
if (Array.isArray(exports))
|
||||
return true;
|
||||
if (typeof exports !== 'object')
|
||||
return false;
|
||||
let isConditional = false;
|
||||
let firstCheck = true;
|
||||
for (const key of Object.keys(exports)) {
|
||||
const curIsConditional = key[0] !== '.';
|
||||
if (firstCheck) {
|
||||
firstCheck = false;
|
||||
isConditional = curIsConditional;
|
||||
} else if (isConditional !== curIsConditional) {
|
||||
throw new ERR_INVALID_PACKAGE_CONFIG(basePath, '"exports" cannot ' +
|
||||
'contain some keys starting with \'.\' and some not. The exports ' +
|
||||
'object must either be an object of package subpath keys or an ' +
|
||||
'object of main entry condition name keys only.');
|
||||
}
|
||||
}
|
||||
return isConditional;
|
||||
}
|
||||
|
||||
function applyExports(basePath, expansion) {
|
||||
const pkgExports = readPackageExports(basePath);
|
||||
const mappingKey = `.${expansion}`;
|
||||
|
||||
if (typeof pkgExports === 'object' && pkgExports !== null) {
|
||||
let pkgExports = readPackageExports(basePath);
|
||||
if (pkgExports === undefined || pkgExports === null || !experimentalModules)
|
||||
return path.resolve(basePath, mappingKey);
|
||||
|
||||
if (isConditionalDotExportSugar(pkgExports, basePath))
|
||||
pkgExports = { '.': pkgExports };
|
||||
|
||||
if (typeof pkgExports === 'object') {
|
||||
if (ObjectPrototype.hasOwnProperty(pkgExports, mappingKey)) {
|
||||
const mapping = pkgExports[mappingKey];
|
||||
return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping, '',
|
||||
basePath, mappingKey);
|
||||
}
|
||||
|
||||
// Fallback to CJS main lookup when no main export is defined
|
||||
if (mappingKey === '.')
|
||||
return basePath;
|
||||
|
||||
let dirMatch = '';
|
||||
for (const candidateKey of Object.keys(pkgExports)) {
|
||||
if (candidateKey[candidateKey.length - 1] !== '/') continue;
|
||||
@@ -476,19 +512,15 @@ function applyExports(basePath, expansion) {
|
||||
subpath, basePath, mappingKey);
|
||||
}
|
||||
}
|
||||
if (mappingKey === '.' && typeof pkgExports === 'string') {
|
||||
return resolveExportsTarget(pathToFileURL(basePath + '/'), pkgExports,
|
||||
'', basePath, mappingKey);
|
||||
}
|
||||
if (pkgExports != null) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const e = new Error(`Package exports for '${basePath}' do not define ` +
|
||||
`a '${mappingKey}' subpath`);
|
||||
e.code = 'MODULE_NOT_FOUND';
|
||||
throw e;
|
||||
}
|
||||
// Fallback to CJS main lookup when no main export is defined
|
||||
if (mappingKey === '.')
|
||||
return basePath;
|
||||
|
||||
return path.resolve(basePath, mappingKey);
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const e = new Error(`Package exports for '${basePath}' do not define ` +
|
||||
`a '${mappingKey}' subpath`);
|
||||
e.code = 'MODULE_NOT_FOUND';
|
||||
throw e;
|
||||
}
|
||||
|
||||
// This only applies to requests of a specific form:
|
||||
@@ -532,7 +564,7 @@ function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) {
|
||||
}
|
||||
} else if (Array.isArray(target)) {
|
||||
for (const targetValue of target) {
|
||||
if (typeof targetValue !== 'string') continue;
|
||||
if (Array.isArray(targetValue)) continue;
|
||||
try {
|
||||
return resolveExportsTarget(pkgPath, targetValue, subpath, basePath,
|
||||
mappingKey);
|
||||
@@ -540,10 +572,43 @@ function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) {
|
||||
if (e.code !== 'MODULE_NOT_FOUND') throw e;
|
||||
}
|
||||
}
|
||||
} else if (typeof target === 'object' && target !== null) {
|
||||
if (experimentalConditionalExports &&
|
||||
ObjectPrototype.hasOwnProperty(target, 'require')) {
|
||||
try {
|
||||
return resolveExportsTarget(pkgPath, target.require, subpath,
|
||||
basePath, mappingKey);
|
||||
} catch (e) {
|
||||
if (e.code !== 'MODULE_NOT_FOUND') throw e;
|
||||
}
|
||||
}
|
||||
if (experimentalConditionalExports &&
|
||||
ObjectPrototype.hasOwnProperty(target, 'node')) {
|
||||
try {
|
||||
return resolveExportsTarget(pkgPath, target.node, subpath,
|
||||
basePath, mappingKey);
|
||||
} catch (e) {
|
||||
if (e.code !== 'MODULE_NOT_FOUND') throw e;
|
||||
}
|
||||
}
|
||||
if (ObjectPrototype.hasOwnProperty(target, 'default')) {
|
||||
try {
|
||||
return resolveExportsTarget(pkgPath, target.default, subpath,
|
||||
basePath, mappingKey);
|
||||
} catch (e) {
|
||||
if (e.code !== 'MODULE_NOT_FOUND') throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
let e;
|
||||
if (mappingKey !== '.') {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
e = new Error(`Package exports for '${basePath}' do not define a ` +
|
||||
`valid '${mappingKey}' target${subpath ? ' for ' + subpath : ''}`);
|
||||
} else {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
e = new Error(`No valid exports main found for '${basePath}'`);
|
||||
}
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const e = new Error(`Package exports for '${basePath}' do not define a ` +
|
||||
`valid '${mappingKey}' target${subpath ? 'for ' + subpath : ''}`);
|
||||
e.code = 'MODULE_NOT_FOUND';
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -200,6 +200,7 @@ constexpr size_t kFsStatsBufferLength =
|
||||
V(crypto_rsa_pss_string, "rsa-pss") \
|
||||
V(cwd_string, "cwd") \
|
||||
V(data_string, "data") \
|
||||
V(default_string, "default") \
|
||||
V(dest_string, "dest") \
|
||||
V(destroyed_string, "destroyed") \
|
||||
V(detached_string, "detached") \
|
||||
@@ -214,6 +215,7 @@ constexpr size_t kFsStatsBufferLength =
|
||||
V(dns_srv_string, "SRV") \
|
||||
V(dns_txt_string, "TXT") \
|
||||
V(done_string, "done") \
|
||||
V(dot_string, ".") \
|
||||
V(duration_string, "duration") \
|
||||
V(emit_warning_string, "emitWarning") \
|
||||
V(empty_object_string, "{}") \
|
||||
@@ -278,6 +280,7 @@ constexpr size_t kFsStatsBufferLength =
|
||||
V(netmask_string, "netmask") \
|
||||
V(next_string, "next") \
|
||||
V(nistcurve_string, "nistCurve") \
|
||||
V(node_string, "node") \
|
||||
V(nsname_string, "nsname") \
|
||||
V(ocsp_request_string, "OCSPRequest") \
|
||||
V(oncertcb_string, "oncertcb") \
|
||||
|
||||
@@ -835,10 +835,16 @@ void ThrowExportsInvalid(Environment* env,
|
||||
const std::string& target,
|
||||
const URL& pjson_url,
|
||||
const URL& base) {
|
||||
const std::string msg = "Cannot resolve package exports target '" + target +
|
||||
"' matched for '" + subpath + "' in " + pjson_url.ToFilePath() +
|
||||
", imported from " + base.ToFilePath();
|
||||
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
|
||||
if (subpath.length()) {
|
||||
const std::string msg = "Cannot resolve package exports target '" + target +
|
||||
"' matched for '" + subpath + "' in " + pjson_url.ToFilePath() +
|
||||
", imported from " + base.ToFilePath();
|
||||
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
|
||||
} else {
|
||||
const std::string msg = "Cannot resolve package main '" + target + "' in" +
|
||||
pjson_url.ToFilePath() + ", imported from " + base.ToFilePath();
|
||||
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void ThrowExportsInvalid(Environment* env,
|
||||
@@ -857,13 +863,13 @@ void ThrowExportsInvalid(Environment* env,
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<URL> ResolveExportsTarget(Environment* env,
|
||||
const std::string& target,
|
||||
const std::string& subpath,
|
||||
const std::string& match,
|
||||
const URL& pjson_url,
|
||||
const URL& base,
|
||||
bool throw_invalid = true) {
|
||||
Maybe<URL> ResolveExportsTargetString(Environment* env,
|
||||
const std::string& target,
|
||||
const std::string& subpath,
|
||||
const std::string& match,
|
||||
const URL& pjson_url,
|
||||
const URL& base,
|
||||
bool throw_invalid = true) {
|
||||
if (target.substr(0, 2) != "./") {
|
||||
if (throw_invalid) {
|
||||
ThrowExportsInvalid(env, match, target, pjson_url, base);
|
||||
@@ -901,68 +907,142 @@ Maybe<URL> ResolveExportsTarget(Environment* env,
|
||||
return Just(subpath_resolved);
|
||||
}
|
||||
|
||||
Maybe<URL> ResolveExportsTarget(Environment* env,
|
||||
const URL& pjson_url,
|
||||
Local<Value> target,
|
||||
const std::string& subpath,
|
||||
const std::string& pkg_subpath,
|
||||
const URL& base,
|
||||
bool throw_invalid = true) {
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = env->context();
|
||||
if (target->IsString()) {
|
||||
Utf8Value target_utf8(isolate, target.As<v8::String>());
|
||||
std::string target_str(*target_utf8, target_utf8.length());
|
||||
Maybe<URL> resolved = ResolveExportsTargetString(env, target_str, subpath,
|
||||
pkg_subpath, pjson_url, base, throw_invalid);
|
||||
if (resolved.IsNothing()) {
|
||||
return Nothing<URL>();
|
||||
}
|
||||
return FinalizeResolution(env, resolved.FromJust(), base);
|
||||
} else if (target->IsArray()) {
|
||||
Local<Array> target_arr = target.As<Array>();
|
||||
const uint32_t length = target_arr->Length();
|
||||
if (length == 0) {
|
||||
if (throw_invalid) {
|
||||
ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base);
|
||||
}
|
||||
return Nothing<URL>();
|
||||
}
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
auto target_item = target_arr->Get(context, i).ToLocalChecked();
|
||||
if (!target_item->IsArray()) {
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url,
|
||||
target_item, subpath, pkg_subpath, base, false);
|
||||
if (resolved.IsNothing()) continue;
|
||||
return FinalizeResolution(env, resolved.FromJust(), base);
|
||||
}
|
||||
}
|
||||
if (throw_invalid) {
|
||||
auto invalid = target_arr->Get(context, length - 1).ToLocalChecked();
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, invalid,
|
||||
subpath, pkg_subpath, base, true);
|
||||
CHECK(resolved.IsNothing());
|
||||
}
|
||||
return Nothing<URL>();
|
||||
} else if (target->IsObject()) {
|
||||
Local<Object> target_obj = target.As<Object>();
|
||||
bool matched = false;
|
||||
Local<Value> conditionalTarget;
|
||||
if (env->options()->experimental_conditional_exports &&
|
||||
target_obj->HasOwnProperty(context, env->node_string()).FromJust()) {
|
||||
matched = true;
|
||||
conditionalTarget =
|
||||
target_obj->Get(context, env->node_string()).ToLocalChecked();
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url,
|
||||
conditionalTarget, subpath, pkg_subpath, base, false);
|
||||
if (!resolved.IsNothing()) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
if (target_obj->HasOwnProperty(context, env->default_string()).FromJust()) {
|
||||
matched = true;
|
||||
conditionalTarget =
|
||||
target_obj->Get(context, env->default_string()).ToLocalChecked();
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url,
|
||||
conditionalTarget, subpath, pkg_subpath, base, false);
|
||||
if (!resolved.IsNothing()) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
if (matched && throw_invalid) {
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url,
|
||||
conditionalTarget, subpath, pkg_subpath, base, true);
|
||||
CHECK(resolved.IsNothing());
|
||||
return Nothing<URL>();
|
||||
}
|
||||
}
|
||||
if (throw_invalid) {
|
||||
ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base);
|
||||
}
|
||||
return Nothing<URL>();
|
||||
}
|
||||
|
||||
Maybe<bool> IsConditionalExportsMainSugar(Environment* env,
|
||||
Local<Value> exports,
|
||||
const URL& pjson_url,
|
||||
const URL& base) {
|
||||
if (exports->IsString() || exports->IsArray()) return Just(true);
|
||||
if (!exports->IsObject()) return Just(false);
|
||||
Local<Context> context = env->context();
|
||||
Local<Object> exports_obj = exports.As<Object>();
|
||||
Local<Array> keys =
|
||||
exports_obj->GetOwnPropertyNames(context).ToLocalChecked();
|
||||
bool isConditionalSugar = false;
|
||||
for (uint32_t i = 0; i < keys->Length(); ++i) {
|
||||
Local<String> key = keys->Get(context, i).ToLocalChecked().As<String>();
|
||||
Utf8Value key_utf8(env->isolate(), key);
|
||||
bool curIsConditionalSugar = key_utf8.length() == 0 || key_utf8[0] != '.';
|
||||
if (i == 0) {
|
||||
isConditionalSugar = curIsConditionalSugar;
|
||||
} else if (isConditionalSugar != curIsConditionalSugar) {
|
||||
const std::string msg = "Cannot resolve package exports in " +
|
||||
pjson_url.ToFilePath() + ", imported from " + base.ToFilePath() + ". " +
|
||||
"\"exports\" cannot contain some keys starting with '.' and some not." +
|
||||
" The exports object must either be an object of package subpath keys" +
|
||||
" or an object of main entry condition name keys only.";
|
||||
node::THROW_ERR_INVALID_PACKAGE_CONFIG(env, msg.c_str());
|
||||
return Nothing<bool>();
|
||||
}
|
||||
}
|
||||
return Just(isConditionalSugar);
|
||||
}
|
||||
|
||||
Maybe<URL> PackageMainResolve(Environment* env,
|
||||
const URL& pjson_url,
|
||||
const PackageConfig& pcfg,
|
||||
const URL& base) {
|
||||
if (pcfg.exists == Exists::Yes) {
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = env->context();
|
||||
|
||||
if (!pcfg.exports.IsEmpty()) {
|
||||
Local<Value> exports = pcfg.exports.Get(isolate);
|
||||
if (exports->IsString() || exports->IsObject() || exports->IsArray()) {
|
||||
Local<Value> target;
|
||||
if (!exports->IsObject()) {
|
||||
target = exports;
|
||||
} else {
|
||||
Local<Object> exports_obj = exports.As<Object>();
|
||||
Local<String> dot_string = String::NewFromUtf8(env->isolate(), ".",
|
||||
v8::NewStringType::kNormal).ToLocalChecked();
|
||||
target =
|
||||
exports_obj->Get(env->context(), dot_string).ToLocalChecked();
|
||||
}
|
||||
if (target->IsString()) {
|
||||
Utf8Value target_utf8(isolate, target.As<v8::String>());
|
||||
std::string target(*target_utf8, target_utf8.length());
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, target, "", ".",
|
||||
pjson_url, base);
|
||||
if (resolved.IsNothing()) {
|
||||
ThrowExportsInvalid(env, ".", target, pjson_url, base);
|
||||
return Nothing<URL>();
|
||||
}
|
||||
return FinalizeResolution(env, resolved.FromJust(), base);
|
||||
} else if (target->IsArray()) {
|
||||
Local<Array> target_arr = target.As<Array>();
|
||||
const uint32_t length = target_arr->Length();
|
||||
if (length == 0) {
|
||||
ThrowExportsInvalid(env, ".", target, pjson_url, base);
|
||||
return Nothing<URL>();
|
||||
}
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
auto target_item = target_arr->Get(context, i).ToLocalChecked();
|
||||
if (target_item->IsString()) {
|
||||
Utf8Value target_utf8(isolate, target_item.As<v8::String>());
|
||||
std::string target_str(*target_utf8, target_utf8.length());
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, target_str, "",
|
||||
".", pjson_url, base, false);
|
||||
if (resolved.IsNothing()) continue;
|
||||
return FinalizeResolution(env, resolved.FromJust(), base);
|
||||
}
|
||||
}
|
||||
auto invalid = target_arr->Get(context, length - 1).ToLocalChecked();
|
||||
if (!invalid->IsString()) {
|
||||
ThrowExportsInvalid(env, ".", invalid, pjson_url, base);
|
||||
return Nothing<URL>();
|
||||
}
|
||||
Utf8Value invalid_utf8(isolate, invalid.As<v8::String>());
|
||||
std::string invalid_str(*invalid_utf8, invalid_utf8.length());
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, invalid_str, "",
|
||||
".", pjson_url, base);
|
||||
CHECK(resolved.IsNothing());
|
||||
return Nothing<URL>();
|
||||
} else {
|
||||
ThrowExportsInvalid(env, ".", target, pjson_url, base);
|
||||
return Nothing<URL>();
|
||||
Maybe<bool> isConditionalExportsMainSugar =
|
||||
IsConditionalExportsMainSugar(env, exports, pjson_url, base);
|
||||
if (isConditionalExportsMainSugar.IsNothing())
|
||||
return Nothing<URL>();
|
||||
if (isConditionalExportsMainSugar.FromJust()) {
|
||||
return ResolveExportsTarget(env, pjson_url, exports, "", "", base,
|
||||
true);
|
||||
} else if (exports->IsObject()) {
|
||||
Local<Object> exports_obj = exports.As<Object>();
|
||||
if (exports_obj->HasOwnProperty(env->context(), env->dot_string())
|
||||
.FromJust()) {
|
||||
Local<Value> target =
|
||||
exports_obj->Get(env->context(), env->dot_string())
|
||||
.ToLocalChecked();
|
||||
return ResolveExportsTarget(env, pjson_url, target, "", "", base,
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1002,7 +1082,11 @@ Maybe<URL> PackageExportsResolve(Environment* env,
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = env->context();
|
||||
Local<Value> exports = pcfg.exports.Get(isolate);
|
||||
if (!exports->IsObject()) {
|
||||
Maybe<bool> isConditionalExportsMainSugar =
|
||||
IsConditionalExportsMainSugar(env, exports, pjson_url, base);
|
||||
if (isConditionalExportsMainSugar.IsNothing())
|
||||
return Nothing<URL>();
|
||||
if (!exports->IsObject() || isConditionalExportsMainSugar.FromJust()) {
|
||||
ThrowExportsNotFound(env, pkg_subpath, pjson_url, base);
|
||||
return Nothing<URL>();
|
||||
}
|
||||
@@ -1012,49 +1096,12 @@ Maybe<URL> PackageExportsResolve(Environment* env,
|
||||
|
||||
if (exports_obj->HasOwnProperty(context, subpath).FromJust()) {
|
||||
Local<Value> target = exports_obj->Get(context, subpath).ToLocalChecked();
|
||||
if (target->IsString()) {
|
||||
Utf8Value target_utf8(isolate, target.As<v8::String>());
|
||||
std::string target_str(*target_utf8, target_utf8.length());
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, target_str, "",
|
||||
pkg_subpath, pjson_url, base);
|
||||
if (resolved.IsNothing()) {
|
||||
ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base);
|
||||
return Nothing<URL>();
|
||||
}
|
||||
return FinalizeResolution(env, resolved.FromJust(), base);
|
||||
} else if (target->IsArray()) {
|
||||
Local<Array> target_arr = target.As<Array>();
|
||||
const uint32_t length = target_arr->Length();
|
||||
if (length == 0) {
|
||||
ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base);
|
||||
return Nothing<URL>();
|
||||
}
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
auto target_item = target_arr->Get(context, i).ToLocalChecked();
|
||||
if (target_item->IsString()) {
|
||||
Utf8Value target_utf8(isolate, target_item.As<v8::String>());
|
||||
std::string target(*target_utf8, target_utf8.length());
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, target, "",
|
||||
pkg_subpath, pjson_url, base, false);
|
||||
if (resolved.IsNothing()) continue;
|
||||
return FinalizeResolution(env, resolved.FromJust(), base);
|
||||
}
|
||||
}
|
||||
auto invalid = target_arr->Get(context, length - 1).ToLocalChecked();
|
||||
if (!invalid->IsString()) {
|
||||
ThrowExportsInvalid(env, pkg_subpath, invalid, pjson_url, base);
|
||||
return Nothing<URL>();
|
||||
}
|
||||
Utf8Value invalid_utf8(isolate, invalid.As<v8::String>());
|
||||
std::string invalid_str(*invalid_utf8, invalid_utf8.length());
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, invalid_str, "",
|
||||
pkg_subpath, pjson_url, base);
|
||||
CHECK(resolved.IsNothing());
|
||||
return Nothing<URL>();
|
||||
} else {
|
||||
ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base);
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, target, "",
|
||||
pkg_subpath, base);
|
||||
if (resolved.IsNothing()) {
|
||||
return Nothing<URL>();
|
||||
}
|
||||
return FinalizeResolution(env, resolved.FromJust(), base);
|
||||
}
|
||||
|
||||
Local<String> best_match;
|
||||
@@ -1076,49 +1123,13 @@ Maybe<URL> PackageExportsResolve(Environment* env,
|
||||
if (best_match_str.length() > 0) {
|
||||
auto target = exports_obj->Get(context, best_match).ToLocalChecked();
|
||||
std::string subpath = pkg_subpath.substr(best_match_str.length());
|
||||
if (target->IsString()) {
|
||||
Utf8Value target_utf8(isolate, target.As<v8::String>());
|
||||
std::string target(*target_utf8, target_utf8.length());
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, target, subpath,
|
||||
pkg_subpath, pjson_url, base);
|
||||
if (resolved.IsNothing()) {
|
||||
ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base);
|
||||
return Nothing<URL>();
|
||||
}
|
||||
return FinalizeResolution(env, URL(subpath, resolved.FromJust()), base);
|
||||
} else if (target->IsArray()) {
|
||||
Local<Array> target_arr = target.As<Array>();
|
||||
const uint32_t length = target_arr->Length();
|
||||
if (length == 0) {
|
||||
ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base);
|
||||
return Nothing<URL>();
|
||||
}
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
auto target_item = target_arr->Get(context, i).ToLocalChecked();
|
||||
if (target_item->IsString()) {
|
||||
Utf8Value target_utf8(isolate, target_item.As<v8::String>());
|
||||
std::string target_str(*target_utf8, target_utf8.length());
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, target_str, subpath,
|
||||
pkg_subpath, pjson_url, base, false);
|
||||
if (resolved.IsNothing()) continue;
|
||||
return FinalizeResolution(env, resolved.FromJust(), base);
|
||||
}
|
||||
}
|
||||
auto invalid = target_arr->Get(context, length - 1).ToLocalChecked();
|
||||
if (!invalid->IsString()) {
|
||||
ThrowExportsInvalid(env, pkg_subpath, invalid, pjson_url, base);
|
||||
return Nothing<URL>();
|
||||
}
|
||||
Utf8Value invalid_utf8(isolate, invalid.As<v8::String>());
|
||||
std::string invalid_str(*invalid_utf8, invalid_utf8.length());
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, invalid_str, subpath,
|
||||
pkg_subpath, pjson_url, base);
|
||||
CHECK(resolved.IsNothing());
|
||||
return Nothing<URL>();
|
||||
} else {
|
||||
ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base);
|
||||
|
||||
Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, target, subpath,
|
||||
pkg_subpath, base);
|
||||
if (resolved.IsNothing()) {
|
||||
return Nothing<URL>();
|
||||
}
|
||||
return FinalizeResolution(env, resolved.FromJust(), base);
|
||||
}
|
||||
|
||||
ThrowExportsNotFound(env, pkg_subpath, pjson_url, base);
|
||||
|
||||
@@ -331,6 +331,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
||||
"experimental ES Module support and caching modules",
|
||||
&EnvironmentOptions::experimental_modules,
|
||||
kAllowedInEnvironment);
|
||||
AddOption("--experimental-conditional-exports",
|
||||
"experimental support for conditional exports targets",
|
||||
&EnvironmentOptions::experimental_conditional_exports,
|
||||
kAllowedInEnvironment);
|
||||
AddOption("--experimental-resolve-self",
|
||||
"experimental support for require/import of the current package",
|
||||
&EnvironmentOptions::experimental_resolve_self,
|
||||
|
||||
@@ -101,6 +101,7 @@ class EnvironmentOptions : public Options {
|
||||
public:
|
||||
bool abort_on_uncaught_exception = false;
|
||||
bool enable_source_maps = false;
|
||||
bool experimental_conditional_exports = false;
|
||||
bool experimental_json_modules = false;
|
||||
bool experimental_modules = false;
|
||||
bool experimental_resolve_self = false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Flags: --experimental-modules --experimental-resolve-self
|
||||
// Flags: --experimental-modules --experimental-resolve-self --experimental-conditional-exports
|
||||
|
||||
import { mustCall } from '../common/index.mjs';
|
||||
import { ok, deepStrictEqual, strictEqual } from 'assert';
|
||||
@@ -23,7 +23,16 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
|
||||
['pkgexports/fallbackfile', { default: 'asdf' }],
|
||||
// Dot main
|
||||
['pkgexports', { default: 'asdf' }],
|
||||
// Conditional split for require
|
||||
['pkgexports/condition', isRequire ? { default: 'encoded path' } :
|
||||
{ default: 'asdf' }],
|
||||
// String exports sugar
|
||||
['pkgexports-sugar', { default: 'main' }],
|
||||
// Conditional object exports sugar
|
||||
['pkgexports-sugar2', isRequire ? { default: 'not-exported' } :
|
||||
{ default: 'main' }]
|
||||
]);
|
||||
|
||||
for (const [validSpecifier, expected] of validSpecifiers) {
|
||||
if (validSpecifier === null) continue;
|
||||
|
||||
@@ -39,6 +48,9 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
|
||||
// The file exists but isn't exported. The exports is a number which counts
|
||||
// as a non-null value without any properties, just like `{}`.
|
||||
['pkgexports-number/hidden.js', './hidden.js'],
|
||||
// Sugar cases still encapsulate
|
||||
['pkgexports-sugar/not-exported.js', './not-exported.js'],
|
||||
['pkgexports-sugar2/not-exported.js', './not-exported.js']
|
||||
]);
|
||||
|
||||
const invalidExports = new Map([
|
||||
@@ -79,7 +91,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
|
||||
assertStartsWith(err.message, (isRequire ? 'Package exports' :
|
||||
'Cannot resolve'));
|
||||
assertIncludes(err.message, isRequire ?
|
||||
`do not define a valid '${subpath}' subpath` :
|
||||
`do not define a valid '${subpath}' target` :
|
||||
`matched for '${subpath}'`);
|
||||
}));
|
||||
}
|
||||
@@ -93,11 +105,22 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
|
||||
'Cannot find module');
|
||||
}));
|
||||
|
||||
// THe use of %2F escapes in paths fails loading
|
||||
// The use of %2F escapes in paths fails loading
|
||||
loadFixture('pkgexports/sub/..%2F..%2Fbar.js').catch(mustCall((err) => {
|
||||
strictEqual(err.code, isRequire ? 'ERR_INVALID_FILE_URL_PATH' :
|
||||
'ERR_MODULE_NOT_FOUND');
|
||||
}));
|
||||
|
||||
// Sugar conditional exports main mixed failure case
|
||||
loadFixture('pkgexports-sugar-fail').catch(mustCall((err) => {
|
||||
strictEqual(err.code, 'ERR_INVALID_PACKAGE_CONFIG');
|
||||
assertStartsWith(err.message, (isRequire ? 'Invalid package' :
|
||||
'Cannot resolve'));
|
||||
assertIncludes(err.message, '"exports" cannot contain some keys starting ' +
|
||||
'with \'.\' and some not. The exports object must either be an object of ' +
|
||||
'package subpath keys or an object of main entry condition name keys ' +
|
||||
'only.');
|
||||
}));
|
||||
});
|
||||
|
||||
const { requireFromInside, importFromInside } = fromInside;
|
||||
@@ -124,6 +147,6 @@ function assertStartsWith(actual, expected) {
|
||||
}
|
||||
|
||||
function assertIncludes(actual, expected) {
|
||||
ok(actual.toString().indexOf(expected),
|
||||
ok(actual.toString().indexOf(expected) !== -1,
|
||||
`${JSON.stringify(actual)} includes ${JSON.stringify(expected)}`);
|
||||
}
|
||||
|
||||
1
test/fixtures/node_modules/pkgexports-sugar-fail/main.js
generated
vendored
Normal file
1
test/fixtures/node_modules/pkgexports-sugar-fail/main.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = 'main';
|
||||
3
test/fixtures/node_modules/pkgexports-sugar-fail/not-exported.js
generated
vendored
Normal file
3
test/fixtures/node_modules/pkgexports-sugar-fail/not-exported.js
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = 'not-exported';
|
||||
6
test/fixtures/node_modules/pkgexports-sugar-fail/package.json
generated
vendored
Normal file
6
test/fixtures/node_modules/pkgexports-sugar-fail/package.json
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"exports": {
|
||||
"default": "./main.js",
|
||||
"./main": "./main.js"
|
||||
}
|
||||
}
|
||||
1
test/fixtures/node_modules/pkgexports-sugar/main.js
generated
vendored
Normal file
1
test/fixtures/node_modules/pkgexports-sugar/main.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = 'main';
|
||||
3
test/fixtures/node_modules/pkgexports-sugar/not-exported.js
generated
vendored
Normal file
3
test/fixtures/node_modules/pkgexports-sugar/not-exported.js
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = 'not-exported';
|
||||
3
test/fixtures/node_modules/pkgexports-sugar/package.json
generated
vendored
Normal file
3
test/fixtures/node_modules/pkgexports-sugar/package.json
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"exports": "./main.js"
|
||||
}
|
||||
1
test/fixtures/node_modules/pkgexports-sugar2/main.js
generated
vendored
Normal file
1
test/fixtures/node_modules/pkgexports-sugar2/main.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = 'main';
|
||||
3
test/fixtures/node_modules/pkgexports-sugar2/not-exported.js
generated
vendored
Normal file
3
test/fixtures/node_modules/pkgexports-sugar2/not-exported.js
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = 'not-exported';
|
||||
6
test/fixtures/node_modules/pkgexports-sugar2/package.json
generated
vendored
Normal file
6
test/fixtures/node_modules/pkgexports-sugar2/package.json
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"exports": {
|
||||
"require": "./not-exported.js",
|
||||
"default": "./main.js"
|
||||
}
|
||||
}
|
||||
5
test/fixtures/node_modules/pkgexports/package.json
generated
vendored
5
test/fixtures/node_modules/pkgexports/package.json
generated
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@pkgexports/name",
|
||||
"main": "./asdf.js",
|
||||
"exports": {
|
||||
".": "./asdf.js",
|
||||
"./hole": "./lib/hole.js",
|
||||
"./space": "./sp%20ce.js",
|
||||
"./valid-cjs": "./asdf.js",
|
||||
@@ -18,6 +18,7 @@
|
||||
"./fallbackfile": [[], null, {}, "builtin:x", "./asdf.js"],
|
||||
"./nofallback1": [],
|
||||
"./nofallback2": [null, {}, "builtin:x"],
|
||||
"./nodemodules": "./node_modules/internalpkg/x.js"
|
||||
"./nodemodules": "./node_modules/internalpkg/x.js",
|
||||
"./condition": [{ "require": "./sp ce.js" }, "./asdf.js"]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user