UnRS Resolver Node API with PNP support
npm install unrs-resolver> [!NOTE]
>
> This is a fork of [oxc-resolver] and [rspack-resolver], and will be used in [eslint-plugin-import-x] and [eslint-import-resolver-typescript] cause 100% compatible with [enhanced-resolve] is the non-goal of [oxc-resolver] itself, we add [enhanced-resolve] specific features like pnp support.
>
> We also fix several bugs reported by [eslint-plugin-import-x] and [eslint-import-resolver-typescript] users:
>
> - takes paths and references into account at the same time
> - references should take higher priority
> - support pnpapi core module and package deep link
> - enable more targets support
> - absolute path aliasing should not be skipped
> - use [napi-postinstall] for legacy npm versions
> - Raspberry PI 4 aarch64 compatibility issue and import-js/eslint-import-resolver-typescript#406 due to [mimalloc-safe]
> - support load_as_directory for pnp mode
> - resolve parent base url correctly by normalizing as absolute path
>
> The list could be longer in the future, but we don't want to make it too long here.
>
> We also sync with [oxc-resolver] and [rspack-resolver] regularly to keep up with the latest changes:
>
> - oxc-resolver: #15, #49, #62, #86 and #94
> - rspack-resolver(planned): #59
>
> Last but not least, we prepare some bug fix PRs first on our side and PR back into upstream projects, and we will keep doing this in the future:
>
> - oxc-resolver: #84 with oxc-resolver#455
> - rspack-resolver: #7 with rspack-resolver#54, which is eventually replaced by oxc-resolver#443
[![Crates.io][crates-badge]][crates-url]
[![npmjs.com][npm-badge]][npm-url]
[![Docs.rs][docs-badge]][docs-url]
[![Build Status][ci-badge]][ci-url]
[![Code Coverage][code-coverage-badge]][code-coverage-url]
[![CodSpeed Badge][codspeed-badge]][codspeed-url]
[![Sponsors][sponsors-badge]][sponsors-url]
[![MIT licensed][license-badge]][license-url]
Rust port of [enhanced-resolve].
- Released on [crates.io][crates-url] and [npm][npm-url].
- Implements the ESM and CommonJS module resolution algorithm specification.
- Built-in [tsconfig-paths-webpack-plugin]
- support extending tsconfig defined in tsconfig.extends
- support paths alias defined in tsconfig.compilerOptions.paths
- support project references defined tsconfig.references
- support template variable ${configDir} for substitution of config files directory path
- Supports in-memory file system via the FileSystem trait.
- Contains tracing instrumentation.
See index.d.ts for resolveSync and ResolverFactory API.
Quick example:
``javascript
import assert from 'node:assert';
import path from 'node:path';
import resolve, { ResolverFactory } from 'unrs-resolver';
// resolve
assert(resolve.sync(process.cwd(), './index.js').path, path.resolve('index.js'));
// ResolverFactory`
const resolver = new ResolverFactory();
assert(resolver.sync(process.cwd(), './index.js').path, path.resolve('index.js'));
See https://stackblitz.com/edit/unrs-resolver for usage example.
- For node.js, yarn pnp should work without any configuration, given the following conditions:
- the program is called with the yarn command, where the value process.versions.pnp is set..pnp.cjs
- manifest file exists in the closest directory, searched from the current working directory,
- no multi-project setup, per second bullet point in FIND_PNP_MANIFEST
An absolute path to a directory where the specifier is resolved against.
For CommonJS modules, it is the __dirname variable that contains the absolute path to the folder containing current module.
For ECMAScript modules, it is the value of import.meta.dirname.
Behavior is undefined when given a path to a file.
The string passed to require or import, i.e. require("specifier") or import "specifier"
- Error: Package subpath '.' is not defined by "exports" in - occurs when resolving without conditionNames.
The following usages apply to both Rust and Node.js; the code snippets are written in JavaScript.
To handle the exports field in package.json, ESM and CJS need to be differentiated.
> defaultConditions is the conditional environment name array, ["node", "import"].
This means when the caller is an ESM import (import "module"), resolve options should be
`javascript`
{
"conditionNames": ["node", "import"]
}
> LOAD_PACKAGE_EXPORTS(X, DIR)
>
> 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH,
> package.json "exports", ["node", "require"]) defined in the ESM resolver.
This means when the caller is a CJS require (require("module")), resolve options should be
`javascript`
{
"conditionNames": ["node", "require"]
}
To support both CJS and ESM with the same cache:
`javascript
const esmResolver = new ResolverFactory({
conditionNames: ['node', 'import'],
});
const cjsResolver = esmResolver.cloneWithOptions({
conditionNames: ['node', 'require'],
});
`
From this non-standard spec:
> The browser field is provided to JavaScript bundlers or component tools when packaging modules for client side use.
The option is
`javascript`
{
"aliasFields": ["browser"]
}
`javascript`
{
"mainFields": ["module", "main"]
}
Quoting esbuild's documentation:
- main - This is the standard field for all packages that are meant to be used with node. The name main is hard-coded in to node's module resolution logic itself. Because it's intended for use with node, it's reasonable to expect that the file path in this field is a CommonJS-style module.module
- - This field came from a proposal for how to integrate ECMAScript modules into node. Because of this, it's reasonable to expect that the file path in this field is an ECMAScript-style module. This proposal wasn't adopted by node (node uses "type": "module" instead) but it was adopted by major bundlers because ECMAScript-style modules lead to better tree shaking, or dead code removal.browser
- - This field came from a proposal that allows bundlers to replace node-specific files or modules with their browser-friendly versions. It lets you specify an alternate browser-specific entry point. Note that it is possible for a package to use both the browser and module field together (see the note below).
The following options are aligned with [enhanced-resolve], and is implemented for Rust crate usage.
See index.d.ts for Node.js usage.
| Field | Default | Description |
| ------------------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| alias | {} | A hash map of module alias configurations |
| aliasFields | [] | A list of alias fields in description files |
| extensionAlias | {} | An object which maps extension to extension aliases |
| conditionNames | [] | A list of exports field condition names |
| descriptionFiles | ["package.json"] | A list of description files to read from |
| enforceExtension | false | Enforce that a extension from extensions must be used |
| exportsFields | ["exports"] | A list of exports fields in description files |
| extensions | [".js", ".json", ".node"] | A list of extensions which should be tried for files |
| fallback | {} | Same as alias, but only used if default resolving fails |exports
| fileSystem | | The file system which should be used |
| fullySpecified | false | Request passed to resolve is already fully specified and extensions or main files are not resolved for it (they are still resolved for internal requests) |
| mainFields | ["main"] | A list of main fields in description files |
| mainFiles | ["index"] | A list of main files in directories |
| modules | ["node_modules"] | A list of directories to resolve modules from, can be absolute path or folder name |
| resolveToContext | false | Resolve to a context instead of a file |
| preferRelative | false | Prefer to resolve module requests as relative request and fallback to resolving as module |
| preferAbsolute | false | Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots |
| restrictions | [] | A list of resolve restrictions |
| roots | [] | A list of root paths |
| symlinks | true | Whether to resolve symlinks to their symlinked location |
| allowPackageExportsInDirectoryResolve | false | Allow field in require('../directory'). Not part of enhanced-resolve. |
| Field | Default | Description |
| ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| tsconfig | None | TypeScript related config for resolver |
| tsconfig.configFile | | A relative path to the tsconfig file based on cwd, or an absolute path of tsconfig file. |[]
| tsconfig.references | | - 'auto': inherits from TypeScript config string []
- : relative path (based on directory of the referencing tsconfig file) or absolute path of referenced project's tsconfig |
| Field | Default | Description |
| ---------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| cachePredicate | function() { return true }; | A function which decides whether a request should be cached or not. An object is passed to the function with path and request properties. |request.context
| cacheWithContext | true | If unsafe cache is enabled, includes in the cache key |
| plugins | [] | A list of additional resolve plugins which should be applied |
| resolver | undefined | A prepared Resolver to which the plugins are attached |
| unsafeCache | false | Use this cache object to unsafely cache the successful requests |
The following environment variable emits tracing information for the unrs_resolver::resolve function.
e.g.
``
2024-06-11T07:12:20.003537Z DEBUG unrs_resolver: options: ResolveOptions { ... }, path: "...", specifier: "...", ret: "..."
at /path/to/unrs_resolver-1.8.1/src/lib.rs:212
in unrs_resolver::resolve with path: "...", specifier: "..."
The input values are options, path and specifier, the returned value is ret.
``
UNRS_LOG=DEBUG your_program
Tests are ported from
- enhanced-resolve
- tsconfig-path and parcel-resolver for tsconfig-paths
Test cases are located in ./src/tests, fixtures are located in ./tests
- [x] alias.test.js
- [x] browserField.test.js
- [x] dependencies.test.js
- [x] exportsField.test.js
- [x] extension-alias.test.js
- [x] extensions.test.js
- [x] fallback.test.js
- [x] fullSpecified.test.js
- [x] identifier.test.js (see unit test in crates/unrs_resolver/src/request.rs)crates/unrs_resolver/src/path.rs
- [x] importsField.test.js
- [x] incorrect-description-file.test.js (need to add ctx.fileDependencies)
- [x] missing.test.js
- [x] path.test.js (see unit test in )
- [ ] plugins.test.js
- [ ] pnp.test.js
- [x] resolve.test.js
- [x] restrictions.test.js (partially done, regex is not supported yet)
- [x] roots.test.js
- [x] scoped-packages.test.js
- [x] simple.test.js
- [x] symlink.test.js
Irrelevant tests
- CachedInputFileSystem.test.js
- SyncAsyncFileSystemDecorator.test.js
- forEachBail.test.js
- getPaths.test.js
- pr-53.test.js
- unsafe-cache.test.js
- yield.test.js

