-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
module: package "imports" field #34117
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
Changes from all commits
d9f7c01
5875d53
105b7f2
8fe68f7
1dc83f5
ceffe4c
2570191
ec4b7a4
eb34788
6a69697
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -538,6 +538,43 @@ and in a CommonJS one. For example, this code will also work: | |
const { something } = require('a-package/foo'); // Loads from ./foo.js. | ||
``` | ||
|
||
### Internal package imports | ||
|
||
In addition to the `"exports"` field it is possible to define internal package | ||
import maps that only apply to import specifiers from within the package itself. | ||
|
||
Entries in the imports field must always start with `#` to ensure they are | ||
clearly disambiguated from package specifiers. | ||
|
||
For example, the imports field can be used to gain the benefits of conditional | ||
exports for internal modules: | ||
|
||
```json | ||
// package.json | ||
{ | ||
"imports": { | ||
"#dep": { | ||
"node": "dep-node-native", | ||
"default": "./dep-polyfill.js" | ||
} | ||
}, | ||
"dependencies": { | ||
"dep-node-native": "^1.0.0" | ||
} | ||
} | ||
``` | ||
|
||
where `import '#dep'` would now get the resolution of the external package | ||
`dep-node-native` (including its exports in turn), and instead get the local | ||
file `./dep-polyfill.js` relative to the package in other environments. | ||
|
||
Unlike the exports field, import maps permit mapping to external packages | ||
because this provides an important use case for conditional loading and also can | ||
be done without the risk of cycles, unlike for exports. | ||
|
||
Apart from the above, the resolution rules for the imports field are otherwise | ||
analogous to the exports field. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we might want to add that fragments and queries are not preserved for non-directory paths. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume they are neither preserved nor removed..? It may make sense to call out that for bare specifiers There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looking at the markdown algorithm they would be removed it seems. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to find the part where it removes anything from the specifier and I can't find a reference to "?"/"#" or "query"/"fragment". According to my reading of the markdown algorithm (and memory of the spec), the following should work: // ./package.json
{
"name": "pkg",
"imports": {
"#/some?private#specifier": "dep/this?is?all#just%chars"
}
}
// ./entry.mjs
import '#/some?private#specifier';
// ./node_modules/dep/package.json
{
"name": "pkg",
"exports": {
"./this?is?all#just%chars": "./foo.mjs?some=query"
}
} The import in
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the point is that {
"field (you used exports but this PR is imports)": {
"#/x?y" : "./x"
} import '#/x?z'; would not carry There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bmeck like import maps, the left hand side is always a direct string replacement, so the only difference remains the standard CJS v ESM resolvers handling resolution differently for those cases, but this applies equally to any absolute or relative paths - imports and exports targets are no different. @jkrems note that I have disallowed |
||
|
||
### Dual CommonJS/ES module packages | ||
|
||
Prior to the introduction of support for ES modules in Node.js, it was a common | ||
|
@@ -1552,10 +1589,11 @@ The resolver can throw the following errors: | |
or package subpath specifier. | ||
* _Invalid Package Configuration_: package.json configuration is invalid or | ||
contains an invalid configuration. | ||
* _Invalid Package Target_: Package exports define a target module within the | ||
package that is an invalid type or string target. | ||
* _Invalid Package Target_: Package exports or imports define a target module | ||
guybedford marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for the package that is an invalid type or string target. | ||
* _Package Path Not Exported_: Package exports do not define or permit a target | ||
subpath in the package for the given module. | ||
* _Package Import Not Defined_: Package imports do not define the specifier. | ||
* _Module Not Found_: The package or module requested does not exist. | ||
|
||
<details> | ||
|
@@ -1567,11 +1605,14 @@ The resolver can throw the following errors: | |
> 1. If _specifier_ is a valid URL, then | ||
> 1. Set _resolvedURL_ to the result of parsing and reserializing | ||
> _specifier_ as a URL. | ||
> 1. Otherwise, if _specifier_ starts with _"/"_, then | ||
> 1. Throw an _Invalid Module Specifier_ error. | ||
> 1. Otherwise, if _specifier_ starts with _"./"_ or _"../"_, then | ||
> 1. Otherwise, if _specifier_ starts with _"/"_, _"./"_ or _"../"_, then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OT: This algorithm is pretty much un-reviewable at this point (at least to me). I don't think it's in scope for this PR but maybe the answer is to move it to its own page and rethink how it's organized? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm totally open to suggestions. My main concerns are keeping the API nature of the docs straightforward and having a nice link to this spec as it should be a reference. I do generally not like having it in a details element because it makes it difficult to link this spec without it being overlooked so I'd be all for a dedicated page actually. |
||
> 1. Set _resolvedURL_ to the URL resolution of _specifier_ relative to | ||
> _parentURL_. | ||
> 1. Otherwise, if _specifier_ starts with _"#"_, then | ||
> 1. Set _resolvedURL_ to the result of | ||
> **PACKAGE_INTERNAL_RESOLVE**(_specifier_, _parentURL_). | ||
> 1. If _resolvedURL_ is **null** or **undefined**, throw a | ||
> _Package Import Not Defined_ error. | ||
> 1. Otherwise, | ||
> 1. Note: _specifier_ is now a bare specifier. | ||
> 1. Set _resolvedURL_ the result of | ||
|
@@ -1609,7 +1650,7 @@ The resolver can throw the following errors: | |
> 1. If _packageSubpath_ contains any _"."_ or _".."_ segments or percent | ||
> encoded strings for _"/"_ or _"\\"_, then | ||
> 1. Throw an _Invalid Module Specifier_ error. | ||
> 1. Set _selfUrl_ to the result of | ||
> 1. Let _selfUrl_ be the result of | ||
> **SELF_REFERENCE_RESOLVE**(_packageName_, _packageSubpath_, _parentURL_). | ||
> 1. If _selfUrl_ isn't empty, return _selfUrl_. | ||
> 1. If _packageSubpath_ is _undefined_ and _packageName_ is a Node.js builtin | ||
|
@@ -1632,8 +1673,11 @@ The resolver can throw the following errors: | |
> 1. If _pjson_ is not **null** and _pjson_ has an _"exports"_ key, then | ||
> 1. Let _exports_ be _pjson.exports_. | ||
> 1. If _exports_ is not **null** or **undefined**, then | ||
> 1. Return **PACKAGE_EXPORTS_RESOLVE**(_packageURL_, | ||
> _packageSubpath_, _pjson.exports_). | ||
> 1. Let _resolved_ be the result of **PACKAGE_EXPORTS_RESOLVE**( | ||
> _packageURL_, _packageSubpath_, _pjson.exports_). | ||
> 1. If _resolved_ is **null** or **undefined**, throw a | ||
> _Package Path Not Exported_ error. | ||
> 1. Return _resolved_. | ||
> 1. Return the URL resolution of _packageSubpath_ in _packageURL_. | ||
> 1. Throw a _Module Not Found_ error. | ||
|
||
|
@@ -1654,8 +1698,11 @@ The resolver can throw the following errors: | |
> 1. If _pjson_ is not **null** and _pjson_ has an _"exports"_ key, then | ||
> 1. Let _exports_ be _pjson.exports_. | ||
> 1. If _exports_ is not **null** or **undefined**, then | ||
> 1. Return **PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _subpath_, | ||
> _pjson.exports_). | ||
> 1. Let _resolved_ be the result of **PACKAGE_EXPORTS_RESOLVE**( | ||
> _packageURL_, _subpath_, _pjson.exports_). | ||
> 1. If _resolved_ is **null** or **undefined**, throw a | ||
> _Package Path Not Exported_ error. | ||
> 1. Return _resolved_. | ||
> 1. Return the URL resolution of _subpath_ in _packageURL_. | ||
> 1. Otherwise, return **undefined**. | ||
|
||
|
@@ -1668,12 +1715,18 @@ The resolver can throw the following errors: | |
> not starting with _"."_, throw an _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. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**( | ||
> _packageURL_, _pjson.exports_, _""_, **false**, _defaultEnv_). | ||
> 1. If _resolved_ is **null** or **undefined**, throw a | ||
> _Package Path Not Exported_ error. | ||
> 1. Return _resolved_. | ||
> 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. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**( | ||
> _packageURL_, _mainExport_, _""_, **false**, _defaultEnv_). | ||
> 1. If _resolved_ is **null** or **undefined**, throw a | ||
> _Package Path Not Exported_ error. | ||
> 1. Return _resolved_. | ||
> 1. Throw a _Package Path Not Exported_ error. | ||
> 1. Let _legacyMainURL_ be the result applying the legacy | ||
> **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, throwing a | ||
|
@@ -1687,31 +1740,37 @@ The resolver can throw the following errors: | |
> 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. Return **PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_, | ||
> _""_, **false**, _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 | ||
> 1. If _packagePath_ starts with _directory_, then | ||
> 1. Let _target_ be the value of _exports\[directory\]_. | ||
> 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_, _defaultEnv_). | ||
> 1. Throw a _Package Path Not Exported_ error. | ||
> 1. Return **PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_, | ||
> _subpath_, **false**, _defaultEnv_). | ||
> 1. Return **null**. | ||
|
||
**PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _env_) | ||
**PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _internal_, _env_) | ||
|
||
> 1. If _target_ is a String, then | ||
> 1. If _target_ does not start with _"./"_ or contains any _"node_modules"_ | ||
> segments including _"node_modules"_ percent-encoding, throw an | ||
> _Invalid Package Target_ error. | ||
> 1. If _target_ contains any _"node_modules"_ segments including | ||
> _"node_modules"_ percent-encoding, throw an _Invalid Package Target_ | ||
> error. | ||
> 1. If _subpath_ has non-zero length and _target_ does not end with _"/"_, | ||
> throw an _Invalid Module Specifier_ error. | ||
> 1. If _target_ does not start with _"./"_, then | ||
> 1. If _target_ does not start with _"../"_ or _"/"_ and is not a valid | ||
> URL, then | ||
> 1. If _internal_ is **true**, return **PACKAGE_RESOLVE**( | ||
> _target_ + _subpath_, _packageURL_ + _"/"_)_. | ||
> 1. Otherwise throw an _Invalid Package Target_ error. | ||
> 1. Let _resolvedTarget_ be the URL resolution of the concatenation of | ||
> _packageURL_ and _target_. | ||
> 1. If _resolvedTarget_ is not contained in _packageURL_, throw an | ||
> _Invalid Package Target_ error. | ||
> 1. If _subpath_ has non-zero length and _target_ does not end with _"/"_, | ||
> throw an _Invalid Module Specifier_ error. | ||
> 1. Let _resolved_ be the URL resolution of the concatenation of | ||
> _subpath_ and _resolvedTarget_. | ||
> 1. If _resolved_ is not contained in _resolvedTarget_, throw an | ||
|
@@ -1723,22 +1782,48 @@ The resolver can throw the following errors: | |
> 1. For each property _p_ of _target_, in object insertion order as, | ||
> 1. If _p_ equals _"default"_ or _env_ contains an entry for _p_, then | ||
> 1. Let _targetValue_ be the value of the _p_ property in _target_. | ||
> 1. Return the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**( | ||
> _packageURL_, _targetValue_, _subpath_, _env_), continuing the | ||
> loop on any _Package Path Not Exported_ error. | ||
> 1. Throw a _Package Path Not Exported_ error. | ||
> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**( | ||
> _packageURL_, _targetValue_, _subpath_, _internal_, _env_) | ||
> 1. If _resolved_ is equal to **undefined**, continue the loop. | ||
> 1. Return _resolved_. | ||
> 1. Return **undefined**. | ||
> 1. Otherwise, if _target_ is an Array, then | ||
> 1. If _target.length is zero, throw a _Package Path Not Exported_ error. | ||
> 1. If _target.length is zero, return **null**. | ||
> 1. For each item _targetValue_ in _target_, do | ||
> 1. If _targetValue_ is an Array, continue the loop. | ||
> 1. Return the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, | ||
> _targetValue_, _subpath_, _env_), continuing the loop on any | ||
> _Package Path Not Exported_ or _Invalid Package Target_ error. | ||
> 1. Throw the last fallback resolution error. | ||
> 1. Otherwise, if _target_ is _null_, throw a _Package Path Not Exported_ | ||
> error. | ||
> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**( | ||
> _packageURL_, _targetValue_, _subpath_, _internal_, _env_), | ||
> continuing the loop on any _Invalid Package Target_ error. | ||
> 1. If _resolved_ is **undefined**, continue the loop. | ||
> 1. Return _resolved_. | ||
> 1. Return or throw the last fallback resolution **null** return or error. | ||
> 1. Otherwise, if _target_ is _null_, return **null**. | ||
> 1. Otherwise throw an _Invalid Package Target_ error. | ||
|
||
**PACKAGE_INTERNAL_RESOLVE**(_specifier_, _parentURL_) | ||
|
||
> 1. Assert: _specifier_ begins with _"#"_. | ||
> 1. If _specifier_ is exactly equal to _"#"_ or starts with _"#/"_, then | ||
> 1. Throw an _Invalid Module Specifier_ error. | ||
> 1. Let _packageURL_ be the result of **READ_PACKAGE_SCOPE**(_parentURL_). | ||
> 1. If _packageURL_ is not **null**, then | ||
> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_packageURL_). | ||
> 1. If _pjson.imports is a non-null Object, then | ||
> 1. Let _imports_ be _pjson.imports_. | ||
> 1. If _specifier_ is a key of _imports_, then | ||
> 1. Let _target_ be the value of _imports\[specifier\]_. | ||
> 1. Return **PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_, | ||
> _""_, **true**, _defaultEnv_). | ||
> 1. Let _directoryKeys_ be the list of keys of _imports_ ending in | ||
> _"/"_, sorted by length descending. | ||
> 1. For each key _directory_ in _directoryKeys_, do | ||
> 1. If _specifier_ starts with _directory_, then | ||
> 1. Let _target_ be the value of _imports\[directory\]_. | ||
> 1. Let _subpath_ be the substring of _target_ starting at the | ||
> index of the length of _directory_. | ||
> 1. Return **PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_, | ||
> _subpath_, **true**, _defaultEnv_). | ||
> 1. Return **null**. | ||
|
||
**ESM_FORMAT**(_url_) | ||
|
||
> 1. Assert: _url_ corresponds to an existing file. | ||
|
Uh oh!
There was an error while loading. Please reload this page.