217 lines
7.6 KiB
Markdown
217 lines
7.6 KiB
Markdown
|
# resolve-pkg-maps
|
||
|
|
||
|
Utils to resolve `package.json` subpath & conditional [`exports`](https://nodejs.org/api/packages.html#exports)/[`imports`](https://nodejs.org/api/packages.html#imports) in resolvers.
|
||
|
|
||
|
Implements the [ESM resolution algorithm](https://nodejs.org/api/esm.html#resolver-algorithm-specification). Tested [against Node.js](/tests/) for accuracy.
|
||
|
|
||
|
<sub>Support this project by ⭐️ starring and sharing it. [Follow me](https://github.com/privatenumber) to see what other cool projects I'm working on! ❤️</sub>
|
||
|
|
||
|
## Usage
|
||
|
|
||
|
### Resolving `exports`
|
||
|
|
||
|
_utils/package.json_
|
||
|
```json5
|
||
|
{
|
||
|
// ...
|
||
|
"exports": {
|
||
|
"./reverse": {
|
||
|
"require": "./file.cjs",
|
||
|
"default": "./file.mjs"
|
||
|
}
|
||
|
},
|
||
|
// ...
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```ts
|
||
|
import { resolveExports } from 'resolve-pkg-maps'
|
||
|
|
||
|
const [packageName, packageSubpath] = parseRequest('utils/reverse')
|
||
|
|
||
|
const resolvedPaths: string[] = resolveExports(
|
||
|
getPackageJson(packageName).exports,
|
||
|
packageSubpath,
|
||
|
['import', ...otherConditions]
|
||
|
)
|
||
|
// => ['./file.mjs']
|
||
|
```
|
||
|
|
||
|
### Resolving `imports`
|
||
|
|
||
|
_package.json_
|
||
|
```json5
|
||
|
{
|
||
|
// ...
|
||
|
"imports": {
|
||
|
"#supports-color": {
|
||
|
"node": "./index.js",
|
||
|
"default": "./browser.js"
|
||
|
}
|
||
|
},
|
||
|
// ...
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```ts
|
||
|
import { resolveImports } from 'resolve-pkg-maps'
|
||
|
|
||
|
const resolvedPaths: string[] = resolveImports(
|
||
|
getPackageJson('.').imports,
|
||
|
'#supports-color',
|
||
|
['node', ...otherConditions]
|
||
|
)
|
||
|
// => ['./index.js']
|
||
|
```
|
||
|
|
||
|
## API
|
||
|
|
||
|
### resolveExports(exports, request, conditions)
|
||
|
|
||
|
Returns: `string[]`
|
||
|
|
||
|
Resolves the `request` based on `exports` and `conditions`. Returns an array of paths (e.g. in case a fallback array is matched).
|
||
|
|
||
|
#### exports
|
||
|
|
||
|
Type:
|
||
|
```ts
|
||
|
type Exports = PathOrMap | readonly PathOrMap[]
|
||
|
|
||
|
type PathOrMap = string | PathConditionsMap
|
||
|
|
||
|
type PathConditionsMap = {
|
||
|
[condition: string]: PathConditions | null
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The [`exports` property](https://nodejs.org/api/packages.html#exports) value in `package.json`.
|
||
|
|
||
|
#### request
|
||
|
|
||
|
Type: `string`
|
||
|
|
||
|
The package subpath to resolve. Assumes a normalized path is passed in (eg. [repeating slashes `//`](https://github.com/nodejs/node/issues/44316)).
|
||
|
|
||
|
It _should not_ start with `/` or `./`.
|
||
|
|
||
|
Example: if the full import path is `some-package/subpath/file`, the request is `subpath/file`.
|
||
|
|
||
|
|
||
|
#### conditions
|
||
|
|
||
|
Type: `readonly string[]`
|
||
|
|
||
|
An array of conditions to use when resolving the request. For reference, Node.js's default conditions are [`['node', 'import']`](https://nodejs.org/api/esm.html#:~:text=defaultConditions%20is%20the%20conditional%20environment%20name%20array%2C%20%5B%22node%22%2C%20%22import%22%5D.).
|
||
|
|
||
|
The order of this array does not matter; the order of condition keys in the export map is what matters instead.
|
||
|
|
||
|
Not all conditions in the array need to be met to resolve the request. It just needs enough to resolve to a path.
|
||
|
|
||
|
---
|
||
|
|
||
|
### resolveImports(imports, request, conditions)
|
||
|
|
||
|
Returns: `string[]`
|
||
|
|
||
|
Resolves the `request` based on `imports` and `conditions`. Returns an array of paths (e.g. in case a fallback array is matched).
|
||
|
|
||
|
#### imports
|
||
|
|
||
|
Type:
|
||
|
```ts
|
||
|
type Imports = {
|
||
|
[condition: string]: PathOrMap | readonly PathOrMap[] | null
|
||
|
}
|
||
|
|
||
|
type PathOrMap = string | Imports
|
||
|
```
|
||
|
|
||
|
The [`imports` property](https://nodejs.org/api/packages.html#imports) value in `package.json`.
|
||
|
|
||
|
|
||
|
#### request
|
||
|
|
||
|
Type: `string`
|
||
|
|
||
|
The request resolve. Assumes a normalized path is passed in (eg. [repeating slashes `//`](https://github.com/nodejs/node/issues/44316)).
|
||
|
|
||
|
> **Note:** In Node.js, imports resolutions are limited to requests prefixed with `#`. However, this package does not enforce that requirement in case you want to add custom support for non-prefixed entries.
|
||
|
|
||
|
#### conditions
|
||
|
|
||
|
Type: `readonly string[]`
|
||
|
|
||
|
An array of conditions to use when resolving the request. For reference, Node.js's default conditions are [`['node', 'import']`](https://nodejs.org/api/esm.html#:~:text=defaultConditions%20is%20the%20conditional%20environment%20name%20array%2C%20%5B%22node%22%2C%20%22import%22%5D.).
|
||
|
|
||
|
The order of this array does not matter; the order of condition keys in the import map is what matters instead.
|
||
|
|
||
|
Not all conditions in the array need to be met to resolve the request. It just needs enough to resolve to a path.
|
||
|
|
||
|
---
|
||
|
|
||
|
### Errors
|
||
|
|
||
|
#### `ERR_PACKAGE_PATH_NOT_EXPORTED`
|
||
|
- If the request is not exported by the export map
|
||
|
|
||
|
#### `ERR_PACKAGE_IMPORT_NOT_DEFINED`
|
||
|
- If the request is not defined by the import map
|
||
|
|
||
|
#### `ERR_INVALID_PACKAGE_CONFIG`
|
||
|
|
||
|
- If an object contains properties that are both paths and conditions (e.g. start with and without `.`)
|
||
|
- If an object contains numeric properties
|
||
|
|
||
|
#### `ERR_INVALID_PACKAGE_TARGET`
|
||
|
- If a resolved exports path is not a valid path (e.g. not relative or has protocol)
|
||
|
- If a resolved path includes `..` or `node_modules`
|
||
|
- If a resolved path is a type that cannot be parsed
|
||
|
|
||
|
## FAQ
|
||
|
|
||
|
### Why do the APIs return an array of paths?
|
||
|
|
||
|
`exports`/`imports` supports passing in a [fallback array](https://github.com/jkrems/proposal-pkg-exports/#:~:text=Whenever%20there%20is,to%20new%20cases.) to provide fallback paths if the previous one is invalid:
|
||
|
|
||
|
```json5
|
||
|
{
|
||
|
"exports": {
|
||
|
"./feature": [
|
||
|
"./file.js",
|
||
|
"./fallback.js"
|
||
|
]
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Node.js's implementation [picks the first valid path (without attempting to resolve it)](https://github.com/nodejs/node/issues/44282#issuecomment-1220151715) and throws an error if it can't be resolved. Node.js's fallback array is designed for [forward compatibility with features](https://github.com/jkrems/proposal-pkg-exports/#:~:text=providing%20forwards%20compatiblitiy%20for%20new%20features) (e.g. protocols) that can be immediately/inexpensively validated:
|
||
|
|
||
|
```json5
|
||
|
{
|
||
|
"exports": {
|
||
|
"./core-polyfill": ["std:core-module", "./core-polyfill.js"]
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
However, [Webpack](https://webpack.js.org/guides/package-exports/#alternatives) and [TypeScript](https://github.com/microsoft/TypeScript/blob/71e852922888337ef51a0e48416034a94a6c34d9/src/compiler/moduleSpecifiers.ts#L695) have deviated from this behavior and attempts to resolve the next path if a path cannot be resolved.
|
||
|
|
||
|
By returning an array of matched paths instead of just the first one, the user can decide which behavior to adopt.
|
||
|
|
||
|
### How is it different from [`resolve.exports`](https://github.com/lukeed/resolve.exports)?
|
||
|
|
||
|
`resolve.exports` only resolves `exports`, whereas this package resolves both `exports` & `imports`. This comparison will only cover resolving `exports`.
|
||
|
|
||
|
- Despite it's name, `resolve.exports` handles more than just `exports`. It takes in the entire `package.json` object to handle resolving `.` and [self-references](https://nodejs.org/api/packages.html#self-referencing-a-package-using-its-name). This package only accepts `exports`/`imports` maps from `package.json` and is scoped to only resolving what's defined in the maps.
|
||
|
|
||
|
- `resolve.exports` accepts the full request (e.g. `foo/bar`), whereas this package only accepts the requested subpath (e.g. `bar`).
|
||
|
|
||
|
- `resolve.exports` only returns the first result in a fallback array. This package returns an array of results for the user to decide how to handle it.
|
||
|
|
||
|
- `resolve.exports` supports [subpath folder mapping](https://nodejs.org/docs/latest-v16.x/api/packages.html#subpath-folder-mappings) (deprecated in Node.js v16 & removed in v17) but seems to [have a bug](https://github.com/lukeed/resolve.exports/issues/7). This package does not support subpath folder mapping because Node.js has removed it in favor of using subpath patterns.
|
||
|
|
||
|
- Neither resolvers rely on a file-system
|
||
|
|
||
|
This package also addresses many of the bugs in `resolve.exports`, demonstrated in [this test](/tests/exports/compare-resolve.exports.ts).
|