| 1stG | UnRs | UnTS |
| ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|  |  |  |
| 1stG | UnRs | UnTS |
| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|  |  |  |
unrs_resolver` is free and open-source software licensed under the MIT License.
UnRS partially copies code from the following projects.
| Project | License |
| --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| webpack/enhanced-resolve | MIT |
| dividab/tsconfig-paths | MIT |
| parcel-bundler/parcel | MIT |
| tmccombs/json-comments-rs | Apache 2.0 |
| oxc-project/oxc-resolver | MIT |
| web-infra-dev/rspack-resolver | MIT |
[enhanced-resolve]: https://github.com/webpack/enhanced-resolve
[oxc-resolver]: https://github.com/oxc-project/oxc-resolver
[rspack-resolver]: https://github.com/web-infra-dev/rspack-resolver
[eslint-plugin-import-x]: https://github.com/un-ts/eslint-plugin-import-x
[eslint-import-resolver-typescript]: https://github.com/import-js/eslint-import-resolver-typescript
[napi-postinstall]: https://github.com/un-ts/napi-postinstall
[mimalloc-safe]: https://github.com/napi-rs/mimalloc-safe
[tsconfig-paths-webpack-plugin]: https://github.com/dividab/tsconfig-paths-webpack-plugin
[license-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license-url]: https://github.com/unrs/unrs-resolver/blob/main/LICENSE
[ci-badge]: https://github.com/unrs/unrs-resolver/actions/workflows/ci.yml/badge.svg?event=push&branch=main
[ci-url]: https://github.com/unrs/unrs-resolver/actions/workflows/ci.yml?query=event%3Apush+branch%3Amain
[code-coverage-badge]: https://codecov.io/github/unrs/unrs-resolver/branch/main/graph/badge.svg
[code-coverage-url]: https://codecov.io/gh/unrs/unrs-resolver
[sponsors-badge]: https://img.shields.io/github/sponsors/JounQin
[sponsors-url]: https://github.com/sponsors/JounQin
[codspeed-badge]: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json
[codspeed-url]: https://codspeed.io/unrs/unrs-resolver
[crates-badge]: https://img.shields.io/crates/d/unrs_resolver?label=crates.io
[crates-url]: https://crates.io/crates/unrs_resolver
[docs-badge]: https://img.shields.io/docsrs/unrs_resolver
[docs-url]: https://docs.rs/unrs_resolver
[npm-badge]: https://img.shields.io/npm/dw/unrs-resolver?label=npm
[npm-url]: https://www.npmjs.com/package/unrs-resolver