File size: 61,686 Bytes
f119c28 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 | # enhanced-resolve
[![npm][npm]][npm-url]
[![Build Status][build-status]][build-status-url]
[![codecov][codecov-badge]][codecov-url]
[![Install Size][size]][size-url]
[![GitHub Discussions][discussion]][discussion-url]
Offers an async require.resolve function. It's highly configurable.
## Features
- plugin system
- provide a custom filesystem
- sync and async node.js filesystems included
## Getting Started
### Install
```sh
# npm
npm install enhanced-resolve
# or Yarn
yarn add enhanced-resolve
# or pnpm
pnpm add enhanced-resolve
```
### Resolve
There is a Node.js API which allows to resolve requests according to the Node.js resolving rules.
Sync, async (callback) and promise APIs are offered. A `create` method allows to create a custom resolve function.
```js
const resolve = require("enhanced-resolve");
resolve("/some/path/to/folder", "module/dir", (err, result) => {
result; // === "/some/path/node_modules/module/dir/index.js"
});
resolve.sync("/some/path/to/folder", "../../dir");
// === "/some/path/dir/index.js"
const result = await resolve.promise("/some/path/to/folder", "../../dir");
// === "/some/path/dir/index.js"
const myResolve = resolve.create({
// or resolve.create.sync / resolve.create.promise
extensions: [".ts", ".js"],
// see more options below
});
myResolve("/some/path/to/folder", "ts-module", (err, result) => {
result; // === "/some/node_modules/ts-module/index.ts"
});
```
### Public API
All of the following are exposed from `require("enhanced-resolve")`.
#### `resolve(context?, parent, specifier, resolveContext?, callback)`
Async Node-style resolver using the built-in defaults (`conditionNames: ["node"]`, `extensions: [".js", ".json", ".node"]`). Both `context` and `resolveContext` are optional, so the function accepts four shapes:
```js
resolve(context, parent, specifier, resolveContext, callback);
resolve(context, parent, specifier, callback);
resolve(parent, specifier, resolveContext, callback);
resolve(parent, specifier, callback); // most common
```
The `parent` / `specifier` naming mirrors the ECMAScript resolution terms (as in `import.meta.resolve(specifier, parent)`): `parent` is the location you resolve **from**, and `specifier` is the string being resolved.
##### Arguments β what each one is and when to pass it
| Argument | Required | What it is | When to pass it |
| ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `context` | no | Extra info about _who_ is requesting, e.g. `{ environments: ["node+es3+es5+process+native"] }`. Plugins can read arbitrary keys off it. When omitted, a built-in Node context is used. | Pass it only when a plugin you use reads from it. Most callers omit it. |
| `parent` | yes | The absolute directory to resolve **from** β the location the `specifier` is relative to (the ECMAScript "parent", historically called the "lookup start path"). | Always. |
| `specifier` | yes | The string to resolve β a relative specifier (`./utils`), a bare module (`lodash/merge`), an alias, etc. (the ECMAScript "specifier", historically called the "request"). | Always. |
| `resolveContext` | no | A collector object the resolver writes into / reads from: `fileDependencies`, `contextDependencies`, `missingDependencies` (`Set`s it fills in), `log` (a function it calls for trace output). | Pass it when you need to know which files/dirs were touched (e.g. to set up a watcher) or want a log of the resolution steps. |
| `callback` | yes | `(err, result, resolveRequest) => void`. `result` is the resolved absolute path (or `false` if the request resolves to an ignored module). `resolveRequest` is the full request object with `path`, `query`, `fragment`, `descriptionFilePath`, etc. | Always. |
##### Minimal form β just `parent`, `specifier`, `callback`
```js
const resolve = require("enhanced-resolve");
resolve(__dirname, "./utils", (err, result) => {
if (err) throw err;
result; // === "/abs/path/to/utils.js"
});
```
##### Inspecting the full result via the third callback argument
```js
resolve(__dirname, "./utils?query#frag", (err, result, resolveRequest) => {
result; // === "/abs/path/to/utils.js?query#frag" β path with query/fragment appended
resolveRequest.path; // === "/abs/path/to/utils.js" (the bare path, no query/fragment)
resolveRequest.query; // === "?query" (empty string if the request has none)
resolveRequest.fragment; // === "#frag" (empty string if the request has none)
resolveRequest.descriptionFilePath; // path to the nearest package.json, if any
});
```
##### With `resolveContext` β collect dependencies and a log
Pass a `resolveContext` when you need the resolver to report back _what it looked at_. The `Set`s you provide are filled in during resolution, and `log` is called with each step:
```js
const fileDependencies = new Set();
const missingDependencies = new Set();
const contextDependencies = new Set();
const logs = [];
resolve(
__dirname,
"./utils",
{
fileDependencies, // files that were read/checked
missingDependencies, // paths that were probed but didn't exist
contextDependencies, // directories that were read
log: (msg) => logs.push(msg), // trace of each resolution step
},
(err, result) => {
result; // === "/abs/path/to/utils.js"
fileDependencies; // Set { ".../utils.js", ".../package.json", ... }
missingDependencies; // Set { ".../utils", ".../utils.json", ... } (candidates tried)
// `fileDependencies` / `missingDependencies` are exactly what you'd feed
// a file watcher so a rebuild re-runs when any of them changes.
},
);
```
##### With a custom `context`
Pass a `context` when a plugin reads from it. The default resolver only uses `environments`, but custom plugins may key off anything you put here:
```js
resolve(
{ environments: ["node+es3+es5+process+native"] }, // context
__dirname,
"./utils",
(err, result) => {
result; // === "/abs/path/to/utils.js"
},
);
```
#### `resolve.sync(context?, parent, specifier, resolveContext?) => string | false`
Synchronous variant. Throws on failure, returns `false` when the resolve yields no result.
```js
const file = resolve.sync(__dirname, "./utils");
```
#### `resolve.promise(context?, parent, specifier, resolveContext?) => Promise<string | false>`
Promise variant of `resolve`.
```js
const file = await resolve.promise(__dirname, "./utils");
```
#### `resolve.create(options) => ResolveFunctionAsync`
Builds a customized async resolve function. Options are the same as for [`ResolverFactory.createResolver`](#resolver-options); `fileSystem` defaults to the built-in Node.js filesystem.
```js
const resolveTs = resolve.create({ extensions: [".ts", ".tsx", ".js"] });
resolveTs(__dirname, "./component", (err, result) => {
// result === "/abs/path/to/component.tsx"
});
```
#### `resolve.create.sync(options) => ResolveFunction`
Sync variant of `resolve.create`.
```js
const resolveTsSync = resolve.create.sync({ extensions: [".ts", ".js"] });
const file = resolveTsSync(__dirname, "./component");
```
#### `resolve.create.promise(options) => ResolveFunctionPromise`
Promise variant of `resolve.create`.
```js
const resolveTsPromise = resolve.create.promise({ extensions: [".ts", ".js"] });
const file = await resolveTsPromise(__dirname, "./component");
```
#### `ResolverFactory.createResolver(options) => Resolver`
Lower-level factory. Returns a `Resolver` whose `resolve`, `resolveSync`, and `resolvePromise` methods accept `(context, parent, specifier, resolveContext, [callback])`. Use this when you need a reusable resolver instance or access to its `hooks` (see the [Plugins](#plugins) section). `fileSystem` is required here β the high-level `resolve.create` defaults it for you.
```js
const fs = require("fs");
const { CachedInputFileSystem, ResolverFactory } = require("enhanced-resolve");
const resolver = ResolverFactory.createResolver({
fileSystem: new CachedInputFileSystem(fs, 4000),
extensions: [".js", ".json"],
});
// callback
resolver.resolve({}, __dirname, "./utils", {}, (err, file) => {
// ...
});
// sync (requires a sync fileSystem)
const fileSync = resolver.resolveSync({}, __dirname, "./utils");
// promise
const filePromise = await resolver.resolvePromise({}, __dirname, "./utils", {});
```
#### `CachedInputFileSystem(fileSystem, duration)`
Wraps any Node-compatible `fs` to add an in-memory cache for `stat`, `readdir`, `readFile`, `readJson`, and `readlink`. `duration` is the cache TTL in milliseconds (typically `4000`). Call `.purge()` to invalidate, or `.purge(path)` / `.purge([path, ...])` to invalidate specific entries β do this whenever you know files changed (e.g. from a watcher).
```js
const fs = require("fs");
const { CachedInputFileSystem } = require("enhanced-resolve");
const cachedFs = new CachedInputFileSystem(fs, 4000);
// later, when files change:
cachedFs.purge("/abs/path/to/changed-file.js");
```
#### Exported plugins & helpers
For use with the `plugins` option or as standalone utilities:
- `ResolverFactory` β see above.
- `CachedInputFileSystem` β see above.
- `CloneBasenamePlugin(source, target)` β joins the directory's basename onto the path. See [Built-in Plugins](#built-in-plugins).
- `LogInfoPlugin(source)` β logs pipeline state at a hook; enable by passing a `log` function on the `resolveContext`.
- `TsconfigPathsPlugin(options)` β applies `tsconfig.json` `paths` / `baseUrl` mappings; typically configured via the `tsconfig` resolver option instead.
- `forEachBail(array, iterator, callback)` β bail-style async iterator used internally; useful when authoring plugins that try several candidates in order.
```js
const { LogInfoPlugin } = require("enhanced-resolve");
const resolver = ResolverFactory.createResolver({
fileSystem: cachedFs,
extensions: [".js"],
plugins: [new LogInfoPlugin("described-resolve")],
});
resolver.resolve(
{},
__dirname,
"./utils",
{ log: (msg) => console.log(msg) },
() => {},
);
```
### Creating a Resolver
The easiest way to create a resolver is to use the `createResolver` function on `ResolveFactory`, along with one of the supplied File System implementations.
```js
const fs = require("fs");
const { CachedInputFileSystem, ResolverFactory } = require("enhanced-resolve");
// create a resolver
const myResolver = ResolverFactory.createResolver({
// Typical usage will consume the `fs` + `CachedInputFileSystem`, which wraps Node.js `fs` to add caching.
fileSystem: new CachedInputFileSystem(fs, 4000),
extensions: [".js", ".json"],
/* any other resolver options here. Options/defaults can be seen below */
});
// resolve a file with the new resolver
const context = {};
const parent = "/Users/webpack/some/root/dir"; // directory to resolve from
const specifier = "./path/to-look-up.js"; // string to resolve
const resolveContext = {};
// callback
myResolver.resolve(
context,
parent,
specifier,
resolveContext,
(err /* Error */, filepath /* string */) => {
// Do something with the path
},
);
// promise
try {
const filepath = await myResolver.resolvePromise(
context,
parent,
specifier,
resolveContext,
);
// Do something with the path
} catch (err) {
// handle resolve failure
}
// sync (requires a sync fileSystem, e.g. the default Node.js one)
const filepath = myResolver.resolveSync(context, parent, specifier);
```
#### Resolver Options
| Field | Default | Description |
| ------------------------ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| alias | [] | A list of module alias configurations or an object which maps key to value |
| aliasFields | [] | A list of alias fields in description files |
| extensionAlias | {} | An object which maps extension to extension aliases |
| extensionAliasForExports | false | Also apply `extensionAlias` to paths resolved through the package.json `exports` field. Off by default (Node.js-aligned) |
| 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. |
| cacheWithContext | true | If unsafe cache is enabled, includes `request.context` in the cache key |
| 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 |
| 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 |
| plugins | [] | A list of additional resolve plugins which should be applied |
| resolver | undefined | A prepared Resolver to which the plugins are attached |
| 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 |
| tsconfig | false | TypeScript config for paths mapping. Can be `false` (disabled), `true` (use default `tsconfig.json`), a string path to `tsconfig.json`, or an object with `configFile`, `references`, and `baseUrl` options. Supports JSONC format (comments and trailing commas) like TypeScript compiler. |
| tsconfig.configFile | tsconfig.json | Path to the tsconfig.json file |
| tsconfig.references | [] | Project references. `'auto'` to load from tsconfig, or an array of paths to referenced projects |
| tsconfig.baseUrl | undefined | Override baseUrl from tsconfig.json. If provided, this value will be used instead of the baseUrl in the tsconfig file |
| unsafeCache | false | Use this cache object to unsafely cache the successful requests |
#### Option Examples
Small snippets for the non-obvious options. All options are passed to `resolve.create({ ... })` or `ResolverFactory.createResolver({ ... })`.
**`alias`** β rewrite matching requests to a target path, module, or to `false` to ignore them. Accepts an object or an array of entries (array form lets you specify ordering / `onlyModule`).
```js
const options = {
alias: {
"@": path.resolve(__dirname, "src"), // @/utils β src/utils
lodash$: "lodash-es", // exact "lodash", not "lodash/foo"
"ignored-module": false, // short-circuit to an empty module
},
};
```
**`aliasFields`** β read alias maps from fields in `package.json`. The `browser` field is the common case:
```js
const options = { aliasFields: ["browser"] };
```
**`extensionAlias`** β maps one request extension to a list of candidate extensions. Useful for TypeScript ESM where imports are written with `.js` but the source is `.ts`. Applies both to direct requests (e.g. `./foo.js`) and to paths produced by the package.json `imports` field (e.g. `#foo` β `./foo.js` β `./foo.ts`). By default it does **not** apply to paths produced by the `exports` field (to stay aligned with Node.js, which does not substitute extensions on package-exported paths) β see `extensionAliasForExports` below to opt in:
```js
const options = {
extensionAlias: {
".js": [".ts", ".js"],
".mjs": [".mts", ".mjs"],
},
};
```
**`extensionAliasForExports`** β when `true`, also apply `extensionAlias` to paths resolved through the package.json `exports` field. Off by default to match Node.js. Turn it on if you want full alignment with TypeScript's resolver for packages that ship `.ts` sources alongside the compiled `.js` files they list in `exports` (e.g. monorepo source packages, or the `eslint-import-resolver-typescript` use case):
```js
const options = {
extensionAlias: { ".js": [".ts", ".js"] },
extensionAliasForExports: true,
};
```
**`conditionNames` + `exportsFields`** β pick which conditions to match in the `exports` field of `package.json`:
```js
const options = {
conditionNames: ["import", "node", "default"],
exportsFields: ["exports"],
};
```
**`extensions`** β extensions to try for extensionless requests, in order:
```js
const options = { extensions: [".ts", ".tsx", ".js", ".json"] };
```
**`fallback`** β same shape as `alias`, but only consulted when the primary resolve fails. Handy for polyfills:
```js
const options = {
fallback: {
crypto: require.resolve("crypto-browserify"),
stream: false,
},
};
```
**`modules`** β where to look for bare-module requests. Entries can be folder names (searched hierarchically up the tree) or absolute paths (searched directly):
```js
const options = { modules: [path.resolve(__dirname, "src"), "node_modules"] };
```
**`mainFields` / `mainFiles`** β fields in `package.json` to try for a package entry point, and filenames to try inside a directory:
```js
const options = {
mainFields: ["browser", "module", "main"],
mainFiles: ["index"],
};
```
**`roots` + `preferAbsolute`** β resolve server-relative URLs (starting with `/`) against one or more root directories. With `preferAbsolute: true`, absolute-path resolution is tried before the roots are consulted.
```js
const options = {
roots: [path.resolve(__dirname, "public")],
preferAbsolute: false,
};
```
**`restrictions`** β reject results that don't satisfy at least one restriction. Accepts strings (path prefixes) or `RegExp`s:
```js
const options = {
restrictions: [path.resolve(__dirname, "src"), /\.(js|ts)$/],
};
```
**`tsconfig`** β apply TypeScript `paths` / `baseUrl` mappings. Either pass `true` to load `./tsconfig.json`, a path string, or a configuration object:
```js
const options = {
tsconfig: {
configFile: path.resolve(__dirname, "tsconfig.json"),
references: "auto", // honor project references declared in tsconfig
},
};
```
**`symlinks`** β resolve to the real path by following symlinks. Set to `false` to keep the symlinked path (common for monorepo / pnpm layouts where you want module identity tied to the workspace location):
```js
const options = { symlinks: false };
```
**`fullySpecified`** β require fully-specified requests (no extension inference, no `index` lookup) for non-internal requests. Matches Node.js ESM semantics:
```js
const options = { fullySpecified: true };
```
**`unsafeCache`** β pass an object to use as an in-memory cache of successful resolves. Set to `true` to let the resolver allocate its own:
```js
const options = {
unsafeCache: {}, // or true
cacheWithContext: false, // skip context in the cache key β faster, but only safe if context doesn't change the result
};
```
To observe whether a request was served from the cache, wrap the cache object in a `Proxy`. `UnsafeCachePlugin` reads entries with `cache[id]` (cache lookup) and writes them with `cache[id] = result` (cache store), so trapping `get` and `set` is enough to distinguish hits from misses:
```js
const cache = {};
const observedCache = new Proxy(cache, {
get(target, name, receiver) {
const hit = name in target;
console.log(hit ? `[cache hit] ${name}` : `[cache miss] ${name}`);
return Reflect.get(target, name, receiver);
},
set(target, name, value, receiver) {
console.log(`[cache set] ${name}`);
return Reflect.set(target, name, value, receiver);
},
});
const resolver = ResolverFactory.createResolver({
fileSystem: new CachedInputFileSystem(fs, 4000),
extensions: [".js", ".json"],
unsafeCache: observedCache,
});
```
The `name` argument is the cache id β a `JSON.stringify`'d object containing `type`, `context`, `path`, `query`, `fragment`, and `request` β so you can parse it to report on specific resolves. Only successful resolves go through the cache; failures never touch it.
**`fileSystem`** β any `fs`-compatible implementation. Usually `new CachedInputFileSystem(fs, 4000)`; can be a virtual filesystem (e.g. `memfs`) for testing:
```js
const options = { fileSystem: new CachedInputFileSystem(require("fs"), 4000) };
```
**`plugins`** β additional plugin instances appended to the pipeline. See [Plugins](#plugins):
```js
const options = {
plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })],
};
```
## Plugins
Similar to `webpack`, the core of `enhanced-resolve` functionality is implemented as individual plugins that are executed using [`tapable`](https://github.com/webpack/tapable).
These plugins can extend the functionality of the library, adding other ways for files/contexts to be resolved.
A plugin should be a `class` (or its ES5 equivalent) with an `apply` method. The `apply` method will receive a `resolver` instance, that can be used to hook in to the event system.
Plugins are executed in a pipeline, and register which event they should be executed before/after. `source` is the name of the event that starts the pipeline, and `target` is what event this plugin should fire, which is what continues the execution of the pipeline. For a full view of how these plugin events form a chain, see `lib/ResolverFactory.js`, in the `//// pipeline ////` section.
### Built-in Plugins
`enhanced-resolve` ships with the following plugins. Most of them are wired up automatically by `ResolverFactory` based on the [resolver options](#resolver-options); the ones exported from the package entry (`TsconfigPathsPlugin`, `CloneBasenamePlugin`, `LogInfoPlugin`) are the ones you're most likely to use explicitly.
| Plugin | Purpose |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `AliasPlugin` | Replaces a matching request with one or more alternative targets. Powers the `alias` and `fallback` options. |
| `AliasFieldPlugin` | Applies aliasing based on a field in the description file (e.g. the `browser` field). Powers `aliasFields`. |
| `AppendPlugin` | Appends a string (typically an extension) to the current path. Used for `extensions`. |
| `CloneBasenamePlugin` | Joins the current directory basename onto the path (e.g. `/foo/bar` β `/foo/bar/bar`). Useful for directory-named main-file schemes. |
| `ConditionalPlugin` | Forwards the request only when it matches a given partial request shape. |
| `DescriptionFilePlugin` | Finds and loads the nearest description file (e.g. `package.json`) so other plugins can read its fields. Powers `descriptionFiles`. |
| `DirectoryExistsPlugin` | Only continues the pipeline if the current path is an existing directory. |
| `ExportsFieldPlugin` | Resolves requests through the `exports` field of a package's description file. Powers `exportsFields` and `conditionNames`. |
| `ExtensionAliasPlugin` | Maps one extension to a list of alternative extensions (e.g. `.js` β `.ts`, `.js`). Powers `extensionAlias`. |
| `FileExistsPlugin` | Only continues the pipeline if the current path is an existing file, and records the file as a dependency. |
| `ImportsFieldPlugin` | Resolves `#name` requests through the `imports` field of the enclosing package. |
| `JoinRequestPlugin` | Joins the current path with the current request into a new path. |
| `JoinRequestPartPlugin` | Splits a module request into module name + inner request, joining the inner request onto the path. |
| `LogInfoPlugin` | Emits verbose log output at a given pipeline step. Handy for debugging resolves via `resolveContext.log`. |
| `MainFieldPlugin` | Uses a field in the description file (e.g. `main`) to point to the entry file of a package. Powers `mainFields`. |
| `ModulesInHierarchicalDirectoriesPlugin` | Searches for a module by walking up parent directories (the standard `node_modules` lookup). Powers `modules`. |
| `ModulesInRootPlugin` | Searches for a module in a single absolute directory. Powers absolute-path entries in `modules`. |
| `NextPlugin` | Forwards the request from one hook to another without modification β glue between pipeline steps. |
| `ParsePlugin` | Parses a raw request string into its components (path, query, fragment, module flag, etc.). |
| `PnpPlugin` | Resolves module requests through a Yarn PnP API when one is available. |
| `RestrictionsPlugin` | Rejects results that don't match a list of path restrictions (strings or regular expressions). Powers `restrictions`. |
| `ResultPlugin` | Terminal plugin that fires the `result` hook β signals a successful resolve. |
| `RootsPlugin` | Resolves server-relative URL requests (starting with `/`) against one or more root directories. Powers `roots`. |
| `SelfReferencePlugin` | Resolves a package self-reference (e.g. `my-pkg/foo` from within `my-pkg`). |
| `SymlinkPlugin` | Real paths the resolved file by following symlinks. Can be disabled via the `symlinks` option. |
| `TryNextPlugin` | Forwards the request to the next hook with a log message. Useful for trying alternative resolutions. |
| `TsconfigPathsPlugin` | Rewrites requests using the `paths` and `baseUrl` from a `tsconfig.json`. Powers the `tsconfig` option. |
| `UnsafeCachePlugin` | Caches successful resolves in an in-memory map to speed up repeated requests. Powers `unsafeCache`. |
| `UseFilePlugin` | Joins a fixed filename onto the current path (e.g. `index`). Powers `mainFiles`. |
#### Plugin wiring and goals
One-line goal and default wiring (`source β target`) for each plugin. `*` means the plugin is tapped on several hooks β the common ones are listed. Plugins without a fixed wiring are user-tapped.
- **`AliasPlugin`** β Goal: redirect requests matching a configured key to an alternative target. `raw-resolve` β `internal-resolve` for `alias`; `file` β `internal-resolve` as a last-chance remap; `described-resolve` β `internal-resolve` for `fallback`.
- **`AliasFieldPlugin`** β Goal: apply aliases declared in a description-file field like `browser`, so environment-specific substitutions happen without user config. `raw-resolve` / `file` β `internal-resolve`.
- **`AppendPlugin`** β Goal: try appending a fixed string (usually an extension) to the current path. `raw-file` β `file`, one instance per entry in `extensions`.
- **`CloneBasenamePlugin`** β Goal: join the directory's basename onto the path (e.g. `/foo/bar` β `/foo/bar/bar`) for directory-named-main layouts. User-wired via `plugins`.
- **`ConditionalPlugin`** β Goal: gate a forward on a partial match of the request shape (e.g. `{ module: true }`), used to fan-out at branching hooks. Tapped on `after-normal-resolve`, `resolve-as-module`, `described-relative`, and `raw-file`.
- **`DescriptionFilePlugin`** β Goal: locate and attach the nearest description file (usually `package.json`) so downstream plugins can read its fields. `parsed-resolve` β `described-resolve`, `relative` β `described-relative`, `undescribed-resolve-in-package` β `resolve-in-package`, `undescribed-existing-directory` β `existing-directory`, `undescribed-raw-file` β `raw-file`.
- **`DirectoryExistsPlugin`** β Goal: only continue the pipeline if the current path exists as a directory. `resolve-as-module` β `undescribed-resolve-in-package`, `directory` β `undescribed-existing-directory`.
- **`ExportsFieldPlugin`** β Goal: map a request through the `exports` field of a package's description file (with `conditionNames`). `resolve-in-package` β `relative`.
- **`ExtensionAliasPlugin`** β Goal: rewrite a request's extension to a list of candidate extensions (e.g. `.js` β `.ts`, `.js`) for TypeScript ESM and similar. `raw-resolve` β `normal-resolve` for direct requests; also `imports-field-relative` β `relative` so extension substitution applies to `imports`-field targets.
- **`FileExistsPlugin`** β Goal: confirm a candidate path exists as a file and record it as a file dependency. `final-file` β `existing-file`.
- **`ImportsFieldPlugin`** β Goal: resolve `#name` requests through the `imports` field of the enclosing package. `internal` β `imports-field-relative` (relative target) or `imports-resolve` (bare target).
- **`JoinRequestPlugin`** β Goal: join the current path with the current request into a single concrete path. `after-normal-resolve` β `relative` (three stage-offset copies for `preferRelative`, `preferAbsolute`, and default), `resolve-in-existing-directory` β `relative`.
- **`JoinRequestPartPlugin`** β Goal: split a module request into module name + inner request, joining the inner part onto the path. `module` β `resolve-as-module`.
- **`LogInfoPlugin`** β Goal: emit verbose log output at a chosen hook; enable by passing a `log` function on `resolveContext`. User-wired via `plugins`.
- **`MainFieldPlugin`** β Goal: follow a description-file field (e.g. `main`, `module`, `browser`) to the entry file of a package. `existing-directory` β `resolve-in-existing-directory`, one instance per entry in `mainFields`.
- **`ModulesInHierarchicalDirectoriesPlugin`** β Goal: search for a module by walking up parent directories (the standard `node_modules` lookup). `raw-module` β `module`; when PnP is enabled, `alternate-raw-module` β `module` too.
- **`ModulesInRootPlugin`** β Goal: search for a module in a single absolute directory (powers absolute-path entries in `modules`). `raw-module` β `module`.
- **`NextPlugin`** β Goal: glue β forward the current request unchanged from one hook to another. Used across the pipeline wherever two hooks should run sequentially.
- **`ParsePlugin`** β Goal: split the raw request string into path / query / fragment / `module` / `directory` / `internal` flags. `resolve` β `parsed-resolve`; also wired on `internal-resolve` and `imports-resolve`.
- **`PnpPlugin`** β Goal: resolve bare-module requests through Yarn's PnP API when available. `raw-module` β `undescribed-resolve-in-package` on hit, `alternate-raw-module` on miss.
- **`RestrictionsPlugin`** β Goal: reject resolved paths that don't satisfy at least one string prefix or RegExp. Tapped on `resolved`.
- **`ResultPlugin`** β Goal: terminal plugin β fires the `result` lifecycle hook and signals a successful resolve. Tapped on `resolved`.
- **`RootsPlugin`** β Goal: resolve server-relative URL requests (starting with `/`) against one or more root directories. `after-normal-resolve` β `relative`.
- **`SelfReferencePlugin`** β Goal: resolve a package self-reference (`my-pkg/foo` from inside `my-pkg`) via its own `exports`. `raw-module` β `resolve-as-module`.
- **`SymlinkPlugin`** β Goal: real-path the resolved file by following symlinks; can be disabled via `symlinks: false`. `existing-file` β `existing-file` (runs via a stage offset on the same hook).
- **`TryNextPlugin`** β Goal: forward the request to another hook with a log message, useful for trying an alternative candidate. `raw-file` β `file` (as the "no extension" attempt) and user-wired.
- **`TsconfigPathsPlugin`** β Goal: rewrite requests using the `paths` and `baseUrl` from a `tsconfig.json` (including project references). Taps `described-resolve` internally and forwards to `internal-resolve`; exported for direct use as well.
- **`UnsafeCachePlugin`** β Goal: cache successful resolves in an in-memory map for repeated requests. `described-resolve` β `raw-resolve` (only when `unsafeCache` is enabled).
- **`UseFilePlugin`** β Goal: join a fixed filename (e.g. `index`) onto the current path to try as an entry file. `existing-directory` / `undescribed-existing-directory` β `undescribed-raw-file`, one instance per entry in `mainFiles`.
### Hooks
A resolver exposes two kinds of [`tapable`](https://github.com/webpack/tapable) hooks:
- **Lifecycle hooks** on `resolver.hooks` β fired by the resolver itself around each `resolve` call. Use these to observe, not to transform the request.
- **Pipeline hooks** β the named steps that plugins tap as `source` and forward to as `target`. Every pipeline hook is an `AsyncSeriesBailHook<[request, resolveContext], request | null>`: return `callback()` to pass on, `callback(err)` to fail, or `callback(null, request)` to short-circuit with a result. Obtain them with `resolver.ensureHook(name)` (creates if missing) or `resolver.getHook(name)` (throws if missing); names are kebab-case or camelCase and are interchangeable.
#### Lifecycle hooks
| Hook | Type | Fires when |
| ------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `resolveStep` | `SyncHook` | Every time the resolver hands a request to a pipeline hook. Arguments: `(hook, request)`. Ideal for tracing. |
| `noResolve` | `SyncHook` | When a top-level `resolve()` call can't produce a result. Arguments: `(request, error)`. |
| `resolve` | `AsyncSeriesBailHook` | Entry point of the pipeline (also listed below). Tap this to intercept requests before parsing. |
| `result` | `AsyncSeriesHook` | After a successful resolve, with the final request. Fired by `ResultPlugin`. Tap to observe/record results without short-circuiting. |
#### Pipeline hooks
Listed roughly in the order the default pipeline visits them. Full wiring lives in `lib/ResolverFactory.js` under `//// pipeline ////`.
| Hook | Role |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resolve` | Entry point. `ParsePlugin` parses the raw request (path, query, fragment, module flag) and forwards to `parsed-resolve`. |
| `internal-resolve` | Re-entry point used by internal rewrites (e.g. after an `alias` fires). Same role as `resolve`, but `fullySpecified` is forced off. |
| `imports-resolve` | Re-entry point for the target of an `imports` field match; prevents recursive `#` resolution per the Node.js ESM spec. |
| `parsed-resolve` | Request has been parsed. `DescriptionFilePlugin` attaches the nearest `package.json`, then forwards to `described-resolve`. |
| `described-resolve` | Description file is attached. Where `unsafeCache`, `fallback`, and most user plugins (including `MyLibSrcPlugin` below) hook in. |
| `raw-resolve` | After description. Where `alias`, `aliasFields`, `tsconfig` paths, and `extensionAlias` rewrites fire before default resolution. |
| `normal-resolve` | Default resolution starts. Branches into `relative` (for `./`, `../`, absolute), `raw-module` (bare modules), or `internal` (`#imports`). |
| `internal` | `#name` imports-field entry. `ImportsFieldPlugin` maps the specifier and forwards to `imports-field-relative` or `imports-resolve`. |
| `imports-field-relative` | Concrete path from an `imports`-field match, before the normal `relative` flow. `ExtensionAliasPlugin` taps here so `.js` β `.ts` also fires for `#name` targets. Forwards to `relative`. |
| `raw-module` | Bare-module lookup. `SelfReferencePlugin`, `ModulesInHierarchicalDirectoriesPlugin`, `ModulesInRootPlugin`, and `PnpPlugin` all tap here. |
| `alternate-raw-module` | Fallback module lookup used by `PnpPlugin` when PnP can't resolve and `node_modules` should be tried. |
| `module` | A candidate module directory was built. `JoinRequestPartPlugin` splits off the inner request and forwards to `resolve-as-module`. |
| `resolve-as-module` | Treat candidate as a package. `DirectoryExistsPlugin` gates on existence; short single-file modules may re-enter via `undescribed-raw-file`. |
| `undescribed-resolve-in-package` | Inside a located package directory, before its `package.json` has been read. Loads the description, forwards to `resolve-in-package`. |
| `resolve-in-package` | Inside a package with its description loaded. `ExportsFieldPlugin` matches `exports`, otherwise forwards to `resolve-in-existing-directory`. |
| `resolve-in-existing-directory` | Package directory confirmed; join the remaining request onto it and continue at `relative`. |
| `relative` | A concrete path on disk. `DescriptionFilePlugin` loads the nearest `package.json` and forwards to `described-relative`. |
| `described-relative` | Branches to `raw-file` (treat as file) and `directory` (treat as directory). `resolveToContext` skips the file branch. |
| `directory` | Candidate directory. `DirectoryExistsPlugin` gates on existence and forwards to `undescribed-existing-directory`. |
| `undescribed-existing-directory` | Existing directory, before its `package.json` has been read. `UseFilePlugin` tries `mainFiles` via `undescribed-raw-file`. |
| `existing-directory` | Existing directory with description loaded. `MainFieldPlugin` tries `mainFields`; `UseFilePlugin` falls back to `mainFiles`. |
| `undescribed-raw-file` | Candidate file path, before description is read. Loads description, then forwards to `raw-file`. |
| `raw-file` | Apply extension handling: `ConditionalPlugin` short-circuits when `fullySpecified`, `TryNextPlugin` + `AppendPlugin` try each extension. |
| `file` | A specific file path. Last place `alias` and `aliasFields` can redirect; forwards to `final-file`. |
| `final-file` | `FileExistsPlugin` checks the file is real and records it as a file dependency, then forwards to `existing-file`. |
| `existing-file` | Real file on disk. `SymlinkPlugin` real-paths symlinks (unless `symlinks: false`), then forwards to `resolved`. |
| `resolved` | Terminal hook. `RestrictionsPlugin` enforces `restrictions`; `ResultPlugin` fires the `result` lifecycle hook. |
#### `before-` and `after-` prefixes
`ensureHook("before-foo")` and `getHook("before-foo")` return the `foo` hook with `stage: -10`; `after-foo` returns it with `stage: 10`. Use this to tap earlier or later than the default stage without creating a separate hook. You'll see `after-parsed-resolve`, `after-normal-resolve`, `after-relative`, and `after-undescribed-resolve-in-package` used this way inside `ResolverFactory`.
#### Request flow by request type
The same 26 pipeline hooks serve every request, but different request shapes take different paths through them. Each `β` below is one `doResolve` / `NextPlugin` / plugin forward; `resolveStep` fires on every arrow, so tapping it (see [Hook examples](#hook-examples)) prints these chains live.
Relative path (`./utils` from `/src/index.js`) β the default "resolve on disk" path:
```text
resolve (ParsePlugin)
β parsed-resolve (DescriptionFilePlugin attaches nearest package.json)
β described-resolve (NextPlugin; or UnsafeCachePlugin short-circuit)
β raw-resolve (NextPlugin; alias/tsconfig would branch here)
β normal-resolve (JoinRequestPlugin: path=/src/utils, request="")
β relative (DescriptionFilePlugin loads /src/package.json)
β described-relative (branches to file and directory candidates)
ββ β raw-file (ConditionalPlugin / TryNextPlugin)
β β file (AppendPlugin tried each extension, e.g. .js)
β β final-file (FileExistsPlugin confirms the file)
β β existing-file (SymlinkPlugin real-paths it)
β β resolved (RestrictionsPlugin β ResultPlugin)
ββ β directory (DirectoryExistsPlugin; used when path is a dir)
β undescribed-existing-directory
β existing-directory (MainFieldPlugin tries "main", UseFilePlugin tries "index")
β undescribed-raw-file β raw-file β β¦
```
Bare module (`lodash/merge`) β walks up `node_modules`, then treats the hit as a package:
```text
resolve β parsed-resolve β described-resolve β raw-resolve β normal-resolve
β raw-module (ConditionalPlugin {module:true})
β module (ModulesInHierarchicalDirectoriesPlugin walks
/src/node_modules, /node_modules, β¦)
β resolve-as-module (JoinRequestPartPlugin splits "lodash" / "./merge")
β undescribed-resolve-in-package (DirectoryExistsPlugin gates on lodash/ existing)
β resolve-in-package (DescriptionFilePlugin loads lodash/package.json)
ββ β relative (ExportsFieldPlugin, if "exports" matches)
ββ β resolve-in-existing-directory (otherwise; JoinRequestPlugin joins "./merge")
β relative β β¦ (same tail as the relative flow above)
```
Internal import (`#util` from inside a package) β re-enters the pipeline after mapping:
```text
resolve β parsed-resolve β described-resolve β raw-resolve β normal-resolve
β internal (ConditionalPlugin {internal:true})
β imports-resolve (ImportsFieldPlugin mapped "#util" to a bare target)
β parsed-resolve β β¦ (fresh pipeline run, internal:false so # isn't remapped)
```
When the `imports` field maps to a relative target, the branch instead goes:
```text
β internal
β imports-field-relative (ImportsFieldPlugin mapped "#util" to "./util.js";
ExtensionAliasPlugin can swap .js β .ts here)
β relative β β¦ (same tail as the relative flow above)
```
Alias hit (`@/button` with `alias: { "@": "/src" }`) β rewrites then restarts:
```text
resolve β parsed-resolve β described-resolve
β raw-resolve
β internal-resolve (AliasPlugin rewrote request β "/src/button")
β parsed-resolve β β¦ (fullySpecified forced off; AliasPlugin won't re-fire for the rewritten form)
```
`exports`-field hit inside a package (`pkg/feature` matching `"./feature"` in `exports`):
```text
β¦ β raw-module β module β resolve-as-module β undescribed-resolve-in-package
β resolve-in-package
β relative (ExportsFieldPlugin jumped to the exports target;
main-field / main-file logic is skipped)
β described-relative β raw-file β file β final-file β existing-file β resolved
```
Failure β every candidate opts out (`callback()`) and no handler ever short-circuits with a result. `noResolve` fires once, for the top-level request:
```text
β¦ β final-file
β FileExistsPlugin: ENOENT (opts out; no extension candidates left)
β bail hooks unwind, each tapped handler has already tried its alternatives
β top-level resolve() returns no result
β resolver.hooks.noResolve(request, error) (ResultPlugin never fires)
```
#### Hook examples
Trace every pipeline step and observe failures via the lifecycle hooks:
```js
resolver.hooks.resolveStep.tap("Trace", (hook, request) => {
console.log(`[step] ${hook.name}: ${request.request} @ ${request.path}`);
});
resolver.hooks.noResolve.tap("Trace", (request, error) => {
console.log(`[fail] ${request.request}: ${error.message}`);
});
resolver.hooks.result.tapAsync("Trace", (request, _ctx, callback) => {
console.log(`[done] ${request.path}`);
callback();
});
```
Short-circuit at `file` to redirect any `.css` request to a stub without continuing the pipeline:
```js
class StubCssPlugin {
apply(resolver) {
resolver
.getHook("file")
.tapAsync("StubCssPlugin", (request, _ctx, callback) => {
if (!request.path || !request.path.endsWith(".css")) return callback();
callback(null, { ...request, path: require.resolve("./empty.css") });
});
}
}
```
Forward to a different hook with `doResolve` to restart resolution with a rewritten request β see `MyLibSrcPlugin` in [Writing a Custom Plugin](#writing-a-custom-plugin) for the canonical pattern (`getHook("described-resolve")` β `doResolve(ensureHook("resolve"), β¦)`).
### Writing a Custom Plugin
The example below adds a plugin that rewrites any request starting with `my-lib/` to `my-lib/src/`. It taps the `described-resolve` hook (after the description file has been located) and forwards the rewritten request to `resolve`, so the pipeline restarts with the new request.
```js
const fs = require("fs");
const { CachedInputFileSystem, ResolverFactory } = require("enhanced-resolve");
class MyLibSrcPlugin {
apply(resolver) {
const target = resolver.ensureHook("resolve");
resolver
.getHook("described-resolve")
.tapAsync("MyLibSrcPlugin", (request, resolveContext, callback) => {
if (!request.request || !request.request.startsWith("my-lib/")) {
return callback();
}
const newRequest = {
...request,
request: request.request.replace(/^my-lib\//, "my-lib/src/"),
};
resolver.doResolve(
target,
newRequest,
"rewrote my-lib β my-lib/src",
resolveContext,
callback,
);
});
}
}
const myResolver = ResolverFactory.createResolver({
fileSystem: new CachedInputFileSystem(fs, 4000),
extensions: [".js", ".json"],
plugins: [new MyLibSrcPlugin()],
});
```
Tips for writing your own plugin:
- Call `callback()` with no arguments to pass the request on to the next tapped handler at the same `source` hook. This is how you "opt out" when a request doesn't apply.
- Call `resolver.doResolve(target, newRequest, message, resolveContext, callback)` to continue the pipeline at a different hook with a (possibly modified) request.
- Return early with `callback(null, result)` to short-circuit with a specific result, or `callback(err)` to fail the resolve.
- See [Hooks](#hooks) for the full list of pipeline hooks, their order, and the `before-` / `after-` stage modifiers. `lib/ResolverFactory.js` has the exact wiring under `//// pipeline ////`.
## Escaping
It's allowed to escape `#` as `\0#` to avoid parsing it as fragment.
enhanced-resolve will try to resolve requests containing `#` as path and as fragment, so it will automatically figure out if `./some#thing` means `.../some.js#thing` or `.../some#thing.js`. When a `#` is resolved as path it will be escaped in the result. Here: `.../some\0#thing.js`.
## Tests
```sh
npm run test
```
## Passing options from webpack
If you are using `webpack`, and you want to pass custom options to `enhanced-resolve`, the options are passed from the `resolve` key of your webpack configuration e.g.:
```
resolve: {
extensions: ['.js', '.jsx'],
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
plugins: [new DirectoryNamedWebpackPlugin()]
...
},
```
## License
Copyright (c) 2012-2019 JS Foundation and other contributors
MIT (http://www.opensource.org/licenses/mit-license.php)
[npm]: https://img.shields.io/npm/v/enhanced-resolve.svg
[npm-url]: https://www.npmjs.com/package/enhanced-resolve
[build-status]: https://github.com/webpack/enhanced-resolve/actions/workflows/test.yml/badge.svg
[build-status-url]: https://github.com/webpack/enhanced-resolve/actions
[codecov-badge]: https://codecov.io/gh/webpack/enhanced-resolve/branch/main/graph/badge.svg?token=6B6NxtsZc3
[codecov-url]: https://codecov.io/gh/webpack/enhanced-resolve
[size]: https://packagephobia.com/badge?p=enhanced-resolve
[size-url]: https://packagephobia.com/result?p=enhanced-resolve
[discussion]: https://img.shields.io/github/discussions/webpack/webpack
[discussion-url]: https://github.com/webpack/webpack/discussions
|