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