Project Init

This commit is contained in:
Muluhabt
2026-05-29 15:23:46 +03:00
commit 2fbc557aac
67387 changed files with 6063341 additions and 0 deletions

BIN
node_modules/enhanced-resolve/.DS_Store generated vendored Normal file

Binary file not shown.

20
node_modules/enhanced-resolve/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,20 @@
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

784
node_modules/enhanced-resolve/README.md generated vendored Normal file
View File

@@ -0,0 +1,784 @@
# 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?, path, request, resolveContext?, callback)`
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.
```js
const resolve = require("enhanced-resolve");
resolve(__dirname, "./utils", (err, result) => {
// result === "/abs/path/to/utils.js"
});
```
#### `resolve.sync(context?, path, request, 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?, path, request, 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, 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.
```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 lookupStartPath = "/Users/webpack/some/root/dir";
const request = "./path/to-look-up.js";
const resolveContext = {};
// callback
myResolver.resolve(
context,
lookupStartPath,
request,
resolveContext,
(err /* Error */, filepath /* string */) => {
// Do something with the path
},
);
// promise
try {
const filepath = await myResolver.resolvePromise(
context,
lookupStartPath,
request,
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, lookupStartPath, request);
```
#### 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

123
node_modules/enhanced-resolve/lib/AliasFieldPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,123 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const DescriptionFileUtils = require("./DescriptionFileUtils");
const getInnerRequest = require("./getInnerRequest");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").JsonPrimitive} JsonPrimitive */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
// Sentinel stored in `_fieldDataCache` when a description file does not
// contain a usable alias field object. Lets us distinguish "not cached yet"
// from "no valid field" without calling back into `getField`.
const NO_FIELD_OBJECT = Symbol("NoFieldObject");
module.exports = class AliasFieldPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string | string[]} field field
* @param {string | ResolveStepHook} target target
*/
constructor(source, field, target) {
this.source = source;
this.field = field;
this.target = target;
// `this.field` is fixed for the plugin's lifetime, so caching
// per description-file content is safe. The cached value is either
// the resolved alias-map object or the `NO_FIELD_OBJECT` sentinel
// meaning "description file has no usable alias field".
/** @type {WeakMap<import("./Resolver").JsonObject, { [k: string]: JsonPrimitive } | typeof NO_FIELD_OBJECT>} */
this._fieldDataCache = new WeakMap();
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("AliasFieldPlugin", (request, resolveContext, callback) => {
if (!request.descriptionFileData) return callback();
const innerRequest = getInnerRequest(resolver, request);
if (!innerRequest) return callback();
const { descriptionFileData } = request;
let fieldData = this._fieldDataCache.get(descriptionFileData);
if (fieldData === undefined) {
const raw = DescriptionFileUtils.getField(
descriptionFileData,
this.field,
);
fieldData =
raw === null || typeof raw !== "object"
? NO_FIELD_OBJECT
: /** @type {{ [k: string]: JsonPrimitive }} */ (raw);
this._fieldDataCache.set(descriptionFileData, fieldData);
}
if (fieldData === NO_FIELD_OBJECT) {
if (resolveContext.log) {
resolveContext.log(
`Field '${this.field}' doesn't contain a valid alias configuration`,
);
}
return callback();
}
/** @type {JsonPrimitive | undefined} */
const data = Object.prototype.hasOwnProperty.call(
fieldData,
innerRequest,
)
? /** @type {{ [Key in string]: JsonPrimitive }} */ (fieldData)[
innerRequest
]
: innerRequest.startsWith("./")
? /** @type {{ [Key in string]: JsonPrimitive }} */ (fieldData)[
innerRequest.slice(2)
]
: undefined;
if (data === innerRequest) return callback();
if (data === undefined) return callback();
if (data === false) {
/** @type {ResolveRequest} */
const ignoreObj = {
...request,
path: false,
};
if (typeof resolveContext.yield === "function") {
resolveContext.yield(ignoreObj);
return callback(null, null);
}
return callback(null, ignoreObj);
}
/** @type {ResolveRequest} */
const obj = {
...request,
path: /** @type {string} */ (request.descriptionFileRoot),
request: /** @type {string} */ (data),
fullySpecified: false,
};
resolver.doResolve(
target,
obj,
`aliased from description file ${
request.descriptionFilePath
} with mapping '${innerRequest}' to '${/** @type {string} */ data}'`,
resolveContext,
(err, result) => {
if (err) return callback(err);
// Don't allow other aliasing or raw request
if (result === undefined) return callback(null, null);
callback(null, result);
},
);
});
}
};

63
node_modules/enhanced-resolve/lib/AliasPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,63 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {string | string[] | false} Alias */
/** @typedef {{ alias: Alias, name: string, onlyModule?: boolean }} AliasOption */
const { aliasResolveHandler, compileAliasOptions } = require("./AliasUtils");
/**
* When `alias` is given as an array, the targets are tried in priority
* order and the first matching one wins. Tried-and-failed higher-priority
* targets are recorded on `resolveContext.missingDependencies` (via the
* downstream `FileExistsPlugin`) so that a consumer's watcher can
* invalidate the resolve once one of them appears. The winning target is
* recorded on `resolveContext.fileDependencies`; its removal triggers
* re-resolution, at which point the fallback target is returned.
*
* Callers that cache successful resolves (e.g. webpack's `unsafeCache`)
* are responsible for invalidating those entries when the tracked
* dependencies change -- otherwise a stale path may survive across
* rebuilds even though this plugin itself would return the correct
* fallback on a fresh resolve.
*/
module.exports = class AliasPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {AliasOption | AliasOption[]} options options
* @param {string | ResolveStepHook} target target
*/
constructor(source, options, target) {
this.source = source;
this.options = Array.isArray(options) ? options : [options];
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
const compiled = compileAliasOptions(resolver, this.options);
resolver
.getHook(this.source)
.tapAsync("AliasPlugin", (request, resolveContext, callback) => {
aliasResolveHandler(
resolver,
compiled,
target,
request,
resolveContext,
callback,
);
});
}
};

333
node_modules/enhanced-resolve/lib/AliasUtils.js generated vendored Normal file
View File

@@ -0,0 +1,333 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const forEachBail = require("./forEachBail");
const { PathType, getType } = require("./util/path");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {import("./Resolver").ResolveCallback} ResolveCallback */
/** @typedef {string | string[] | false} Alias */
/** @typedef {{ alias: Alias, name: string, onlyModule?: boolean }} AliasOption */
/**
* @typedef {object} CompiledAliasOption
* @property {string} name original alias name
* @property {string} nameWithSlash name + "/" — precomputed to avoid per-resolve concat
* @property {Alias} alias alias target(s)
* @property {boolean} onlyModule normalized onlyModule flag
* @property {string | null} absolutePath absolute form of `name` (with slash ending), null when not absolute
* @property {string | null} wildcardPrefix substring before the single "*" in `name`, null when no wildcard
* @property {string | null} wildcardSuffix substring after the single "*" in `name`, null when no wildcard
* @property {number} firstCharCode first character code of `name` — used as a cheap screen on the hot path. `-1` indicates "matches any first char" (empty wildcard prefix).
* @property {boolean} arrayAlias true when `alias` is an array — precomputed so the hot path skips `Array.isArray`
*/
/**
* Bucketed view of compiled options used by `aliasResolveHandler` to avoid
* walking the full option list on every resolve. The `all` array preserves
* the legacy linear order (declaration order) for the fallback path. The
* `byFirstChar` map buckets options by the first char code of their `name`
* — each bucket preserves declaration order among its members. The
* `hasAnyFirstChar` flag is true when at least one option matches any
* first char (`firstCharCode === -1`), in which case resolve-time scans
* fall back to `all` to keep declaration-order semantics across buckets.
* The `useBuckets` flag is true only when bucketing would actually help —
* i.e. there are at least 2 distinct first chars AND no empty-prefix
* wildcard. When false, the resolve hot path skips the `Map.get` and
* iterates `all` directly with the per-option first-char-code screen
* (matching the pre-bucketing behavior). This avoids paying for `Map.get`
* on degenerate single-bucket lists like a long chain of aliases that
* all share one first char — the bucket lookup adds overhead without
* narrowing the candidate set, which showed up as a transient-memory
* regression on `pathological-deep-stack`.
* @typedef {object} CompiledAliasOptions
* @property {CompiledAliasOption[]} all declaration-ordered list
* @property {Map<number, CompiledAliasOption[]>} byFirstChar bucketed by first char code
* @property {boolean} hasAnyFirstChar true when an empty-prefix wildcard is present
* @property {boolean} useBuckets true when the bucket fast-path should be used at resolve time
*/
const EMPTY_LIST = /** @type {CompiledAliasOption[]} */ ([]);
const EMPTY_COMPILED_OPTIONS = /** @type {CompiledAliasOptions} */ ({
all: EMPTY_LIST,
byFirstChar: new Map(),
hasAnyFirstChar: false,
useBuckets: false,
});
/**
* Precompute per-option strings used on every resolve so the hot path in
* `aliasResolveHandler` does no string concatenation / split work per entry.
* Called once per plugin apply — the returned structure is stable for the
* lifetime of the resolver.
*
* Beyond the per-option precompute step, this also partitions the list into
* a `byFirstChar` map so that, when no "empty-prefix" wildcards are
* present, the resolve-time scan only walks options whose `name` starts
* with the same char as the current request. For large alias lists (300+
* entries) this turns an O(N) screen into O(K) where K is the bucket size
* for the request's first char.
* @param {Resolver} resolver resolver
* @param {AliasOption[]} options options
* @returns {CompiledAliasOptions} compiled options
*/
function compileAliasOptions(resolver, options) {
if (options.length === 0) return EMPTY_COMPILED_OPTIONS;
const all = /** @type {CompiledAliasOption[]} */ (
Array.from({ length: options.length })
);
/** @type {Map<number, CompiledAliasOption[]>} */
const byFirstChar = new Map();
let hasAnyFirstChar = false;
for (let i = 0; i < options.length; i++) {
const item = options[i];
const { name } = item;
let absolutePath = null;
const type = getType(name);
if (type === PathType.AbsolutePosix || type === PathType.AbsoluteWin) {
absolutePath = resolver.join(name, "_").slice(0, -1);
}
const firstStar = name.indexOf("*");
let wildcardPrefix = null;
let wildcardSuffix = null;
if (firstStar !== -1 && !name.includes("*", firstStar + 1)) {
wildcardPrefix = name.slice(0, firstStar);
wildcardSuffix = name.slice(firstStar + 1);
}
// firstCharCode: used by `aliasResolveHandler` to quickly skip aliases
// whose name can't possibly match the current innerRequest. For a plain
// alias (no wildcard) the first char of the name is also the first char
// of `nameWithSlash` and of `absolutePath` (since the latter is derived
// from name via `resolver.join(name, "_")`, which only appends). For a
// wildcard with a non-empty prefix, the first char of that prefix is
// also the first char of name. Only the `name === "*"` case (empty
// wildcard prefix) can match arbitrary first chars — encode that as -1.
let firstCharCode;
if (wildcardPrefix !== null && wildcardPrefix.length === 0) {
firstCharCode = -1;
} else {
firstCharCode = name.length > 0 ? name.charCodeAt(0) : -1;
}
const compiled = {
name,
nameWithSlash: `${name}/`,
alias: item.alias,
onlyModule: Boolean(item.onlyModule),
absolutePath,
wildcardPrefix,
wildcardSuffix,
firstCharCode,
arrayAlias: Array.isArray(item.alias),
};
all[i] = compiled;
if (firstCharCode === -1) {
hasAnyFirstChar = true;
} else {
let bucket = byFirstChar.get(firstCharCode);
if (bucket === undefined) {
bucket = [];
byFirstChar.set(firstCharCode, bucket);
}
bucket.push(compiled);
}
}
// Only enable the bucket fast-path when it would actually help. With
// a single bucket (all aliases share one first char, e.g. a chain of
// `chain-0 -> chain-1 -> …` rewrites), the resolve-time `Map.get`
// does no discrimination — every request lands in that one bucket
// or in nothing — and the lookup is overhead compared to walking
// `all` with the per-option first-char-code screen. Requiring 2+
// distinct first chars matches the cases where bucketing has
// measurable benefit (huge-alias-* / large-alias-list / stack-churn).
const useBuckets = !hasAnyFirstChar && byFirstChar.size >= 2;
return { all, byFirstChar, hasAnyFirstChar, useBuckets };
}
/** @typedef {(err?: null | Error, result?: null | ResolveRequest) => void} InnerCallback */
/**
* @param {Resolver} resolver resolver
* @param {CompiledAliasOptions} options compiled options
* @param {ResolveStepHook} target target
* @param {ResolveRequest} request request
* @param {ResolveContext} resolveContext resolve context
* @param {InnerCallback} callback callback
* @returns {void}
*/
function aliasResolveHandler(
resolver,
options,
target,
request,
resolveContext,
callback,
) {
if (options.all.length === 0) return callback();
const innerRequest = request.request || request.path;
if (!innerRequest) return callback();
// Precompute values used in the inner scan loop so we don't recompute
// them per option. This is meaningful when `options` has hundreds of
// entries (e.g. monorepos with generated alias lists) — see the
// `huge-alias-list` / `huge-alias-miss` benchmarks.
const innerFirstCharCode = innerRequest.charCodeAt(0);
const hasRequestString = Boolean(request.request);
// Dispatch through the first-char-code bucket when it actually
// narrows the candidate set (`useBuckets` requires 2+ distinct
// first chars and no empty-prefix wildcard). When the field has
// only one first-char bucket — e.g. a long chain of `chain-N`
// aliases that all start with the same char — every request lands
// in that one bucket or nothing, so `Map.get` is overhead vs. just
// walking `all` with the per-option char-code screen. Walking
// `all` also matches the pre-bucketing behavior and keeps the
// `pathological-deep-stack` allocation profile flat.
let scan;
if (options.useBuckets) {
const bucket = options.byFirstChar.get(innerFirstCharCode);
if (bucket === undefined) return callback();
scan = bucket;
} else {
scan = options.all;
}
forEachBail(
scan,
(item, callback) => {
// Char-code screen left in for the fallback (`options.all`) path
// where the bucket dispatch above wasn't usable. In the bucket
// path this is always true and folds into a no-op.
const { firstCharCode } = item;
if (firstCharCode !== -1 && firstCharCode !== innerFirstCharCode) {
return callback();
}
/** @type {boolean} */
let shouldStop = false;
// For absolute-name aliases, accept the normalized
// `absolutePath` form as well as the raw `nameWithSlash`.
// `nameWithSlash` unconditionally appends `/`, so a raw
// windows request with native backslashes
// (e.g. `C:\\abs\\foo\\baz` against `name: "C:\\abs\\foo"`)
// otherwise fails `startsWith("C:\\abs\\foo/")` and is
// silently skipped. Mirroring the `absolutePath` check in
// both branches closes the gap without changing any
// existing matches.
const { absolutePath } = item;
const matchRequest =
innerRequest === item.name ||
(!item.onlyModule &&
((hasRequestString && innerRequest.startsWith(item.nameWithSlash)) ||
(absolutePath !== null && innerRequest.startsWith(absolutePath))));
const matchWildcard = !item.onlyModule && item.wildcardPrefix !== null;
if (matchRequest || matchWildcard) {
/**
* @param {Alias} alias alias
* @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
* @returns {void}
*/
const resolveWithAlias = (alias, callback) => {
if (alias === false) {
/** @type {ResolveRequest} */
const ignoreObj = {
...request,
path: false,
};
if (typeof resolveContext.yield === "function") {
resolveContext.yield(ignoreObj);
return callback(null, null);
}
return callback(null, ignoreObj);
}
let newRequestStr;
if (
matchWildcard &&
innerRequest.startsWith(
/** @type {string} */ (item.wildcardPrefix),
) &&
innerRequest.endsWith(/** @type {string} */ (item.wildcardSuffix))
) {
const match = innerRequest.slice(
/** @type {string} */ (item.wildcardPrefix).length,
innerRequest.length -
/** @type {string} */ (item.wildcardSuffix).length,
);
newRequestStr = alias.toString().replace("*", match);
}
if (
matchRequest &&
innerRequest !== alias &&
!innerRequest.startsWith(`${alias}/`)
) {
/** @type {string} */
const remainingRequest = innerRequest.slice(item.name.length);
newRequestStr = alias + remainingRequest;
}
if (newRequestStr !== undefined) {
shouldStop = true;
/** @type {ResolveRequest} */
const obj = {
...request,
request: newRequestStr,
fullySpecified: false,
};
return resolver.doResolve(
target,
obj,
`aliased with mapping '${item.name}': '${alias}' to '${newRequestStr}'`,
resolveContext,
(err, result) => {
if (err) return callback(err);
if (result) return callback(null, result);
return callback();
},
);
}
return callback();
};
/**
* @param {(null | Error)=} err error
* @param {(null | ResolveRequest)=} result result
* @returns {void}
*/
const stoppingCallback = (err, result) => {
if (err) return callback(err);
if (result) return callback(null, result);
// Don't allow other aliasing or raw request
if (shouldStop) return callback(null, null);
return callback();
};
if (item.arrayAlias) {
return forEachBail(
/** @type {string[]} */ (item.alias),
resolveWithAlias,
stoppingCallback,
);
}
return resolveWithAlias(item.alias, stoppingCallback);
}
return callback();
},
callback,
);
}
module.exports.aliasResolveHandler = aliasResolveHandler;
module.exports.compileAliasOptions = compileAliasOptions;

49
node_modules/enhanced-resolve/lib/AppendPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,49 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class AppendPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string} appending appending
* @param {string | ResolveStepHook} target target
*/
constructor(source, appending, target) {
this.source = source;
this.appending = appending;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("AppendPlugin", (request, resolveContext, callback) => {
/** @type {ResolveRequest} */
const obj = {
...request,
path: request.path + this.appending,
relativePath:
request.relativePath && request.relativePath + this.appending,
};
resolver.doResolve(
target,
obj,
this.appending,
resolveContext,
callback,
);
});
}
};

View File

@@ -0,0 +1,735 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
// eslint-disable-next-line n/prefer-global/process
const { nextTick } = require("process");
/** @typedef {import("./Resolver").FileSystem} FileSystem */
/** @typedef {import("./Resolver").PathLike} PathLike */
/** @typedef {import("./Resolver").PathOrFileDescriptor} PathOrFileDescriptor */
/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
/** @typedef {FileSystem & SyncFileSystem} BaseFileSystem */
/**
* @template T
* @typedef {import("./Resolver").FileSystemCallback<T>} FileSystemCallback<T>
*/
/**
* @param {string} path path
* @returns {string} dirname
*/
const dirname = (path) => {
let idx = path.length - 1;
while (idx >= 0) {
const char = path.charCodeAt(idx);
// slash or backslash
if (char === 47 || char === 92) break;
idx--;
}
if (idx < 0) return "";
return path.slice(0, idx);
};
/**
* @template T
* @param {FileSystemCallback<T>[]} callbacks callbacks
* @param {Error | null} err error
* @param {T} result result
*/
const runCallbacks = (callbacks, err, result) => {
if (callbacks.length === 1) {
callbacks[0](err, result);
callbacks.length = 0;
return;
}
let error;
for (const callback of callbacks) {
try {
callback(err, result);
} catch (err) {
if (!error) error = err;
}
}
callbacks.length = 0;
if (error) throw error;
};
// eslint-disable-next-line jsdoc/reject-function-type
/** @typedef {Function} EXPECTED_FUNCTION */
// eslint-disable-next-line jsdoc/reject-any-type
/** @typedef {any} EXPECTED_ANY */
class OperationMergerBackend {
/**
* @param {EXPECTED_FUNCTION | undefined} provider async method in filesystem
* @param {EXPECTED_FUNCTION | undefined} syncProvider sync method in filesystem
* @param {BaseFileSystem} providerContext call context for the provider methods
*/
constructor(provider, syncProvider, providerContext) {
this._provider = provider;
this._syncProvider = syncProvider;
this._providerContext = providerContext;
this._activeAsyncOperations = new Map();
this.provide = this._provider
? // Comment to align jsdoc
/**
* @param {PathLike | PathOrFileDescriptor} path path
* @param {object | FileSystemCallback<EXPECTED_ANY> | undefined} options options
* @param {FileSystemCallback<EXPECTED_ANY>=} callback callback
* @returns {EXPECTED_ANY} result
*/
(path, options, callback) => {
if (typeof options === "function") {
callback =
/** @type {FileSystemCallback<EXPECTED_ANY>} */
(options);
options = undefined;
}
if (
typeof path !== "string" &&
!Buffer.isBuffer(path) &&
!(path instanceof URL) &&
typeof path !== "number"
) {
/** @type {EXPECTED_FUNCTION} */
(callback)(
new TypeError("path must be a string, Buffer, URL or number"),
);
return;
}
if (options) {
return /** @type {EXPECTED_FUNCTION} */ (this._provider).call(
this._providerContext,
path,
options,
callback,
);
}
let callbacks = this._activeAsyncOperations.get(path);
if (callbacks) {
callbacks.push(callback);
return;
}
this._activeAsyncOperations.set(path, (callbacks = [callback]));
/** @type {EXPECTED_FUNCTION} */
(provider)(
path,
/**
* @param {Error} err error
* @param {EXPECTED_ANY} result result
*/
(err, result) => {
this._activeAsyncOperations.delete(path);
runCallbacks(callbacks, err, result);
},
);
}
: null;
this.provideSync = this._syncProvider
? // Comment to align jsdoc
/**
* @param {PathLike | PathOrFileDescriptor} path path
* @param {object=} options options
* @returns {EXPECTED_ANY} result
*/
(path, options) =>
/** @type {EXPECTED_FUNCTION} */ (this._syncProvider).call(
this._providerContext,
path,
options,
)
: null;
}
purge() {}
purgeParent() {}
}
/*
IDLE:
insert data: goto SYNC
SYNC:
before provide: run ticks
event loop tick: goto ASYNC_ACTIVE
ASYNC:
timeout: run tick, goto ASYNC_PASSIVE
ASYNC_PASSIVE:
before provide: run ticks
IDLE --[insert data]--> SYNC --[event loop tick]--> ASYNC_ACTIVE --[interval tick]-> ASYNC_PASSIVE
^ |
+---------[insert data]-------+
*/
const STORAGE_MODE_IDLE = 0;
const STORAGE_MODE_SYNC = 1;
const STORAGE_MODE_ASYNC = 2;
/**
* @callback Provide
* @param {PathLike | PathOrFileDescriptor} path path
* @param {EXPECTED_ANY} options options
* @param {FileSystemCallback<EXPECTED_ANY>} callback callback
* @returns {void}
*/
class CacheBackend {
/**
* @param {number} duration max cache duration of items
* @param {EXPECTED_FUNCTION | undefined} provider async method
* @param {EXPECTED_FUNCTION | undefined} syncProvider sync method
* @param {BaseFileSystem} providerContext call context for the provider methods
*/
constructor(duration, provider, syncProvider, providerContext) {
this._duration = duration;
this._provider = provider;
this._syncProvider = syncProvider;
this._providerContext = providerContext;
/** @type {Map<string, FileSystemCallback<EXPECTED_ANY>[]>} */
this._activeAsyncOperations = new Map();
/** @type {Map<string, { err: Error | null, result?: EXPECTED_ANY, level: Set<string> }>} */
this._data = new Map();
/** @type {Set<string>[]} */
this._levels = [];
for (let i = 0; i < 10; i++) this._levels.push(new Set());
if (duration !== Infinity) {
for (let i = 5000; i < duration; i += 500) {
this._levels.push(new Set());
}
}
this._currentLevel = 0;
this._tickInterval = Math.floor(duration / this._levels.length);
/** @type {STORAGE_MODE_IDLE | STORAGE_MODE_SYNC | STORAGE_MODE_ASYNC} */
this._mode = STORAGE_MODE_IDLE;
/** @type {NodeJS.Timeout | undefined} */
this._timeout = undefined;
/** @type {number | undefined} */
this._nextDecay = undefined;
// eslint-disable-next-line no-warning-comments
// @ts-ignore
this.provide = provider ? this.provide.bind(this) : null;
// eslint-disable-next-line no-warning-comments
// @ts-ignore
this.provideSync = syncProvider ? this.provideSync.bind(this) : null;
}
/**
* @param {PathLike | PathOrFileDescriptor} path path
* @param {EXPECTED_ANY} options options
* @param {FileSystemCallback<EXPECTED_ANY>} callback callback
* @returns {void}
*/
provide(path, options, callback) {
if (typeof options === "function") {
callback = options;
options = undefined;
}
if (
typeof path !== "string" &&
!Buffer.isBuffer(path) &&
!(path instanceof URL) &&
typeof path !== "number"
) {
callback(new TypeError("path must be a string, Buffer, URL or number"));
return;
}
const strPath = typeof path !== "string" ? path.toString() : path;
if (options) {
return /** @type {EXPECTED_FUNCTION} */ (this._provider).call(
this._providerContext,
path,
options,
callback,
);
}
// When in sync mode we can move to async mode
if (this._mode === STORAGE_MODE_SYNC) {
this._enterAsyncMode();
}
// Check in cache
const cacheEntry = this._data.get(strPath);
if (cacheEntry !== undefined) {
if (cacheEntry.err) return nextTick(callback, cacheEntry.err);
return nextTick(callback, null, cacheEntry.result);
}
// Check if there is already the same operation running
let callbacks = this._activeAsyncOperations.get(strPath);
if (callbacks !== undefined) {
callbacks.push(callback);
return;
}
this._activeAsyncOperations.set(strPath, (callbacks = [callback]));
// Run the operation
/** @type {EXPECTED_FUNCTION} */
(this._provider).call(
this._providerContext,
path,
/**
* @param {Error | null} err error
* @param {EXPECTED_ANY=} result result
*/
(err, result) => {
this._activeAsyncOperations.delete(strPath);
this._storeResult(strPath, err, result);
// Enter async mode if not yet done
this._enterAsyncMode();
runCallbacks(
/** @type {FileSystemCallback<EXPECTED_ANY>[]} */ (callbacks),
err,
result,
);
},
);
}
/**
* @param {PathLike | PathOrFileDescriptor} path path
* @param {EXPECTED_ANY} options options
* @returns {EXPECTED_ANY} result
*/
provideSync(path, options) {
if (
typeof path !== "string" &&
!Buffer.isBuffer(path) &&
!(path instanceof URL) &&
typeof path !== "number"
) {
throw new TypeError("path must be a string");
}
const strPath = typeof path !== "string" ? path.toString() : path;
if (options) {
return /** @type {EXPECTED_FUNCTION} */ (this._syncProvider).call(
this._providerContext,
path,
options,
);
}
// In sync mode we may have to decay some cache items
if (this._mode === STORAGE_MODE_SYNC) {
this._runDecays();
}
// Check in cache
const cacheEntry = this._data.get(strPath);
if (cacheEntry !== undefined) {
if (cacheEntry.err) throw cacheEntry.err;
return cacheEntry.result;
}
// Get all active async operations
// This sync operation will also complete them
const callbacks = this._activeAsyncOperations.get(strPath);
this._activeAsyncOperations.delete(strPath);
// Run the operation
// When in idle mode, we will enter sync mode
let result;
try {
result = /** @type {EXPECTED_FUNCTION} */ (this._syncProvider).call(
this._providerContext,
path,
);
} catch (err) {
this._storeResult(strPath, /** @type {Error} */ (err), undefined);
this._enterSyncModeWhenIdle();
if (callbacks) {
runCallbacks(callbacks, /** @type {Error} */ (err), undefined);
}
throw err;
}
this._storeResult(strPath, null, result);
this._enterSyncModeWhenIdle();
if (callbacks) {
runCallbacks(callbacks, null, result);
}
return result;
}
/**
* @param {(string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>)=} what what to purge
* @param {{ exact?: boolean }=} options options; `exact: true` removes only entries whose key matches `what` exactly instead of any entry whose key starts with `what`
*/
purge(what, options) {
if (what === undefined || what === null) {
if (this._mode !== STORAGE_MODE_IDLE) {
this._data.clear();
for (const level of this._levels) {
level.clear();
}
this._enterIdleMode();
}
return;
}
const exact =
options !== undefined && options !== null && options.exact === true;
if (exact) {
if (
typeof what === "string" ||
Buffer.isBuffer(what) ||
what instanceof URL ||
typeof what === "number"
) {
const strWhat = typeof what !== "string" ? what.toString() : what;
const data = this._data.get(strWhat);
if (data !== undefined) {
this._data.delete(strWhat);
data.level.delete(strWhat);
}
} else {
for (const item of what) {
const strItem = typeof item !== "string" ? item.toString() : item;
const data = this._data.get(strItem);
if (data !== undefined) {
this._data.delete(strItem);
data.level.delete(strItem);
}
}
}
if (this._data.size === 0) {
this._enterIdleMode();
}
return;
}
if (
typeof what === "string" ||
Buffer.isBuffer(what) ||
what instanceof URL ||
typeof what === "number"
) {
const strWhat = typeof what !== "string" ? what.toString() : what;
if (strWhat === "") {
// empty string is a prefix of every key — short-circuit the O(n) scan
if (this._mode !== STORAGE_MODE_IDLE) {
this._data.clear();
for (const level of this._levels) {
level.clear();
}
this._enterIdleMode();
}
return;
}
for (const [key, data] of this._data) {
if (key.startsWith(strWhat)) {
this._data.delete(key);
data.level.delete(key);
}
}
if (this._data.size === 0) {
this._enterIdleMode();
}
} else {
for (const [key, data] of this._data) {
for (const item of what) {
const strItem = typeof item !== "string" ? item.toString() : item;
if (key.startsWith(strItem)) {
this._data.delete(key);
data.level.delete(key);
break;
}
}
}
if (this._data.size === 0) {
this._enterIdleMode();
}
}
}
/**
* @param {(string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>)=} what what to purge
*/
purgeParent(what) {
if (what === undefined || what === null) {
this.purge();
} else if (
typeof what === "string" ||
Buffer.isBuffer(what) ||
what instanceof URL ||
typeof what === "number"
) {
const strWhat = typeof what !== "string" ? what.toString() : what;
this.purge(dirname(strWhat));
} else {
const set = new Set();
for (const item of what) {
const strItem = typeof item !== "string" ? item.toString() : item;
set.add(dirname(strItem));
}
this.purge(set);
}
}
/**
* @param {string} path path
* @param {Error | null} err error
* @param {EXPECTED_ANY} result result
*/
_storeResult(path, err, result) {
if (this._data.has(path)) return;
const level = this._levels[this._currentLevel];
this._data.set(path, { err, result, level });
level.add(path);
}
_decayLevel() {
const nextLevel = (this._currentLevel + 1) % this._levels.length;
const decay = this._levels[nextLevel];
this._currentLevel = nextLevel;
for (const item of decay) {
this._data.delete(item);
}
decay.clear();
if (this._data.size === 0) {
this._enterIdleMode();
} else {
/** @type {number} */
(this._nextDecay) += this._tickInterval;
}
}
_runDecays() {
while (
/** @type {number} */ (this._nextDecay) <= Date.now() &&
this._mode !== STORAGE_MODE_IDLE
) {
this._decayLevel();
}
}
_enterAsyncMode() {
let timeout = 0;
switch (this._mode) {
case STORAGE_MODE_ASYNC:
return;
case STORAGE_MODE_IDLE:
this._nextDecay = Date.now() + this._tickInterval;
timeout = this._tickInterval;
break;
case STORAGE_MODE_SYNC:
this._runDecays();
// _runDecays may change the mode
if (
/** @type {STORAGE_MODE_IDLE | STORAGE_MODE_SYNC | STORAGE_MODE_ASYNC} */
(this._mode) === STORAGE_MODE_IDLE
) {
return;
}
timeout = Math.max(
0,
/** @type {number} */ (this._nextDecay) - Date.now(),
);
break;
}
this._mode = STORAGE_MODE_ASYNC;
// When duration is Infinity, cache entries never expire, so there
// is no need to schedule a decay timer.
if (this._duration === Infinity) {
return;
}
const ref = setTimeout(() => {
this._mode = STORAGE_MODE_SYNC;
this._runDecays();
}, timeout);
if (ref.unref) ref.unref();
this._timeout = ref;
}
_enterSyncModeWhenIdle() {
if (this._mode === STORAGE_MODE_IDLE) {
this._mode = STORAGE_MODE_SYNC;
this._nextDecay = Date.now() + this._tickInterval;
}
}
_enterIdleMode() {
this._mode = STORAGE_MODE_IDLE;
this._nextDecay = undefined;
if (this._timeout) clearTimeout(this._timeout);
}
}
/**
* @template {EXPECTED_FUNCTION} Provider
* @template {EXPECTED_FUNCTION} AsyncProvider
* @template FileSystem
* @param {number} duration duration in ms files are cached
* @param {Provider | undefined} provider provider
* @param {AsyncProvider | undefined} syncProvider sync provider
* @param {BaseFileSystem} providerContext provider context
* @returns {OperationMergerBackend | CacheBackend} backend
*/
const createBackend = (duration, provider, syncProvider, providerContext) => {
if (duration > 0) {
return new CacheBackend(duration, provider, syncProvider, providerContext);
}
return new OperationMergerBackend(provider, syncProvider, providerContext);
};
module.exports = class CachedInputFileSystem {
/**
* @param {BaseFileSystem} fileSystem file system
* @param {number} duration duration in ms files are cached
*/
constructor(fileSystem, duration) {
this.fileSystem = fileSystem;
this._lstatBackend = createBackend(
duration,
this.fileSystem.lstat,
this.fileSystem.lstatSync,
this.fileSystem,
);
const lstat = this._lstatBackend.provide;
this.lstat = /** @type {FileSystem["lstat"]} */ (lstat);
const lstatSync = this._lstatBackend.provideSync;
this.lstatSync = /** @type {SyncFileSystem["lstatSync"]} */ (lstatSync);
this._statBackend = createBackend(
duration,
this.fileSystem.stat,
this.fileSystem.statSync,
this.fileSystem,
);
const stat = this._statBackend.provide;
this.stat = /** @type {FileSystem["stat"]} */ (stat);
const statSync = this._statBackend.provideSync;
this.statSync = /** @type {SyncFileSystem["statSync"]} */ (statSync);
this._readdirBackend = createBackend(
duration,
this.fileSystem.readdir,
this.fileSystem.readdirSync,
this.fileSystem,
);
const readdir = this._readdirBackend.provide;
this.readdir = /** @type {FileSystem["readdir"]} */ (readdir);
const readdirSync = this._readdirBackend.provideSync;
this.readdirSync = /** @type {SyncFileSystem["readdirSync"]} */ (
readdirSync
);
this._readFileBackend = createBackend(
duration,
this.fileSystem.readFile,
this.fileSystem.readFileSync,
this.fileSystem,
);
const readFile = this._readFileBackend.provide;
this.readFile = /** @type {FileSystem["readFile"]} */ (readFile);
const readFileSync = this._readFileBackend.provideSync;
this.readFileSync = /** @type {SyncFileSystem["readFileSync"]} */ (
readFileSync
);
this._readJsonBackend = createBackend(
duration,
// prettier-ignore
this.fileSystem.readJson ||
(this.readFile &&
(
/**
* @param {string} path path
* @param {FileSystemCallback<EXPECTED_ANY>} callback callback
*/
(path, callback) => {
this.readFile(path, (err, buffer) => {
if (err) return callback(err);
if (!buffer || buffer.length === 0)
{return callback(new Error("No file content"));}
let data;
try {
data = JSON.parse(buffer.toString("utf8"));
} catch (err_) {
return callback(/** @type {Error} */ (err_));
}
callback(null, data);
});
})
),
// prettier-ignore
this.fileSystem.readJsonSync ||
(this.readFileSync &&
(
/**
* @param {string} path path
* @returns {EXPECTED_ANY} result
*/
(path) => {
const buffer = this.readFileSync(path);
const data = JSON.parse(buffer.toString("utf8"));
return data;
}
)),
this.fileSystem,
);
const readJson = this._readJsonBackend.provide;
this.readJson = /** @type {FileSystem["readJson"]} */ (readJson);
const readJsonSync = this._readJsonBackend.provideSync;
this.readJsonSync = /** @type {SyncFileSystem["readJsonSync"]} */ (
readJsonSync
);
this._readlinkBackend = createBackend(
duration,
this.fileSystem.readlink,
this.fileSystem.readlinkSync,
this.fileSystem,
);
const readlink = this._readlinkBackend.provide;
this.readlink = /** @type {FileSystem["readlink"]} */ (readlink);
const readlinkSync = this._readlinkBackend.provideSync;
this.readlinkSync = /** @type {SyncFileSystem["readlinkSync"]} */ (
readlinkSync
);
this._realpathBackend = createBackend(
duration,
this.fileSystem.realpath,
this.fileSystem.realpathSync,
this.fileSystem,
);
const realpath = this._realpathBackend.provide;
this.realpath = /** @type {FileSystem["realpath"]} */ (realpath);
const realpathSync = this._realpathBackend.provideSync;
this.realpathSync = /** @type {SyncFileSystem["realpathSync"]} */ (
realpathSync
);
}
/**
* @param {(string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>)=} what what to purge
* @param {{ exact?: boolean }=} options options; `exact: true` removes only cache entries whose key matches `what` exactly instead of any entry whose key starts with `what`
*/
purge(what, options) {
this._statBackend.purge(what, options);
this._lstatBackend.purge(what, options);
if (options !== undefined && options !== null && options.exact === true) {
this._readdirBackend.purge(what, options);
} else {
this._readdirBackend.purgeParent(what);
}
this._readFileBackend.purge(what, options);
this._readlinkBackend.purge(what, options);
this._readJsonBackend.purge(what, options);
this._realpathBackend.purge(what, options);
}
};

View File

@@ -0,0 +1,51 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class CloneBasenamePlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string | ResolveStepHook} target target
*/
constructor(source, target) {
this.source = source;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("CloneBasenamePlugin", (request, resolveContext, callback) => {
const requestPath = /** @type {string} */ (request.path);
const filename = resolver.basename(requestPath);
const filePath = resolver.join(requestPath, filename);
/** @type {ResolveRequest} */
const obj = {
...request,
path: filePath,
relativePath:
request.relativePath &&
resolver.join(request.relativePath, filename),
};
resolver.doResolve(
target,
obj,
`using path: ${filePath}`,
resolveContext,
callback,
);
});
}
};

59
node_modules/enhanced-resolve/lib/ConditionalPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,59 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class ConditionalPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {Partial<ResolveRequest>} test compare object
* @param {string | null} message log message
* @param {boolean} allowAlternatives when false, do not continue with the current step when "test" matches
* @param {string | ResolveStepHook} target target
*/
constructor(source, test, message, allowAlternatives, target) {
this.source = source;
this.test = test;
this.message = message;
this.allowAlternatives = allowAlternatives;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
const { test, message, allowAlternatives } = this;
const keys = /** @type {(keyof ResolveRequest)[]} */ (Object.keys(test));
resolver
.getHook(this.source)
.tapAsync("ConditionalPlugin", (request, resolveContext, callback) => {
for (const prop of keys) {
if (request[prop] !== test[prop]) return callback();
}
resolver.doResolve(
target,
request,
message,
resolveContext,
allowAlternatives
? callback
: (err, result) => {
if (err) return callback(err);
// Don't allow other alternatives
if (result === undefined) return callback(null, null);
callback(null, result);
},
);
});
}
};

View File

@@ -0,0 +1,103 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const DescriptionFileUtils = require("./DescriptionFileUtils");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
const BACKSLASH_G = /\\/g;
module.exports = class DescriptionFilePlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string[]} filenames filenames
* @param {boolean} pathIsFile pathIsFile
* @param {string | ResolveStepHook} target target
*/
constructor(source, filenames, pathIsFile, target) {
this.source = source;
this.filenames = filenames;
this.pathIsFile = pathIsFile;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync(
"DescriptionFilePlugin",
(request, resolveContext, callback) => {
const { path } = request;
if (!path) return callback();
const directory = this.pathIsFile
? DescriptionFileUtils.cdUp(path)
: path;
if (!directory) return callback();
DescriptionFileUtils.loadDescriptionFile(
resolver,
directory,
this.filenames,
request.descriptionFilePath
? {
path: request.descriptionFilePath,
content: request.descriptionFileData,
directory:
/** @type {string} */
(request.descriptionFileRoot),
}
: undefined,
resolveContext,
(err, result) => {
if (err) return callback(err);
if (!result) {
if (resolveContext.log) {
resolveContext.log(
`No description file found in ${directory} or above`,
);
}
return callback();
}
const rawRelative = path.slice(result.directory.length);
const relativePath = `.${
rawRelative.includes("\\")
? rawRelative.replace(BACKSLASH_G, "/")
: rawRelative
}`;
/** @type {ResolveRequest} */
const obj = {
...request,
descriptionFilePath: result.path,
descriptionFileData: result.content,
descriptionFileRoot: result.directory,
relativePath,
};
resolver.doResolve(
target,
obj,
`using description file: ${result.path} (relative path: ${relativePath})`,
resolveContext,
(err, result) => {
if (err) return callback(err);
// Don't allow other processing
if (result === undefined) return callback(null, null);
callback(null, result);
},
);
},
);
},
);
}
};

View File

@@ -0,0 +1,231 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const forEachBail = require("./forEachBail");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").JsonObject} JsonObject */
/** @typedef {import("./Resolver").JsonValue} JsonValue */
/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/**
* @typedef {object} DescriptionFileInfo
* @property {JsonObject=} content content
* @property {string} path path
* @property {string} directory directory
*/
/**
* @callback ErrorFirstCallback
* @param {Error | null=} error
* @param {DescriptionFileInfo=} result
*/
/**
* @typedef {object} Result
* @property {string} path path to description file
* @property {string} directory directory of description file
* @property {JsonObject} content content of description file
*/
const CHAR_SLASH = 47;
const CHAR_BACKSLASH = 92;
/**
* Walk up one directory. Called once per package-root candidate and once per
* `described-resolve` (to find the enclosing description file), so it's on
* the resolver's hot path.
*
* Previous implementation called `lastIndexOf("/")` and `lastIndexOf("\\")`
* separately and then picked the larger. For any non-trivial directory
* string on POSIX, `lastIndexOf("\\")` scans the full string just to return
* -1. A single reverse char-code scan does the same work in one pass.
*
* Any single-character directory is treated as a root — `directory.length
* <= 1` collapses the `"/"`, `"\\"` and `""` branches into one compare.
* Without the `"\\"` case, `cdUp("\\")` (reached from a UNC root or a DOS
* device path like `\\?\…`) would return itself via `slice(0, i || 1)`
* and trap `loadDescriptionFile` in an infinite loop. Once single-char
* roots are filtered up front, the reverse scan always produces a
* strictly shorter string.
* @param {string} directory directory
* @returns {string | null} parent directory or null
*/
function cdUp(directory) {
if (directory.length <= 1) return null;
for (let i = directory.length - 1; i >= 0; i--) {
const code = directory.charCodeAt(i);
if (code === CHAR_SLASH || code === CHAR_BACKSLASH) {
return directory.slice(0, i || 1);
}
}
return null;
}
/**
* @param {Resolver} resolver resolver
* @param {string} directory directory
* @param {string[]} filenames filenames
* @param {DescriptionFileInfo | undefined} oldInfo oldInfo
* @param {ResolveContext} resolveContext resolveContext
* @param {ErrorFirstCallback} callback callback
*/
function loadDescriptionFile(
resolver,
directory,
filenames,
oldInfo,
resolveContext,
callback,
) {
// Hoist the per-filename iterator and the per-level done callback out
// of `findDescriptionFile`. They both close over `directory`, which we
// reassign as we walk up the tree, so the same closures keep working
// across every level — the previous implementation re-allocated both
// arrows on every recursion step, which adds up on deep walks (multiple
// `DescriptionFilePlugin` taps per resolve, each climbing several
// directories looking for `package.json`).
/**
* @param {string} filename filename
* @param {(err?: null | Error, result?: null | Result) => void} iterCallback callback
* @returns {void}
*/
const iterFilename = (filename, iterCallback) => {
const descriptionFilePath = resolver.join(directory, filename);
/**
* @param {(null | Error)=} err error
* @param {JsonObject=} resolvedContent content
* @returns {void}
*/
function onJson(err, resolvedContent) {
if (err) {
if (resolveContext.log) {
resolveContext.log(
`${descriptionFilePath} (directory description file): ${err}`,
);
} else {
err.message = `${descriptionFilePath} (directory description file): ${err}`;
}
return iterCallback(err);
}
iterCallback(null, {
content: /** @type {JsonObject} */ (resolvedContent),
directory,
path: descriptionFilePath,
});
}
if (resolver.fileSystem.readJson) {
resolver.fileSystem.readJson(descriptionFilePath, (err, content) => {
if (err) {
if (
typeof (/** @type {NodeJS.ErrnoException} */ (err).code) !==
"undefined"
) {
if (resolveContext.missingDependencies) {
resolveContext.missingDependencies.add(descriptionFilePath);
}
return iterCallback();
}
if (resolveContext.fileDependencies) {
resolveContext.fileDependencies.add(descriptionFilePath);
}
return onJson(err);
}
if (resolveContext.fileDependencies) {
resolveContext.fileDependencies.add(descriptionFilePath);
}
onJson(null, content);
});
} else {
resolver.fileSystem.readFile(descriptionFilePath, (err, content) => {
if (err) {
if (resolveContext.missingDependencies) {
resolveContext.missingDependencies.add(descriptionFilePath);
}
return iterCallback();
}
if (resolveContext.fileDependencies) {
resolveContext.fileDependencies.add(descriptionFilePath);
}
/** @type {JsonObject | undefined} */
let json;
if (content) {
try {
json = JSON.parse(content.toString());
} catch (/** @type {unknown} */ err_) {
return onJson(/** @type {Error} */ (err_));
}
} else {
return onJson(new Error("No content in file"));
}
onJson(null, json);
});
}
};
// Forward-declared so the helpers below can reference each other
// without falling foul of `no-use-before-define`.
/** @type {() => void} */
let findDescriptionFile;
/**
* @param {(null | Error)=} err error
* @param {(null | Result)=} result result
* @returns {void}
*/
const onLevelDone = (err, result) => {
if (err) return callback(err);
if (result) return callback(null, result);
const dir = cdUp(directory);
if (!dir) {
return callback();
}
directory = dir;
return findDescriptionFile();
};
findDescriptionFile = () => {
if (oldInfo && oldInfo.directory === directory) {
// We already have info for this directory and can reuse it
return callback(null, oldInfo);
}
forEachBail(filenames, iterFilename, onLevelDone);
};
findDescriptionFile();
}
/**
* @param {JsonObject} content content
* @param {string | string[]} field field
* @returns {JsonValue | undefined} field data
*/
function getField(content, field) {
if (!content) return undefined;
if (Array.isArray(field)) {
/** @type {JsonValue} */
let current = content;
for (let j = 0; j < field.length; j++) {
if (current === null || typeof current !== "object") {
current = null;
break;
}
current = /** @type {JsonValue} */ (
/** @type {JsonObject} */
(current)[field[j]]
);
}
return current;
}
return content[field];
}
module.exports.cdUp = cdUp;
module.exports.getField = getField;
module.exports.loadDescriptionFile = loadDescriptionFile;

View File

@@ -0,0 +1,68 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class DirectoryExistsPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string | ResolveStepHook} target target
*/
constructor(source, target) {
this.source = source;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync(
"DirectoryExistsPlugin",
(request, resolveContext, callback) => {
const fs = resolver.fileSystem;
const directory = request.path;
if (!directory) return callback();
fs.stat(directory, (err, stat) => {
// Combine the two miss branches: a stat failure and a
// "not a directory" result share the same handling — record
// the path on `missingDependencies`, log the right reason,
// then bail. The error-message ternary picks the wording
// that matched the failing condition.
if (err || !stat || !stat.isDirectory()) {
if (resolveContext.missingDependencies) {
resolveContext.missingDependencies.add(directory);
}
if (resolveContext.log) {
resolveContext.log(
err || !stat
? `${directory} doesn't exist`
: `${directory} is not a directory`,
);
}
return callback();
}
if (resolveContext.fileDependencies) {
resolveContext.fileDependencies.add(directory);
}
resolver.doResolve(
target,
request,
`existing directory ${directory}`,
resolveContext,
callback,
);
});
},
);
}
};

232
node_modules/enhanced-resolve/lib/ExportsFieldPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,232 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const DescriptionFileUtils = require("./DescriptionFileUtils");
const forEachBail = require("./forEachBail");
const { processExportsField } = require("./util/entrypoints");
const { parseIdentifier } = require("./util/identifier");
const {
deprecatedInvalidSegmentRegEx,
invalidSegmentRegEx,
} = require("./util/path");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").JsonObject} JsonObject */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {import("./util/entrypoints").ExportsField} ExportsField */
/** @typedef {import("./util/entrypoints").FieldProcessor} FieldProcessor */
module.exports = class ExportsFieldPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {Set<string>} conditionNames condition names
* @param {string | string[]} fieldNamePath name path
* @param {string | ResolveStepHook} target target
*/
constructor(source, conditionNames, fieldNamePath, target) {
this.source = source;
this.target = target;
this.conditionNames = conditionNames;
this.fieldName = fieldNamePath;
// `null` is cached for description files that have no exports field,
// so subsequent resolves against the same package.json skip the
// `DescriptionFileUtils.getField` walk entirely.
/** @type {WeakMap<JsonObject, FieldProcessor | null>} */
this._fieldProcessorCache = new WeakMap();
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("ExportsFieldPlugin", (request, resolveContext, callback) => {
// When there is no description file, abort
if (!request.descriptionFileData) return callback();
if (
// When the description file is inherited from parent, abort
// (There is no description file inside of this package)
request.relativePath !== "." ||
request.request === undefined
) {
return callback();
}
const { descriptionFileData } = request;
const remainingRequest =
request.query || request.fragment
? (request.request === "." ? "./" : request.request) +
request.query +
request.fragment
: request.request;
/** @type {string[]} */
let paths;
/** @type {string | null} */
let usedField;
try {
// Look up the cached processor first. On a cache hit we
// avoid re-walking the description file for the exports
// field — and `null` is cached for description files that
// have no exports field at all, so those skip the read
// entirely. `processExportsField` can throw on a malformed
// `exports` map (e.g. a key without a leading `.`), so
// building the processor must stay inside this try/catch.
let fieldProcessor =
this._fieldProcessorCache.get(descriptionFileData);
if (
fieldProcessor === undefined &&
!this._fieldProcessorCache.has(descriptionFileData)
) {
const exportsField =
/** @type {ExportsField | null | undefined} */
(
DescriptionFileUtils.getField(
descriptionFileData,
this.fieldName,
)
);
fieldProcessor = exportsField
? processExportsField(exportsField)
: null;
this._fieldProcessorCache.set(descriptionFileData, fieldProcessor);
}
if (!fieldProcessor) return callback();
if (request.directory) {
return callback(
new Error(
`Resolving to directories is not possible with the exports field (request was ${remainingRequest}/)`,
),
);
}
[paths, usedField] = fieldProcessor(
remainingRequest,
this.conditionNames,
);
} catch (/** @type {unknown} */ err) {
if (resolveContext.log) {
resolveContext.log(
`Exports field in ${request.descriptionFilePath} can't be processed: ${err}`,
);
}
return callback(/** @type {Error} */ (err));
}
if (paths.length === 0) {
const conditions = [...this.conditionNames];
const conditionsStr =
conditions.length === 1
? `the condition "${conditions[0]}"`
: `the conditions ${JSON.stringify(conditions)}`;
return callback(
new Error(
`"${remainingRequest}" is not exported under ${conditionsStr} from package ${request.descriptionFileRoot} (see exports field in ${request.descriptionFilePath})`,
),
);
}
forEachBail(
paths,
/**
* @param {string} path path
* @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
* @param {number} i index
* @returns {void}
*/
(path, callback, i) => {
const parsedIdentifier = parseIdentifier(path);
if (!parsedIdentifier) return callback();
const [relativePath, query, fragment] = parsedIdentifier;
if (!relativePath.startsWith("./")) {
if (paths.length === i) {
return callback(
new Error(
`Invalid "exports" target "${path}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`,
),
);
}
return callback();
}
const withoutDotSlash = relativePath.slice(2);
if (
invalidSegmentRegEx.test(withoutDotSlash) &&
deprecatedInvalidSegmentRegEx.test(withoutDotSlash)
) {
if (paths.length === i) {
return callback(
new Error(
`Invalid "exports" target "${path}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`,
),
);
}
return callback();
}
/** @type {ResolveRequest} */
const obj = {
...request,
request: undefined,
path: resolver.join(
/** @type {string} */ (request.descriptionFileRoot),
relativePath,
),
relativePath,
query,
fragment,
};
resolver.doResolve(
target,
obj,
`using exports field: ${path}`,
resolveContext,
(err, result) => {
if (err) return callback(err);
// Don't allow to continue - https://github.com/webpack/enhanced-resolve/issues/400
if (result === undefined) return callback(null, null);
callback(null, result);
},
);
},
/**
* @param {(null | Error)=} err error
* @param {(null | ResolveRequest)=} result result
* @returns {void}
*/
(err, result) => {
if (err) return callback(err);
// When an exports field match was found but the target file doesn't exist,
// return an error to prevent fallback to parent node_modules directories.
// Per the Node.js ESM spec, a matched exports entry that fails to resolve
// is a hard error, not a signal to continue searching up the directory tree.
// See: https://github.com/webpack/enhanced-resolve/issues/399
if (!result) {
return callback(
new Error(
`Package path ${remainingRequest} is exported from package ${request.descriptionFileRoot}, but no valid target file was found (see exports field in ${request.descriptionFilePath})`,
),
);
}
callback(null, result);
},
);
});
}
};

View File

@@ -0,0 +1,129 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const forEachBail = require("./forEachBail");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {{ alias: string | string[], extension: string }} ExtensionAliasOption */
module.exports = class ExtensionAliasPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {ExtensionAliasOption} options options
* @param {string | ResolveStepHook} target target
*/
constructor(source, options, target) {
this.source = source;
this.options = options;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
const { extension, alias } = this.options;
resolver
.getHook(this.source)
.tapAsync("ExtensionAliasPlugin", (request, resolveContext, callback) => {
// Two modes of operation:
// - "request" mode: original request specifier still carries the
// extension (e.g. user wrote `./foo.js`). We swap the extension
// on `request.request` and re-resolve.
// - "path" mode: the specifier has already been joined into an
// absolute `request.path` (e.g. produced by the imports field).
// We swap the extension on `request.path` and `request.relativePath`.
const useRequest = request.request !== undefined;
const source = useRequest
? /** @type {string} */ (request.request)
: request.path;
if (!source || !source.endsWith(extension)) return callback();
const isAliasString = typeof alias === "string";
// Hoist the base (everything before the old extension) out of the
// per-alias `resolve` callback. For an array `alias`, the callback
// runs once per candidate extension; the base does not change
// between iterations, so there's no reason to recompute it.
const sourceBase = source.slice(0, -extension.length);
const relativePathBase =
!useRequest &&
request.relativePath &&
request.relativePath.endsWith(extension)
? request.relativePath.slice(0, -extension.length)
: null;
/**
* @param {string} alias extension alias
* @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
* @param {number=} index index
* @returns {void}
*/
const resolve = (alias, callback, index) => {
const newValue = `${sourceBase}${alias}`;
const nextRequest = useRequest
? {
...request,
request: newValue,
fullySpecified: true,
}
: {
...request,
path: newValue,
relativePath:
relativePathBase !== null
? `${relativePathBase}${alias}`
: request.relativePath,
fullySpecified: true,
};
return resolver.doResolve(
target,
nextRequest,
`aliased from extension alias with mapping '${extension}' to '${alias}'`,
resolveContext,
(err, result) => {
// Throw error if we are on the last alias (for multiple aliases) and it failed, always throw if we are not an array or we have only one alias
if (!isAliasString && index) {
if (index !== this.options.alias.length) {
if (resolveContext.log) {
resolveContext.log(
`Failed to alias from extension alias with mapping '${extension}' to '${alias}' for '${newValue}': ${err}`,
);
}
return callback(null, result);
}
return callback(err, result);
}
callback(err, result);
},
);
};
/**
* @param {(null | Error)=} err error
* @param {(null | ResolveRequest)=} result result
* @returns {void}
*/
const stoppingCallback = (err, result) => {
if (err) return callback(err);
if (result) return callback(null, result);
// Don't allow other aliasing or raw request
return callback(null, null);
};
if (isAliasString) {
resolve(alias, stoppingCallback);
} else if (alias.length > 1) {
forEachBail(alias, resolve, stoppingCallback);
} else {
resolve(alias[0], stoppingCallback);
}
});
}
};

65
node_modules/enhanced-resolve/lib/FileExistsPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,65 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class FileExistsPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string | ResolveStepHook} target target
*/
constructor(source, target) {
this.source = source;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
const fs = resolver.fileSystem;
resolver
.getHook(this.source)
.tapAsync("FileExistsPlugin", (request, resolveContext, callback) => {
const file = request.path;
if (!file) return callback();
fs.stat(file, (err, stat) => {
// Combine the two miss branches: a stat failure and a
// "not a file" result share the same handling — record the
// path on `missingDependencies`, log the right reason, then
// bail. The error-message ternary picks the wording that
// matched the failing condition.
if (err || !stat || !stat.isFile()) {
if (resolveContext.missingDependencies) {
resolveContext.missingDependencies.add(file);
}
if (resolveContext.log) {
resolveContext.log(
err || !stat
? `${file} doesn't exist`
: `${file} is not a file`,
);
}
return callback();
}
if (resolveContext.fileDependencies) {
resolveContext.fileDependencies.add(file);
}
resolver.doResolve(
target,
request,
`existing file: ${file}`,
resolveContext,
callback,
);
});
});
}
};

233
node_modules/enhanced-resolve/lib/ImportsFieldPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,233 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const DescriptionFileUtils = require("./DescriptionFileUtils");
const forEachBail = require("./forEachBail");
const { processImportsField } = require("./util/entrypoints");
const { parseIdentifier } = require("./util/identifier");
const { invalidSegmentRegEx } = require("./util/path");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").JsonObject} JsonObject */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {import("./util/entrypoints").FieldProcessor} FieldProcessor */
/** @typedef {import("./util/entrypoints").ImportsField} ImportsField */
const dotCode = ".".charCodeAt(0);
module.exports = class ImportsFieldPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {Set<string>} conditionNames condition names
* @param {string | string[]} fieldNamePath name path
* @param {string | ResolveStepHook} targetFile target file
* @param {string | ResolveStepHook} targetPackage target package
*/
constructor(
source,
conditionNames,
fieldNamePath,
targetFile,
targetPackage,
) {
this.source = source;
this.targetFile = targetFile;
this.targetPackage = targetPackage;
this.conditionNames = conditionNames;
this.fieldName = fieldNamePath;
// `null` is cached for description files that have no imports field,
// so subsequent resolves against the same package.json skip the
// `DescriptionFileUtils.getField` walk entirely.
/** @type {WeakMap<JsonObject, FieldProcessor | null>} */
this._fieldProcessorCache = new WeakMap();
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const targetFile = resolver.ensureHook(this.targetFile);
const targetPackage = resolver.ensureHook(this.targetPackage);
resolver
.getHook(this.source)
.tapAsync("ImportsFieldPlugin", (request, resolveContext, callback) => {
// When there is no description file, abort
if (!request.descriptionFileData || request.request === undefined) {
return callback();
}
const { descriptionFileData } = request;
// Skip the concat when there's nothing to append — the common
// case has empty query/fragment, so this avoids an allocation
// per resolve. Mirrors the pattern in ExportsFieldPlugin.
const remainingRequest =
request.query || request.fragment
? request.request + request.query + request.fragment
: request.request;
/** @type {string[]} */
let paths;
/** @type {string | null} */
let usedField;
try {
// Look up the cached processor first. On a cache hit we
// avoid re-walking the description file for the imports
// field — and `null` is cached for description files that
// have no imports field at all, so those skip the read
// entirely. `processImportsField` can throw on a
// malformed `imports` map, so building the processor must
// stay inside this try/catch.
let fieldProcessor =
this._fieldProcessorCache.get(descriptionFileData);
if (
fieldProcessor === undefined &&
!this._fieldProcessorCache.has(descriptionFileData)
) {
const importsField =
/** @type {ImportsField | null | undefined} */
(
DescriptionFileUtils.getField(
descriptionFileData,
this.fieldName,
)
);
fieldProcessor = importsField
? processImportsField(importsField)
: null;
this._fieldProcessorCache.set(descriptionFileData, fieldProcessor);
}
if (!fieldProcessor) return callback();
if (request.directory) {
return callback(
new Error(
`Resolving to directories is not possible with the imports field (request was ${remainingRequest}/)`,
),
);
}
[paths, usedField] = fieldProcessor(
remainingRequest,
this.conditionNames,
);
} catch (/** @type {unknown} */ err) {
if (resolveContext.log) {
resolveContext.log(
`Imports field in ${request.descriptionFilePath} can't be processed: ${err}`,
);
}
return callback(/** @type {Error} */ (err));
}
if (paths.length === 0) {
return callback(
new Error(
`Package import ${remainingRequest} is not imported from package ${request.descriptionFileRoot} (see imports field in ${request.descriptionFilePath})`,
),
);
}
forEachBail(
paths,
/**
* @param {string} path path
* @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
* @param {number} i index
* @returns {void}
*/
(path, callback, i) => {
const parsedIdentifier = parseIdentifier(path);
if (!parsedIdentifier) return callback();
const [path_, query, fragment] = parsedIdentifier;
switch (path_.charCodeAt(0)) {
// should be relative
case dotCode: {
const withoutDotSlash = path_.slice(2);
if (invalidSegmentRegEx.test(withoutDotSlash)) {
if (paths.length === i) {
return callback(
new Error(
`Invalid "imports" target "${path}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`,
),
);
}
return callback();
}
/** @type {ResolveRequest} */
const obj = {
...request,
request: undefined,
path: resolver.join(
/** @type {string} */ (request.descriptionFileRoot),
path_,
),
relativePath: path_,
query,
fragment,
};
resolver.doResolve(
targetFile,
obj,
`using imports field: ${path}`,
resolveContext,
(err, result) => {
if (err) return callback(err);
// Don't allow to continue - https://github.com/webpack/enhanced-resolve/issues/400
if (result === undefined) return callback(null, null);
callback(null, result);
},
);
break;
}
// package resolving
default: {
/** @type {ResolveRequest} */
const obj = {
...request,
request: path_,
relativePath: path_,
fullySpecified: true,
query,
fragment,
};
resolver.doResolve(
targetPackage,
obj,
`using imports field: ${path}`,
resolveContext,
(err, result) => {
if (err) return callback(err);
// Don't allow to continue - https://github.com/webpack/enhanced-resolve/issues/400
if (result === undefined) return callback(null, null);
callback(null, result);
},
);
}
}
},
/**
* @param {null | Error=} err error
* @param {null | ResolveRequest=} result result
* @returns {void}
*/
(err, result) => callback(err, result || null),
);
});
}
};

View File

@@ -0,0 +1,75 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
const namespaceStartCharCode = "@".charCodeAt(0);
module.exports = class JoinRequestPartPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string | ResolveStepHook} target target
*/
constructor(source, target) {
this.source = source;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync(
"JoinRequestPartPlugin",
(request, resolveContext, callback) => {
const req = request.request || "";
let i = req.indexOf("/", 3);
if (i >= 0 && req.charCodeAt(2) === namespaceStartCharCode) {
i = req.indexOf("/", i + 1);
}
/** @type {string} */
let moduleName;
/** @type {string} */
let remainingRequest;
/** @type {boolean} */
let fullySpecified;
if (i < 0) {
moduleName = req;
remainingRequest = ".";
fullySpecified = false;
} else {
moduleName = req.slice(0, i);
remainingRequest = `.${req.slice(i)}`;
fullySpecified = /** @type {boolean} */ (request.fullySpecified);
}
/** @type {ResolveRequest} */
const obj = {
...request,
path: resolver.join(
/** @type {string} */
(request.path),
moduleName,
),
relativePath:
request.relativePath &&
resolver.join(request.relativePath, moduleName),
request: remainingRequest,
fullySpecified,
};
resolver.doResolve(target, obj, null, resolveContext, callback);
},
);
}
};

45
node_modules/enhanced-resolve/lib/JoinRequestPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,45 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class JoinRequestPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string | ResolveStepHook} target target
*/
constructor(source, target) {
this.source = source;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("JoinRequestPlugin", (request, resolveContext, callback) => {
const requestPath = /** @type {string} */ (request.path);
const requestRequest = /** @type {string} */ (request.request);
/** @type {ResolveRequest} */
const obj = {
...request,
path: resolver.join(requestPath, requestRequest),
relativePath:
request.relativePath &&
resolver.join(request.relativePath, requestRequest),
request: undefined,
};
resolver.doResolve(target, obj, null, resolveContext, callback);
});
}
};

58
node_modules/enhanced-resolve/lib/LogInfoPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,58 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class LogInfoPlugin {
/**
* @param {string | ResolveStepHook} source source
*/
constructor(source) {
this.source = source;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const { source } = this;
resolver
.getHook(this.source)
.tapAsync("LogInfoPlugin", (request, resolveContext, callback) => {
if (!resolveContext.log) return callback();
const { log } = resolveContext;
const prefix = `[${source}] `;
if (request.path) {
log(`${prefix}Resolving in directory: ${request.path}`);
}
if (request.request) {
log(`${prefix}Resolving request: ${request.request}`);
}
if (request.module) log(`${prefix}Request is an module request.`);
if (request.directory) log(`${prefix}Request is a directory request.`);
if (request.query) {
log(`${prefix}Resolving request query: ${request.query}`);
}
if (request.fragment) {
log(`${prefix}Resolving request fragment: ${request.fragment}`);
}
if (request.descriptionFilePath) {
log(
`${prefix}Has description data from ${request.descriptionFilePath}`,
);
}
if (request.relativePath) {
log(
`${prefix}Relative path from description file is: ${request.relativePath}`,
);
}
callback();
});
}
};

97
node_modules/enhanced-resolve/lib/MainFieldPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,97 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const DescriptionFileUtils = require("./DescriptionFileUtils");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").JsonObject} JsonObject */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {{ name: string | string[], forceRelative: boolean }} MainFieldOptions */
const alreadyTriedMainField = Symbol("alreadyTriedMainField");
// Sentinel cached for description files where the main field resolves to a
// value we cannot use (missing, non-string, ".", "./"). Cheaper to store and
// check than to re-walk the description file on every resolve.
const NO_MAIN = Symbol("NoMain");
module.exports = class MainFieldPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {MainFieldOptions} options options
* @param {string | ResolveStepHook} target target
*/
constructor(source, options, target) {
this.source = source;
this.options = options;
this.target = target;
// Cache the resolved `mainModule` per description-file content. The
// options (`name`, `forceRelative`) are fixed for this plugin
// instance, so caching against content alone is safe. Stores either
// the ready-to-use request string or the `NO_MAIN` sentinel.
/** @type {WeakMap<JsonObject, string | typeof NO_MAIN>} */
this._mainModuleCache = new WeakMap();
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("MainFieldPlugin", (request, resolveContext, callback) => {
if (
request.path !== request.descriptionFileRoot ||
/** @type {ResolveRequest & { [alreadyTriedMainField]?: string }} */
(request)[alreadyTriedMainField] === request.descriptionFilePath ||
!request.descriptionFilePath
) {
return callback();
}
const descFileData = /** @type {JsonObject} */ (
request.descriptionFileData
);
let mainModule = this._mainModuleCache.get(descFileData);
if (mainModule === undefined) {
let raw =
/** @type {string | null | undefined} */
(DescriptionFileUtils.getField(descFileData, this.options.name));
if (!raw || typeof raw !== "string" || raw === "." || raw === "./") {
this._mainModuleCache.set(descFileData, NO_MAIN);
return callback();
}
if (this.options.forceRelative && !/^\.\.?\//.test(raw)) {
raw = `./${raw}`;
}
mainModule = raw;
this._mainModuleCache.set(descFileData, mainModule);
} else if (mainModule === NO_MAIN) {
return callback();
}
const filename = resolver.basename(request.descriptionFilePath);
/** @type {ResolveRequest & { [alreadyTriedMainField]?: string }} */
const obj = {
...request,
request: mainModule,
module: false,
directory: mainModule.endsWith("/"),
[alreadyTriedMainField]: request.descriptionFilePath,
};
return resolver.doResolve(
target,
obj,
`use ${mainModule} from ${this.options.name} in ${filename}`,
resolveContext,
callback,
);
});
}
};

View File

@@ -0,0 +1,9 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
// TODO remove in next major
module.exports = require("./ModulesInHierarchicalDirectoriesPlugin");

View File

@@ -0,0 +1,47 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { modulesResolveHandler } = require("./ModulesUtils");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class ModulesInHierarchicalDirectoriesPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string | string[]} directories directories
* @param {string | ResolveStepHook} target target
*/
constructor(source, directories, target) {
this.source = source;
this.directories = /** @type {string[]} */ [...directories];
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync(
"ModulesInHierarchicalDirectoriesPlugin",
(request, resolveContext, callback) => {
modulesResolveHandler(
resolver,
this.directories,
target,
request,
resolveContext,
callback,
);
},
);
}
};

View File

@@ -0,0 +1,49 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class ModulesInRootPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string} path path
* @param {string | ResolveStepHook} target target
*/
constructor(source, path, target) {
this.source = source;
this.path = path;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("ModulesInRootPlugin", (request, resolveContext, callback) => {
/** @type {ResolveRequest} */
const obj = {
...request,
path: this.path,
request: `./${request.request}`,
module: false,
};
resolver.doResolve(
target,
obj,
`looking for modules in ${this.path}`,
resolveContext,
callback,
);
});
}
};

132
node_modules/enhanced-resolve/lib/ModulesUtils.js generated vendored Normal file
View File

@@ -0,0 +1,132 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const forEachBail = require("./forEachBail");
const { getPathsCached } = require("./getPaths");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
/** @typedef {(err?: null | Error, result?: null | ResolveRequest) => void} InnerCallback */
/**
* Per-(directories-array) cache of the flat `addrs` list produced for a given
* `request.path`. For a fixed directories configuration the fan-out of
* `ancestor × directory` is deterministic per request.path, and many resolves
* share the same starting directory (sibling files in one project, loops over
* a batch of imports, etc.) — caching avoids the `getPaths` regex split plus
* `len(paths) × len(directories)` join calls per resolve.
*
* The outer map is keyed on the directories array reference (plugin-owned,
* stable for the lifetime of the resolver), and the inner map on the
* starting `request.path`. Kept private to this module (rather than hung off
* `resolver.pathCache`) so the pathCache's hidden-class shape is unchanged —
* that avoids perturbing the interpreter-mode IC state for the
* `resolver.pathCache.{join,dirname,basename}.fn(...)` accesses that run on
* every resolve, which the CodSpeed instruction-count harness is sensitive to.
* @type {WeakMap<string[], Map<string, string[]>>}
*/
const _addrsCacheByDirs = new WeakMap();
/**
* @param {Resolver} resolver resolver
* @param {string[]} directories directories
* @param {ResolveStepHook} target target
* @param {ResolveRequest} request request
* @param {ResolveContext} resolveContext resolve context
* @param {InnerCallback} callback callback
* @returns {void}
*/
function modulesResolveHandler(
resolver,
directories,
target,
request,
resolveContext,
callback,
) {
const fs = resolver.fileSystem;
const requestPath = /** @type {string} */ (request.path);
// Compute-or-reuse the flat `addrs` list. Inlined (rather than a helper
// function) so the cache-hit path — which is the vast majority of
// invocations — stays a single WeakMap + Map lookup with no function-call
// overhead. See `_addrsCacheByDirs` above for caching rationale.
let addrs;
let perPath = _addrsCacheByDirs.get(directories);
if (perPath === undefined) {
perPath = new Map();
_addrsCacheByDirs.set(directories, perPath);
} else {
addrs = perPath.get(requestPath);
}
if (addrs === undefined) {
const { paths } = getPathsCached(fs, requestPath);
const pathsLen = paths.length;
const dirsLen = directories.length;
// Pre-size the flat array rather than going through `map().reduce()`
// with intermediate arrays + spreads.
// eslint-disable-next-line unicorn/no-new-array
addrs = new Array(pathsLen * dirsLen);
let idx = 0;
const joinFn = resolver.pathCache.join.fn;
for (let pi = 0; pi < pathsLen; pi++) {
const pathItem = paths[pi];
for (let di = 0; di < dirsLen; di++) {
addrs[idx++] = joinFn(pathItem, directories[di]);
}
}
perPath.set(requestPath, addrs);
}
// Hoist the dot-prefixed request out of the per-addr iterator. `addrs`
// can have up to `paths.length × directories.length` entries (e.g. 36
// for an 8-deep source dir × 4-module config), and concatenating the
// same `./${request.request}` string on every iteration is wasted
// work — it's constant for the whole fan-out.
const relRequest = `./${request.request}`;
forEachBail(
addrs,
/**
* @param {string} addr addr
* @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
* @returns {void}
*/
(addr, callback) => {
fs.stat(addr, (err, stat) => {
if (!err && stat && stat.isDirectory()) {
/** @type {ResolveRequest} */
const obj = {
...request,
path: addr,
request: relRequest,
module: false,
};
const message = `looking for modules in ${addr}`;
return resolver.doResolve(
target,
obj,
message,
resolveContext,
callback,
);
}
if (resolveContext.log) {
resolveContext.log(`${addr} doesn't exist or is not a directory`);
}
if (resolveContext.missingDependencies) {
resolveContext.missingDependencies.add(addr);
}
return callback();
});
},
callback,
);
}
module.exports = {
modulesResolveHandler,
};

33
node_modules/enhanced-resolve/lib/NextPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,33 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class NextPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string | ResolveStepHook} target target
*/
constructor(source, target) {
this.source = source;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("NextPlugin", (request, resolveContext, callback) => {
resolver.doResolve(target, request, null, resolveContext, callback);
});
}
};

77
node_modules/enhanced-resolve/lib/ParsePlugin.js generated vendored Normal file
View File

@@ -0,0 +1,77 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class ParsePlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {Partial<ResolveRequest>} requestOptions request options
* @param {string | ResolveStepHook} target target
*/
constructor(source, requestOptions, target) {
this.source = source;
this.requestOptions = requestOptions;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("ParsePlugin", (request, resolveContext, callback) => {
const parsed = resolver.parse(/** @type {string} */ (request.request));
/** @type {ResolveRequest} */
const obj = { ...request, ...parsed, ...this.requestOptions };
if (request.query && !parsed.query) {
obj.query = request.query;
}
if (request.fragment && !parsed.fragment) {
obj.fragment = request.fragment;
}
if (parsed && resolveContext.log) {
if (parsed.module) resolveContext.log("Parsed request is a module");
if (parsed.directory) {
resolveContext.log("Parsed request is a directory");
}
}
// There is an edge-case where a request with # can be a path or a fragment -> try both
if (obj.request && !obj.query && obj.fragment) {
const directory = obj.fragment.endsWith("/");
/** @type {ResolveRequest} */
const alternative = {
...obj,
directory,
request:
obj.request +
(obj.directory ? "/" : "") +
(directory ? obj.fragment.slice(0, -1) : obj.fragment),
fragment: "",
};
resolver.doResolve(
target,
alternative,
null,
resolveContext,
(err, result) => {
if (err) return callback(err);
if (result) return callback(null, result);
resolver.doResolve(target, obj, null, resolveContext, callback);
},
);
return;
}
resolver.doResolve(target, obj, null, resolveContext, callback);
});
}
};

134
node_modules/enhanced-resolve/lib/PnpPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,134 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Maël Nison @arcanis
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/**
* @typedef {object} PnpApiImpl
* @property {(packageName: string, issuer: string, options: { considerBuiltins: boolean }) => string | null} resolveToUnqualified resolve to unqualified
*/
module.exports = class PnpPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {PnpApiImpl} pnpApi pnpApi
* @param {string | ResolveStepHook} target target
* @param {string | ResolveStepHook} alternateTarget alternateTarget
*/
constructor(source, pnpApi, target, alternateTarget) {
this.source = source;
this.pnpApi = pnpApi;
this.target = target;
this.alternateTarget = alternateTarget;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
/** @type {ResolveStepHook} */
const target = resolver.ensureHook(this.target);
const alternateTarget = resolver.ensureHook(this.alternateTarget);
resolver
.getHook(this.source)
.tapAsync("PnpPlugin", (request, resolveContext, callback) => {
const req = request.request;
if (!req) return callback();
// The trailing slash indicates to PnP that this value is a folder rather than a file
const issuer = `${request.path}/`;
const packageMatch = /^(@[^/]+\/)?[^/]+/.exec(req);
if (!packageMatch) return callback();
const [packageName] = packageMatch;
const innerRequest = `.${req.slice(packageName.length)}`;
/** @type {string | undefined | null} */
let resolution;
/** @type {string | undefined | null} */
let apiResolution;
try {
resolution = this.pnpApi.resolveToUnqualified(packageName, issuer, {
considerBuiltins: false,
});
if (resolution === null) {
// This is either not a PnP managed issuer or it's a Node builtin
// Try to continue resolving with our alternatives
resolver.doResolve(
alternateTarget,
request,
"issuer is not managed by a pnpapi",
resolveContext,
(err, result) => {
if (err) return callback(err);
if (result) return callback(null, result);
// Skip alternatives
return callback(null, null);
},
);
return;
}
if (resolveContext.fileDependencies) {
apiResolution = this.pnpApi.resolveToUnqualified("pnpapi", issuer, {
considerBuiltins: false,
});
}
} catch (/** @type {unknown} */ error) {
if (
/** @type {Error & { code: string }} */
(error).code === "MODULE_NOT_FOUND" &&
/** @type {Error & { pnpCode: string }} */
(error).pnpCode === "UNDECLARED_DEPENDENCY"
) {
// This is not a PnP managed dependency.
// Try to continue resolving with our alternatives
if (resolveContext.log) {
resolveContext.log("request is not managed by the pnpapi");
for (const line of /** @type {Error} */ (error).message
.split("\n")
.filter(Boolean)) {
resolveContext.log(` ${line}`);
}
}
return callback();
}
return callback(/** @type {Error} */ (error));
}
if (resolution === packageName) return callback();
if (apiResolution && resolveContext.fileDependencies) {
resolveContext.fileDependencies.add(apiResolution);
}
/** @type {ResolveRequest} */
const obj = {
...request,
path: resolution,
request: innerRequest,
ignoreSymlinks: true,
fullySpecified: request.fullySpecified && innerRequest !== ".",
};
resolver.doResolve(
target,
obj,
`resolved by pnp to ${resolution}`,
resolveContext,
(err, result) => {
if (err) return callback(err);
if (result) return callback(null, result);
// Skip alternatives
return callback(null, null);
},
);
});
}
};

1226
node_modules/enhanced-resolve/lib/Resolver.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

790
node_modules/enhanced-resolve/lib/ResolverFactory.js generated vendored Normal file
View File

@@ -0,0 +1,790 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
// eslint-disable-next-line n/prefer-global/process
const { versions } = require("process");
const AliasFieldPlugin = require("./AliasFieldPlugin");
const AliasPlugin = require("./AliasPlugin");
const AppendPlugin = require("./AppendPlugin");
const ConditionalPlugin = require("./ConditionalPlugin");
const DescriptionFilePlugin = require("./DescriptionFilePlugin");
const DirectoryExistsPlugin = require("./DirectoryExistsPlugin");
const ExportsFieldPlugin = require("./ExportsFieldPlugin");
const ExtensionAliasPlugin = require("./ExtensionAliasPlugin");
const FileExistsPlugin = require("./FileExistsPlugin");
const ImportsFieldPlugin = require("./ImportsFieldPlugin");
const JoinRequestPartPlugin = require("./JoinRequestPartPlugin");
const JoinRequestPlugin = require("./JoinRequestPlugin");
const MainFieldPlugin = require("./MainFieldPlugin");
const ModulesInHierarchicalDirectoriesPlugin = require("./ModulesInHierarchicalDirectoriesPlugin");
const ModulesInRootPlugin = require("./ModulesInRootPlugin");
const NextPlugin = require("./NextPlugin");
const ParsePlugin = require("./ParsePlugin");
const PnpPlugin = require("./PnpPlugin");
const Resolver = require("./Resolver");
const RestrictionsPlugin = require("./RestrictionsPlugin");
const ResultPlugin = require("./ResultPlugin");
const RootsPlugin = require("./RootsPlugin");
const SelfReferencePlugin = require("./SelfReferencePlugin");
const SymlinkPlugin = require("./SymlinkPlugin");
const SyncAsyncFileSystemDecorator = require("./SyncAsyncFileSystemDecorator");
const TryNextPlugin = require("./TryNextPlugin");
const TsconfigPathsPlugin = require("./TsconfigPathsPlugin");
const UnsafeCachePlugin = require("./UnsafeCachePlugin");
const UseFilePlugin = require("./UseFilePlugin");
const { PathType, getType } = require("./util/path");
/** @typedef {import("./AliasPlugin").AliasOption} AliasOptionEntry */
/** @typedef {import("./ExtensionAliasPlugin").ExtensionAliasOption} ExtensionAliasOption */
/** @typedef {import("./PnpPlugin").PnpApiImpl} PnpApi */
/** @typedef {import("./Resolver").EnsuredHooks} EnsuredHooks */
/** @typedef {import("./Resolver").FileSystem} FileSystem */
/** @typedef {import("./Resolver").KnownHooks} KnownHooks */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
/** @typedef {import("./UnsafeCachePlugin").Cache} Cache */
/** @typedef {string | string[] | false} AliasOptionNewRequest */
/** @typedef {{ [k: string]: AliasOptionNewRequest }} AliasOptions */
/** @typedef {{ [k: string]: string | string[] }} ExtensionAliasOptions */
/** @typedef {false | 0 | "" | null | undefined} Falsy */
/** @typedef {{ apply: (resolver: Resolver) => void } | ((this: Resolver, resolver: Resolver) => void) | Falsy} Plugin */
/**
* @typedef {object} TsconfigOptions
* @property {string=} configFile A relative path to the tsconfig file based on cwd, or an absolute path of tsconfig file
* @property {string[] | "auto"=} references References to other tsconfig files. 'auto' inherits from TypeScript config, or an array of relative/absolute paths
* @property {string=} baseUrl Override baseUrl from tsconfig.json. If provided, this value will be used instead of the baseUrl in the tsconfig file
*/
/**
* @typedef {object} UserResolveOptions
* @property {(AliasOptions | AliasOptionEntry[])=} alias A list of module alias configurations or an object which maps key to value
* @property {(AliasOptions | AliasOptionEntry[])=} fallback A list of module alias configurations or an object which maps key to value, applied only after modules option
* @property {ExtensionAliasOptions=} extensionAlias An object which maps extension to extension aliases
* @property {boolean=} extensionAliasForExports Also apply `extensionAlias` to paths resolved through the package.json `exports` field. Off by default (Node.js-aligned); when enabled, matches TypeScript's behavior for packages that ship TS sources alongside compiled JS.
* @property {(string | string[])[]=} aliasFields A list of alias fields in description files
* @property {((predicate: ResolveRequest) => boolean)=} cachePredicate A function which decides whether a request should be cached or not. An object is passed with at least `path` and `request` properties.
* @property {boolean=} cacheWithContext Whether or not the unsafeCache should include request context as part of the cache key.
* @property {string[]=} descriptionFiles A list of description files to read from
* @property {string[]=} conditionNames A list of exports field condition names.
* @property {boolean=} enforceExtension Enforce that a extension from extensions must be used
* @property {(string | string[])[]=} exportsFields A list of exports fields in description files
* @property {(string | string[])[]=} importsFields A list of imports fields in description files
* @property {string[]=} extensions A list of extensions which should be tried for files
* @property {FileSystem} fileSystem The file system which should be used
* @property {(Cache | boolean)=} unsafeCache Use this cache object to unsafely cache the successful requests
* @property {boolean=} symlinks Resolve symlinks to their symlinked location
* @property {Resolver=} resolver A prepared Resolver to which the plugins are attached
* @property {string[] | string=} modules A list of directories to resolve modules from, can be absolute path or folder name
* @property {(string | string[] | { name: string | string[], forceRelative: boolean })[]=} mainFields A list of main fields in description files
* @property {string[]=} mainFiles A list of main files in directories
* @property {Plugin[]=} plugins A list of additional resolve plugins which should be applied
* @property {PnpApi | null=} pnpApi A PnP API that should be used - null is "never", undefined is "auto"
* @property {string[]=} roots A list of root paths
* @property {boolean=} fullySpecified The request is already fully specified and no extensions or directories are resolved for it
* @property {boolean=} resolveToContext Resolve to a context instead of a file
* @property {(string | RegExp)[]=} restrictions A list of resolve restrictions
* @property {boolean=} useSyncFileSystemCalls Use only the sync constraints of the file system calls
* @property {boolean=} preferRelative Prefer to resolve module requests as relative requests before falling back to modules
* @property {boolean=} preferAbsolute Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots
* @property {string | boolean | TsconfigOptions=} tsconfig TypeScript config file path or config object with configFile and references
*/
/**
* @typedef {object} ResolveOptions
* @property {AliasOptionEntry[]} alias alias
* @property {AliasOptionEntry[]} fallback fallback
* @property {Set<string | string[]>} aliasFields alias fields
* @property {ExtensionAliasOption[]} extensionAlias extension alias
* @property {boolean} extensionAliasForExports apply extension alias to exports field targets
* @property {(predicate: ResolveRequest) => boolean} cachePredicate cache predicate
* @property {boolean} cacheWithContext cache with context
* @property {Set<string>} conditionNames A list of exports field condition names.
* @property {string[]} descriptionFiles description files
* @property {boolean} enforceExtension enforce extension
* @property {Set<string | string[]>} exportsFields exports fields
* @property {Set<string | string[]>} importsFields imports fields
* @property {Set<string>} extensions extensions
* @property {FileSystem} fileSystem fileSystem
* @property {Cache | false} unsafeCache unsafe cache
* @property {boolean} symlinks symlinks
* @property {Resolver=} resolver resolver
* @property {(string | string[])[]} modules modules
* @property {{ name: string[], forceRelative: boolean }[]} mainFields main fields
* @property {Set<string>} mainFiles main files
* @property {Plugin[]} plugins plugins
* @property {PnpApi | null} pnpApi pnp API
* @property {Set<string>} roots roots
* @property {boolean} fullySpecified fully specified
* @property {boolean} resolveToContext resolve to context
* @property {Set<string | RegExp>} restrictions restrictions
* @property {boolean} preferRelative prefer relative
* @property {boolean} preferAbsolute prefer absolute
* @property {string | boolean | TsconfigOptions} tsconfig tsconfig file path or config object
*/
/**
* @param {PnpApi | null=} option option
* @returns {PnpApi | null} processed option
*/
function processPnpApiOption(option) {
if (
option === undefined &&
/** @type {NodeJS.ProcessVersions & { pnp: string }} */ versions.pnp
) {
const _findPnpApi =
/** @type {(issuer: string) => PnpApi | null}} */
(
// @ts-expect-error maybe nothing
require("module").findPnpApi
);
if (_findPnpApi) {
return {
resolveToUnqualified(request, issuer, opts) {
const pnpapi = _findPnpApi(issuer);
if (!pnpapi) {
// Issuer isn't managed by PnP
return null;
}
return pnpapi.resolveToUnqualified(request, issuer, opts);
},
};
}
}
return option || null;
}
/**
* @param {AliasOptions | AliasOptionEntry[] | undefined} alias alias
* @returns {AliasOptionEntry[]} normalized aliases
*/
function normalizeAlias(alias) {
return typeof alias === "object" && !Array.isArray(alias) && alias !== null
? Object.keys(alias).map((key) => {
/** @type {AliasOptionEntry} */
const obj = { name: key, onlyModule: false, alias: alias[key] };
if (/\$$/.test(key)) {
obj.onlyModule = true;
obj.name = key.slice(0, -1);
}
return obj;
})
: /** @type {AliasOptionEntry[]} */ (alias) || [];
}
/**
* Merging filtered elements
* @param {string[]} array source array
* @param {(item: string) => boolean} filter predicate
* @returns {(string | string[])[]} merge result
*/
function mergeFilteredToArray(array, filter) {
/** @type {(string | string[])[]} */
const result = [];
const set = new Set(array);
for (const item of set) {
if (filter(item)) {
const lastElement =
result.length > 0 ? result[result.length - 1] : undefined;
if (Array.isArray(lastElement)) {
lastElement.push(item);
} else {
result.push([item]);
}
} else {
result.push(item);
}
}
return result;
}
/**
* @param {UserResolveOptions} options input options
* @returns {ResolveOptions} output options
*/
function createOptions(options) {
const mainFieldsSet = new Set(options.mainFields || ["main"]);
/** @type {ResolveOptions["mainFields"]} */
const mainFields = [];
for (const item of mainFieldsSet) {
if (typeof item === "string") {
mainFields.push({
name: [item],
forceRelative: true,
});
} else if (Array.isArray(item)) {
mainFields.push({
name: item,
forceRelative: true,
});
} else {
mainFields.push({
name: Array.isArray(item.name) ? item.name : [item.name],
forceRelative: item.forceRelative,
});
}
}
return {
alias: normalizeAlias(options.alias),
fallback: normalizeAlias(options.fallback),
aliasFields: new Set(options.aliasFields),
cachePredicate:
options.cachePredicate ||
function trueFn() {
return true;
},
cacheWithContext:
typeof options.cacheWithContext !== "undefined"
? options.cacheWithContext
: true,
exportsFields: new Set(options.exportsFields || ["exports"]),
importsFields: new Set(options.importsFields || ["imports"]),
conditionNames: new Set(options.conditionNames),
descriptionFiles: [
...new Set(options.descriptionFiles || ["package.json"]),
],
enforceExtension:
options.enforceExtension === undefined
? Boolean(options.extensions && options.extensions.includes(""))
: options.enforceExtension,
extensions: new Set(options.extensions || [".js", ".json", ".node"]),
extensionAlias: options.extensionAlias
? Object.keys(options.extensionAlias).map((k) => ({
extension: k,
alias: /** @type {ExtensionAliasOptions} */ (options.extensionAlias)[
k
],
}))
: [],
extensionAliasForExports: options.extensionAliasForExports || false,
fileSystem: options.useSyncFileSystemCalls
? new SyncAsyncFileSystemDecorator(
/** @type {SyncFileSystem} */ (
/** @type {unknown} */ (options.fileSystem)
),
)
: options.fileSystem,
unsafeCache:
options.unsafeCache && typeof options.unsafeCache !== "object"
? /** @type {Cache} */ ({})
: options.unsafeCache || false,
symlinks: typeof options.symlinks !== "undefined" ? options.symlinks : true,
resolver: options.resolver,
modules: mergeFilteredToArray(
Array.isArray(options.modules)
? options.modules
: options.modules
? [options.modules]
: ["node_modules"],
(item) => {
const type = getType(item);
return type === PathType.Normal || type === PathType.Relative;
},
),
mainFields,
mainFiles: new Set(options.mainFiles || ["index"]),
plugins: options.plugins || [],
pnpApi: processPnpApiOption(options.pnpApi),
roots: new Set(options.roots || undefined),
fullySpecified: options.fullySpecified || false,
resolveToContext: options.resolveToContext || false,
preferRelative: options.preferRelative || false,
preferAbsolute: options.preferAbsolute || false,
restrictions: new Set(options.restrictions),
tsconfig:
typeof options.tsconfig === "undefined" ? false : options.tsconfig,
};
}
/**
* @param {UserResolveOptions} options resolve options
* @returns {Resolver} created resolver
*/
module.exports.createResolver = function createResolver(options) {
const normalizedOptions = createOptions(options);
const {
alias,
fallback,
aliasFields,
extensionAliasForExports,
cachePredicate,
cacheWithContext,
conditionNames,
descriptionFiles,
enforceExtension,
exportsFields,
extensionAlias,
importsFields,
extensions,
fileSystem,
fullySpecified,
mainFields,
mainFiles,
modules,
plugins: userPlugins,
pnpApi,
resolveToContext,
preferRelative,
preferAbsolute,
symlinks,
unsafeCache,
resolver: customResolver,
restrictions,
roots,
tsconfig,
} = normalizedOptions;
const plugins = [...userPlugins];
const resolver =
customResolver || new Resolver(fileSystem, normalizedOptions);
// // pipeline ////
resolver.ensureHook("resolve");
resolver.ensureHook("internalResolve");
resolver.ensureHook("newInternalResolve");
resolver.ensureHook("importsResolve");
resolver.ensureHook("parsedResolve");
resolver.ensureHook("describedResolve");
resolver.ensureHook("rawResolve");
resolver.ensureHook("normalResolve");
resolver.ensureHook("internal");
resolver.ensureHook("rawModule");
resolver.ensureHook("alternateRawModule");
resolver.ensureHook("module");
resolver.ensureHook("resolveAsModule");
resolver.ensureHook("undescribedResolveInPackage");
resolver.ensureHook("resolveInPackage");
resolver.ensureHook("resolveInExistingDirectory");
resolver.ensureHook("importsFieldRelative");
if (extensionAliasForExports) {
resolver.ensureHook("exportsFieldRelative");
}
resolver.ensureHook("relative");
resolver.ensureHook("describedRelative");
resolver.ensureHook("directory");
resolver.ensureHook("undescribedExistingDirectory");
resolver.ensureHook("existingDirectory");
resolver.ensureHook("undescribedRawFile");
resolver.ensureHook("rawFile");
resolver.ensureHook("file");
resolver.ensureHook("finalFile");
resolver.ensureHook("existingFile");
resolver.ensureHook("resolved");
// TODO remove in next major
// cspell:word Interal
// Backward-compat
// @ts-expect-error
resolver.hooks.newInteralResolve = resolver.hooks.newInternalResolve;
// resolve
for (const { source, resolveOptions } of [
{ source: "resolve", resolveOptions: { fullySpecified } },
{ source: "internal-resolve", resolveOptions: { fullySpecified: false } },
// Entry point for non-relative targets from the imports field.
// Sets internal: false to prevent re-entering imports resolution,
// aligning with the Node.js ESM spec where PACKAGE_IMPORTS_RESOLVE
// does not recursively resolve # specifiers.
// https://nodejs.org/api/esm.html#resolution-algorithm-specification
{
source: "imports-resolve",
resolveOptions: { fullySpecified: false, internal: false },
},
]) {
plugins.push(new ParsePlugin(source, resolveOptions, "parsed-resolve"));
}
// parsed-resolve
plugins.push(
new DescriptionFilePlugin(
"parsed-resolve",
descriptionFiles,
false,
"described-resolve",
),
);
plugins.push(new NextPlugin("after-parsed-resolve", "described-resolve"));
// described-resolve
if (unsafeCache) {
plugins.push(
new UnsafeCachePlugin(
"described-resolve",
cachePredicate,
/** @type {import("./UnsafeCachePlugin").Cache} */ (unsafeCache),
cacheWithContext,
"raw-resolve",
),
);
} else {
plugins.push(new NextPlugin("described-resolve", "raw-resolve"));
}
if (fallback.length > 0) {
plugins.push(
new AliasPlugin("described-resolve", fallback, "internal-resolve"),
);
}
// raw-resolve
if (alias.length > 0) {
plugins.push(new AliasPlugin("raw-resolve", alias, "internal-resolve"));
}
if (tsconfig) {
plugins.push(new TsconfigPathsPlugin(tsconfig));
}
for (const item of aliasFields) {
plugins.push(new AliasFieldPlugin("raw-resolve", item, "internal-resolve"));
}
for (const item of extensionAlias) {
plugins.push(
new ExtensionAliasPlugin("raw-resolve", item, "normal-resolve"),
);
}
plugins.push(new NextPlugin("raw-resolve", "normal-resolve"));
// normal-resolve
if (preferRelative) {
plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
}
plugins.push(
new ConditionalPlugin(
"after-normal-resolve",
{ module: true },
"resolve as module",
false,
"raw-module",
),
);
plugins.push(
new ConditionalPlugin(
"after-normal-resolve",
{ internal: true },
"resolve as internal import",
false,
"internal",
),
);
if (preferAbsolute) {
plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
}
if (roots.size > 0) {
plugins.push(new RootsPlugin("after-normal-resolve", roots, "relative"));
}
if (!preferRelative && !preferAbsolute) {
plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
}
// internal
for (const importsField of importsFields) {
plugins.push(
new ImportsFieldPlugin(
"internal",
conditionNames,
importsField,
"imports-field-relative",
"imports-resolve",
),
);
}
// imports-field-relative: apply extensionAlias to paths produced by the
// imports field (TypeScript-style extension substitution for self-package
// imports like `#foo` -> `./foo.js` -> `./foo.ts`). Unlike the exports
// field, the imports field is internal to the package, so applying the
// consumer's extension aliases does not expose files the package author
// did not intend to export. See issue #413.
for (const item of extensionAlias) {
plugins.push(
new ExtensionAliasPlugin("imports-field-relative", item, "relative"),
);
}
plugins.push(new NextPlugin("imports-field-relative", "relative"));
// raw-module
for (const exportsField of exportsFields) {
plugins.push(
new SelfReferencePlugin("raw-module", exportsField, "resolve-as-module"),
);
}
for (const item of modules) {
if (Array.isArray(item)) {
if (item.includes("node_modules") && pnpApi) {
plugins.push(
new ModulesInHierarchicalDirectoriesPlugin(
"raw-module",
item.filter((i) => i !== "node_modules"),
"module",
),
);
plugins.push(
new PnpPlugin(
"raw-module",
pnpApi,
"undescribed-resolve-in-package",
"alternate-raw-module",
),
);
plugins.push(
new ModulesInHierarchicalDirectoriesPlugin(
"alternate-raw-module",
["node_modules"],
"module",
),
);
} else {
plugins.push(
new ModulesInHierarchicalDirectoriesPlugin(
"raw-module",
item,
"module",
),
);
}
} else {
plugins.push(new ModulesInRootPlugin("raw-module", item, "module"));
}
}
// module
plugins.push(new JoinRequestPartPlugin("module", "resolve-as-module"));
// resolve-as-module
if (!resolveToContext) {
plugins.push(
new ConditionalPlugin(
"resolve-as-module",
{ directory: false, request: "." },
"single file module",
true,
"undescribed-raw-file",
),
);
}
plugins.push(
new DirectoryExistsPlugin(
"resolve-as-module",
"undescribed-resolve-in-package",
),
);
// undescribed-resolve-in-package
plugins.push(
new DescriptionFilePlugin(
"undescribed-resolve-in-package",
descriptionFiles,
false,
"resolve-in-package",
),
);
plugins.push(
new NextPlugin(
"after-undescribed-resolve-in-package",
"resolve-in-package",
),
);
// resolve-in-package
const exportsFieldTarget = extensionAliasForExports
? "exports-field-relative"
: "relative";
for (const exportsField of exportsFields) {
plugins.push(
new ExportsFieldPlugin(
"resolve-in-package",
conditionNames,
exportsField,
exportsFieldTarget,
),
);
}
plugins.push(
new NextPlugin("resolve-in-package", "resolve-in-existing-directory"),
);
// exports-field-relative (opt-in via `extensionAliasForExports`):
// apply `extensionAlias` to paths produced by the exports field. This is
// off by default to match Node.js (which does not substitute extensions on
// bare-module targets), and on opt-in aligns with TypeScript for packages
// that ship TS sources alongside the compiled JS they list in `exports`.
if (extensionAliasForExports) {
for (const item of extensionAlias) {
plugins.push(
new ExtensionAliasPlugin("exports-field-relative", item, "relative"),
);
}
plugins.push(new NextPlugin("exports-field-relative", "relative"));
}
// resolve-in-existing-directory
plugins.push(
new JoinRequestPlugin("resolve-in-existing-directory", "relative"),
);
// relative
plugins.push(
new DescriptionFilePlugin(
"relative",
descriptionFiles,
true,
"described-relative",
),
);
plugins.push(new NextPlugin("after-relative", "described-relative"));
// described-relative
if (resolveToContext) {
plugins.push(new NextPlugin("described-relative", "directory"));
} else {
plugins.push(
new ConditionalPlugin(
"described-relative",
{ directory: false },
null,
true,
"raw-file",
),
);
plugins.push(
new ConditionalPlugin(
"described-relative",
{ fullySpecified: false },
"as directory",
true,
"directory",
),
);
}
// directory
plugins.push(
new DirectoryExistsPlugin("directory", "undescribed-existing-directory"),
);
if (resolveToContext) {
// undescribed-existing-directory
plugins.push(new NextPlugin("undescribed-existing-directory", "resolved"));
} else {
// undescribed-existing-directory
plugins.push(
new DescriptionFilePlugin(
"undescribed-existing-directory",
descriptionFiles,
false,
"existing-directory",
),
);
for (const item of mainFiles) {
plugins.push(
new UseFilePlugin(
"undescribed-existing-directory",
item,
"undescribed-raw-file",
),
);
}
// described-existing-directory
for (const item of mainFields) {
plugins.push(
new MainFieldPlugin(
"existing-directory",
item,
"resolve-in-existing-directory",
),
);
}
for (const item of mainFiles) {
plugins.push(
new UseFilePlugin("existing-directory", item, "undescribed-raw-file"),
);
}
// undescribed-raw-file
plugins.push(
new DescriptionFilePlugin(
"undescribed-raw-file",
descriptionFiles,
true,
"raw-file",
),
);
plugins.push(new NextPlugin("after-undescribed-raw-file", "raw-file"));
// raw-file
plugins.push(
new ConditionalPlugin(
"raw-file",
{ fullySpecified: true },
null,
false,
"file",
),
);
if (!enforceExtension) {
plugins.push(new TryNextPlugin("raw-file", "no extension", "file"));
}
for (const item of extensions) {
plugins.push(new AppendPlugin("raw-file", item, "file"));
}
// file
if (alias.length > 0) {
plugins.push(new AliasPlugin("file", alias, "internal-resolve"));
}
for (const item of aliasFields) {
plugins.push(new AliasFieldPlugin("file", item, "internal-resolve"));
}
plugins.push(new NextPlugin("file", "final-file"));
// final-file
plugins.push(new FileExistsPlugin("final-file", "existing-file"));
// existing-file
if (symlinks) {
plugins.push(new SymlinkPlugin("existing-file", "existing-file"));
}
plugins.push(new NextPlugin("existing-file", "resolved"));
}
const { resolved } =
/** @type {KnownHooks & EnsuredHooks} */
(resolver.hooks);
// resolved
if (restrictions.size > 0) {
plugins.push(new RestrictionsPlugin(resolved, restrictions));
}
plugins.push(new ResultPlugin(resolved));
// // RESOLVER ////
for (const plugin of plugins) {
if (typeof plugin === "function") {
/** @type {(this: Resolver, resolver: Resolver) => void} */
(plugin).call(resolver, resolver);
} else if (plugin) {
plugin.apply(resolver);
}
}
return resolver;
};

View File

@@ -0,0 +1,70 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
const slashCode = "/".charCodeAt(0);
const backslashCode = "\\".charCodeAt(0);
/**
* @param {string} path path
* @param {string} parent parent path
* @returns {boolean} true, if path is inside of parent
*/
const isInside = (path, parent) => {
if (!path.startsWith(parent)) return false;
if (path.length === parent.length) return true;
const charCode = path.charCodeAt(parent.length);
return charCode === slashCode || charCode === backslashCode;
};
module.exports = class RestrictionsPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {Set<string | RegExp>} restrictions restrictions
*/
constructor(source, restrictions) {
this.source = source;
this.restrictions = restrictions;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
resolver
.getHook(this.source)
.tapAsync("RestrictionsPlugin", (request, resolveContext, callback) => {
if (typeof request.path === "string") {
const { path } = request;
for (const rule of this.restrictions) {
if (typeof rule === "string") {
if (!isInside(path, rule)) {
if (resolveContext.log) {
resolveContext.log(
`${path} is not inside of the restriction ${rule}`,
);
}
return callback(null, null);
}
} else if (!rule.test(path)) {
if (resolveContext.log) {
resolveContext.log(
`${path} doesn't match the restriction ${rule}`,
);
}
return callback(null, null);
}
}
}
callback();
});
}
};

43
node_modules/enhanced-resolve/lib/ResultPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,43 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class ResultPlugin {
/**
* @param {ResolveStepHook} source source
*/
constructor(source) {
this.source = source;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
this.source.tapAsync(
"ResultPlugin",
(request, resolverContext, callback) => {
const obj = { ...request };
if (resolverContext.log) {
resolverContext.log(`reporting result ${obj.path}`);
}
resolver.hooks.result.callAsync(obj, resolverContext, (err) => {
if (err) return callback(err);
if (typeof resolverContext.yield === "function") {
resolverContext.yield(obj);
callback(null, null);
} else {
callback(null, obj);
}
});
},
);
}
};

69
node_modules/enhanced-resolve/lib/RootsPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,69 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const forEachBail = require("./forEachBail");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
class RootsPlugin {
/**
* @param {string | ResolveStepHook} source source hook
* @param {Set<string>} roots roots
* @param {string | ResolveStepHook} target target hook
*/
constructor(source, roots, target) {
this.roots = [...roots];
this.source = source;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("RootsPlugin", (request, resolveContext, callback) => {
const req = request.request;
if (!req) return callback();
if (!req.startsWith("/")) return callback();
forEachBail(
this.roots,
/**
* @param {string} root root
* @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
* @returns {void}
*/
(root, callback) => {
const path = resolver.join(root, req.slice(1));
/** @type {ResolveRequest} */
const obj = {
...request,
path,
relativePath: request.relativePath && path,
};
resolver.doResolve(
target,
obj,
`root path ${root}`,
resolveContext,
callback,
);
},
callback,
);
});
}
}
module.exports = RootsPlugin;

View File

@@ -0,0 +1,106 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const DescriptionFileUtils = require("./DescriptionFileUtils");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").JsonObject} JsonObject */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
const slashCode = "/".charCodeAt(0);
// Sentinel stored in `_nameCache` when the description file either has no
// exports field (so self-reference can't apply) or no string `name`.
const NO_SELF_REF = Symbol("NoSelfRef");
module.exports = class SelfReferencePlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string | string[]} fieldNamePath name path
* @param {string | ResolveStepHook} target target
*/
constructor(source, fieldNamePath, target) {
this.source = source;
this.target = target;
this.fieldName = fieldNamePath;
// Self-reference needs both an exports field and a `"name"` string.
// Both are stable per description-file content, so cache the decision
// in one WeakMap: the resolved name when self-reference is possible,
// or `NO_SELF_REF` when it isn't. This skips the two per-resolve
// `DescriptionFileUtils.getField` walks for hot packages.
/** @type {WeakMap<JsonObject, string | typeof NO_SELF_REF>} */
this._nameCache = new WeakMap();
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("SelfReferencePlugin", (request, resolveContext, callback) => {
if (!request.descriptionFileData) return callback();
const req = request.request;
if (!req) return callback();
const { descriptionFileData } = request;
let name = this._nameCache.get(descriptionFileData);
if (name === undefined) {
// Feature is only enabled when an exports field is present
const exportsField = DescriptionFileUtils.getField(
descriptionFileData,
this.fieldName,
);
if (!exportsField) {
this._nameCache.set(descriptionFileData, NO_SELF_REF);
return callback();
}
const rawName = DescriptionFileUtils.getField(
descriptionFileData,
"name",
);
if (typeof rawName !== "string") {
this._nameCache.set(descriptionFileData, NO_SELF_REF);
return callback();
}
name = rawName;
this._nameCache.set(descriptionFileData, name);
} else if (name === NO_SELF_REF) {
return callback();
}
if (
req.startsWith(name) &&
(req.length === name.length ||
req.charCodeAt(name.length) === slashCode)
) {
const remainingRequest = `.${req.slice(name.length)}`;
/** @type {ResolveRequest} */
const obj = {
...request,
request: remainingRequest,
path: /** @type {string} */ (request.descriptionFileRoot),
relativePath: ".",
};
resolver.doResolve(
target,
obj,
"self reference",
resolveContext,
callback,
);
} else {
return callback();
}
});
}
};

132
node_modules/enhanced-resolve/lib/SymlinkPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,132 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const forEachBail = require("./forEachBail");
const { getPathsCached } = require("./getPaths");
const { PathType, getType } = require("./util/path");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class SymlinkPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string | ResolveStepHook} target target
*/
constructor(source, target) {
this.source = source;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
const fs = resolver.fileSystem;
resolver
.getHook(this.source)
.tapAsync("SymlinkPlugin", (request, resolveContext, callback) => {
if (request.ignoreSymlinks) return callback();
const pathsResult = getPathsCached(
fs,
/** @type {string} */ (request.path),
);
const { paths, segments } = pathsResult;
// `pathsResult.segments` is shared across callers via the cache.
// The only place we need to mutate is `pathSegments[idx] = result`
// when `fs.readlink` succeeds — which is rare (the vast majority
// of paths contain no symlinks, e.g. every resolve on
// `cache-predicate`'s no-symlink fixture). Defer the copy until
// we actually see a symlink so the common no-symlink path stays
// allocation-free.
/** @type {string[] | null} */
let pathSegments = null;
let containsSymlink = false;
let idx = -1;
forEachBail(
paths,
/**
* @param {string} path path
* @param {(err?: null | Error, result?: null | number) => void} callback callback
* @returns {void}
*/
(path, callback) => {
idx++;
if (resolveContext.fileDependencies) {
resolveContext.fileDependencies.add(path);
}
fs.readlink(path, (err, result) => {
if (!err && result) {
// First symlink seen — take our own copy now, so
// the cached `segments` array stays pristine for
// sibling resolves.
if (pathSegments === null) {
pathSegments = [...segments];
}
pathSegments[idx] = /** @type {string} */ (result);
containsSymlink = true;
// Shortcut when absolute symlink found
const resultType = getType(result.toString());
if (
resultType === PathType.AbsoluteWin ||
resultType === PathType.AbsolutePosix
) {
return callback(null, idx);
}
}
callback();
});
},
/**
* @param {null | Error=} err error
* @param {null | number=} idx result
* @returns {void}
*/
(err, idx) => {
if (!containsSymlink) return callback();
// `containsSymlink === true` implies we took a copy in
// `pathSegments` already, so it's non-null. The copy is
// our own, so `slice` to trim is fine and spreading to
// "unshare" is no longer necessary.
const own = /** @type {string[]} */ (pathSegments);
const resultSegments =
typeof idx === "number" ? own.slice(0, idx + 1) : own;
const result = resultSegments.reduceRight((a, b) =>
resolver.join(a, b),
);
/** @type {ResolveRequest} */
const obj = {
...request,
path: result,
};
resolver.doResolve(
target,
obj,
`resolved symlink to ${result}`,
resolveContext,
(err, innerResult) => {
if (err) return callback(err);
// The symlink-resolved (real) path is authoritative. If
// resolving it produced a result, use it. If it did not —
// e.g. a `restrictions` rule rejected the real target —
// stop here with no result instead of letting the next
// plugin report the original in-root symlink path, which
// would leave the symlink unresolved and bypass
// `restrictions`.
if (innerResult) return callback(null, innerResult);
return callback(null, null);
},
);
},
);
});
}
};

View File

@@ -0,0 +1,258 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver").FileSystem} FileSystem */
/** @typedef {import("./Resolver").StringCallback} StringCallback */
/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
// eslint-disable-next-line jsdoc/reject-function-type
/** @typedef {Function} SyncOrAsyncFunction */
// eslint-disable-next-line jsdoc/reject-any-type
/** @typedef {any} ResultOfSyncOrAsyncFunction */
/**
* @param {SyncFileSystem} fs file system implementation
* @constructor
*/
function SyncAsyncFileSystemDecorator(fs) {
this.fs = fs;
this.lstat = undefined;
this.lstatSync = undefined;
const { lstatSync } = fs;
if (lstatSync) {
this.lstat =
/** @type {FileSystem["lstat"]} */
(
(arg, options, callback) => {
let result;
try {
result = /** @type {SyncOrAsyncFunction | undefined} */ (callback)
? lstatSync.call(fs, arg, options)
: lstatSync.call(fs, arg);
} catch (err) {
return (callback || options)(
/** @type {NodeJS.ErrnoException | null} */
(err),
);
}
(callback || options)(
null,
/** @type {ResultOfSyncOrAsyncFunction} */
(result),
);
}
);
this.lstatSync =
/** @type {SyncFileSystem["lstatSync"]} */
((arg, options) => lstatSync.call(fs, arg, options));
}
this.stat =
/** @type {FileSystem["stat"]} */
(
(arg, options, callback) => {
let result;
try {
result = /** @type {SyncOrAsyncFunction | undefined} */ (callback)
? fs.statSync(arg, options)
: fs.statSync(arg);
} catch (err) {
return (callback || options)(
/** @type {NodeJS.ErrnoException | null} */
(err),
);
}
(callback || options)(
null,
/** @type {ResultOfSyncOrAsyncFunction} */
(result),
);
}
);
this.statSync =
/** @type {SyncFileSystem["statSync"]} */
((arg, options) => fs.statSync(arg, options));
this.readdir =
/** @type {FileSystem["readdir"]} */
(
(arg, options, callback) => {
let result;
try {
result = /** @type {SyncOrAsyncFunction | undefined} */ (callback)
? fs.readdirSync(
arg,
/** @type {Exclude<Parameters<FileSystem["readdir"]>[1], (err: NodeJS.ErrnoException | null, files: string[]) => void>} */
(options),
)
: fs.readdirSync(arg);
} catch (err) {
return (callback || options)(
/** @type {NodeJS.ErrnoException | null} */
(err),
[],
);
}
(callback || options)(
null,
/** @type {ResultOfSyncOrAsyncFunction} */
(result),
);
}
);
this.readdirSync =
/** @type {SyncFileSystem["readdirSync"]} */
(
(arg, options) =>
fs.readdirSync(
arg,
/** @type {Parameters<SyncFileSystem["readdirSync"]>[1]} */ (options),
)
);
this.readFile =
/** @type {FileSystem["readFile"]} */
(
(arg, options, callback) => {
let result;
try {
result = /** @type {SyncOrAsyncFunction | undefined} */ (callback)
? fs.readFileSync(arg, options)
: fs.readFileSync(arg);
} catch (err) {
return (callback || options)(
/** @type {NodeJS.ErrnoException | null} */
(err),
);
}
(callback || options)(
null,
/** @type {ResultOfSyncOrAsyncFunction} */
(result),
);
}
);
this.readFileSync =
/** @type {SyncFileSystem["readFileSync"]} */
((arg, options) => fs.readFileSync(arg, options));
this.readlink =
/** @type {FileSystem["readlink"]} */
(
(arg, options, callback) => {
let result;
try {
result = /** @type {SyncOrAsyncFunction | undefined} */ (callback)
? fs.readlinkSync(
arg,
/** @type {Exclude<Parameters<FileSystem["readlink"]>[1], StringCallback>} */
(options),
)
: fs.readlinkSync(arg);
} catch (err) {
return (callback || options)(
/** @type {NodeJS.ErrnoException | null} */
(err),
);
}
(callback || options)(
null,
/** @type {ResultOfSyncOrAsyncFunction} */
(result),
);
}
);
this.readlinkSync =
/** @type {SyncFileSystem["readlinkSync"]} */
(
(arg, options) =>
fs.readlinkSync(
arg,
/** @type {Parameters<SyncFileSystem["readlinkSync"]>[1]} */ (
options
),
)
);
this.readJson = undefined;
this.readJsonSync = undefined;
const { readJsonSync } = fs;
if (readJsonSync) {
this.readJson =
/** @type {FileSystem["readJson"]} */
(
(arg, callback) => {
let result;
try {
result = readJsonSync.call(fs, arg);
} catch (err) {
return callback(
/** @type {NodeJS.ErrnoException | Error | null} */ (err),
);
}
callback(null, result);
}
);
this.readJsonSync =
/** @type {SyncFileSystem["readJsonSync"]} */
((arg) => readJsonSync.call(fs, arg));
}
this.realpath = undefined;
this.realpathSync = undefined;
const { realpathSync } = fs;
if (realpathSync) {
this.realpath =
/** @type {FileSystem["realpath"]} */
(
(arg, options, callback) => {
let result;
try {
result = /** @type {SyncOrAsyncFunction | undefined} */ (callback)
? realpathSync.call(
fs,
arg,
/** @type {Exclude<Parameters<NonNullable<FileSystem["realpath"]>>[1], StringCallback>} */
(options),
)
: realpathSync.call(fs, arg);
} catch (err) {
return (callback || options)(
/** @type {NodeJS.ErrnoException | null} */
(err),
);
}
(callback || options)(
null,
/** @type {ResultOfSyncOrAsyncFunction} */
(result),
);
}
);
this.realpathSync =
/** @type {SyncFileSystem["realpathSync"]} */
(
(arg, options) =>
realpathSync.call(
fs,
arg,
/** @type {Parameters<NonNullable<SyncFileSystem["realpathSync"]>>[1]} */
(options),
)
);
}
}
module.exports = SyncAsyncFileSystemDecorator;

41
node_modules/enhanced-resolve/lib/TryNextPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,41 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class TryNextPlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string} message message
* @param {string | ResolveStepHook} target target
*/
constructor(source, message, target) {
this.source = source;
this.message = message;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("TryNextPlugin", (request, resolveContext, callback) => {
resolver.doResolve(
target,
request,
this.message,
resolveContext,
callback,
);
});
}
};

View File

@@ -0,0 +1,865 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Natsu @xiaoxiaojx
*/
"use strict";
const { aliasResolveHandler, compileAliasOptions } = require("./AliasUtils");
const { modulesResolveHandler } = require("./ModulesUtils");
const { readJson } = require("./util/fs");
const { PathType: _PathType, isSubPath, normalize } = require("./util/path");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {import("./AliasUtils").AliasOption} AliasOption */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
/** @typedef {import("./Resolver").FileSystem} FileSystem */
/** @typedef {import("./Resolver").TsconfigPathsData} TsconfigPathsData */
/** @typedef {import("./Resolver").TsconfigPathsMap} TsconfigPathsMap */
/** @typedef {import("./ResolverFactory").TsconfigOptions} TsconfigOptions */
// Sentinel stored in `_contextSelectionCache` for `requestPath`s whose
// scan returned `null` ("no context matched"). Using a non-null marker
// lets the cache-hit path be a single `Map.get()` — we distinguish
// "cached null" from "not cached yet" without a second `has` lookup.
const NULL_CONTEXT = Symbol("NULL_CONTEXT");
/**
* Per-`TsconfigPathsMap` memoization of `_selectPathsDataForContext`.
*
* Real-world builds resolve hundreds of requests per source file (every
* import in the file), and webpack-style resolvers walk the same
* `requestPath` (= source-file directory) for each one. Without this
* cache every resolve re-scans the full `contextList` even though the
* answer is invariant for a given `(map, requestPath)` pair.
*
* The outer key is the `TsconfigPathsMap` itself — rebuilt on every
* tsconfig change — so a `WeakMap` lets the inner map be collected
* automatically once the map goes away. The inner Map is keyed by
* `requestPath` (string); a `Symbol` sentinel stands in for "no
* matching context" so `Map.get` alone distinguishes the three states
* (cached data / cached null / not cached).
* @type {WeakMap<TsconfigPathsMap, Map<string, TsconfigPathsData | typeof NULL_CONTEXT>>}
*/
const _contextSelectionCache = new WeakMap();
/**
* @typedef {object} TsconfigCompilerOptions
* @property {string=} baseUrl Base URL for resolving paths
* @property {{ [key: string]: string[] }=} paths TypeScript paths mapping
*/
/**
* @typedef {object} TsconfigReference
* @property {string} path Path to the referenced project
*/
/**
* @typedef {object} Tsconfig
* @property {TsconfigCompilerOptions=} compilerOptions Compiler options
* @property {string | string[]=} extends Extended configuration paths
* @property {TsconfigReference[]=} references Project references
*/
const DEFAULT_CONFIG_FILE = "tsconfig.json";
const READ_JSON_OPTIONS = { stripComments: true };
// Trailing `/*` or `\*` segment of a tsconfig `paths` mapping (e.g.
// `./src/*` → `./src`). Hoisted so we don't allocate a fresh regex per
// path entry on every tsconfig load — and so the same regex object can be
// reused for the matching `test` + `replace` pair below.
const WILDCARD_TAIL_RE = /[/\\]\*$/;
/**
* @param {string} pattern Path pattern
* @returns {number} Length of the prefix
*/
function getPrefixLength(pattern) {
const prefixLength = pattern.indexOf("*");
if (prefixLength === -1) {
return pattern.length;
}
return prefixLength;
}
/**
* Sort path patterns.
* If a module name can be matched with multiple patterns then pattern with the longest prefix will be picked.
* @param {string[]} arr Array of path patterns
* @returns {string[]} Array of path patterns sorted by longest prefix
*/
function sortByLongestPrefix(arr) {
return [...arr].sort((a, b) => getPrefixLength(b) - getPrefixLength(a));
}
/**
* Merge two tsconfig objects
* @param {Tsconfig | null} base base config
* @param {Tsconfig | null} config config to merge
* @returns {Tsconfig} merged config
*/
function mergeTsconfigs(base, config) {
base = base || {};
config = config || {};
return {
...base,
...config,
compilerOptions: {
.../** @type {TsconfigCompilerOptions} */ (base.compilerOptions),
.../** @type {TsconfigCompilerOptions} */ (config.compilerOptions),
},
};
}
/**
* Substitute ${configDir} template variable in path
* @param {string} pathValue the path value
* @param {string} configDir the config directory
* @returns {string} the path with substituted template
*/
function substituteConfigDir(pathValue, configDir) {
// eslint-disable-next-line no-template-curly-in-string
if (!pathValue.includes("${configDir}")) return pathValue;
return pathValue.replace(/\$\{configDir\}/g, configDir);
}
/**
* Convert tsconfig paths to resolver options
* @param {string} configDir Config file directory
* @param {{ [key: string]: string[] }} paths TypeScript paths mapping
* @param {Resolver} resolver resolver instance
* @param {string=} baseUrl Base URL for resolving paths (relative to configDir)
* @returns {TsconfigPathsData} the resolver options
*/
function tsconfigPathsToResolveOptions(configDir, paths, resolver, baseUrl) {
// Calculate absolute base URL
const absoluteBaseUrl = !baseUrl
? configDir
: resolver.join(configDir, baseUrl);
/** @type {string[]} */
const sortedKeys = sortByLongestPrefix(Object.keys(paths));
/** @type {AliasOption[]} */
const alias = [];
/** @type {string[]} */
const modules = [];
for (const pattern of sortedKeys) {
const mappings = paths[pattern];
// Substitute ${configDir} in path mappings
const absolutePaths = mappings.map((mapping) => {
const substituted = substituteConfigDir(mapping, configDir);
return resolver.join(absoluteBaseUrl, substituted);
});
if (absolutePaths.length > 0) {
if (pattern === "*") {
// Pull `dir/*` entries directly into `modules` with their
// trailing wildcard stripped, skipping anything else. The
// previous `.map(...).filter(Boolean)` form allocated two
// throwaway arrays plus a spread iterator per `*` mapping.
for (let j = 0; j < absolutePaths.length; j++) {
const dir = absolutePaths[j];
if (WILDCARD_TAIL_RE.test(dir)) {
modules.push(dir.replace(WILDCARD_TAIL_RE, ""));
}
}
} else {
alias.push({ name: pattern, alias: absolutePaths });
}
}
}
if (baseUrl && absoluteBaseUrl && !modules.includes(absoluteBaseUrl)) {
modules.push(absoluteBaseUrl);
}
return {
alias: compileAliasOptions(resolver, alias),
modules,
};
}
/**
* Get the base context for the current project
* @param {string} context the context
* @param {Resolver} resolver resolver instance
* @param {string=} baseUrl base URL for resolving paths
* @returns {string} the base context
*/
function getAbsoluteBaseUrl(context, resolver, baseUrl) {
return !baseUrl ? context : resolver.join(context, baseUrl);
}
/**
* @param {TsconfigPathsData} main main paths data
* @param {string} mainContext main context
* @param {{ [baseUrl: string]: TsconfigPathsData }} refs references map
* @param {Set<string>} fileDependencies file dependencies
* @returns {TsconfigPathsMap} the tsconfig paths map
*/
function buildTsconfigPathsMap(main, mainContext, refs, fileDependencies) {
const allContexts = /** @type {{ [context: string]: TsconfigPathsData }} */ ({
[mainContext]: main,
...refs,
});
// Precompute the key list once per tsconfig load. `_selectPathsDataForContext`
// runs per resolve and otherwise would call `Object.entries(allContexts)`
// each time, allocating a fresh [key, value][] array.
const contextList = Object.keys(allContexts);
return {
main,
mainContext,
refs,
allContexts,
contextList,
fileDependencies,
};
}
module.exports = class TsconfigPathsPlugin {
/**
* @param {true | string | TsconfigOptions} configFileOrOptions tsconfig file path or options object
*/
constructor(configFileOrOptions) {
if (
typeof configFileOrOptions === "object" &&
configFileOrOptions !== null
) {
// Options object format
const { configFile } = configFileOrOptions;
/** @type {boolean} */
this.isAutoConfigFile = typeof configFile !== "string";
/** @type {string} */
this.configFile = this.isAutoConfigFile
? DEFAULT_CONFIG_FILE
: /** @type {string} */ (configFile);
/** @type {string[] | "auto"} */
if (Array.isArray(configFileOrOptions.references)) {
/** @type {TsconfigReference[] | "auto"} */
this.references = configFileOrOptions.references.map((ref) => ({
path: ref,
}));
} else if (configFileOrOptions.references === "auto") {
this.references = "auto";
} else {
this.references = [];
}
/** @type {string | undefined} */
this.baseUrl = configFileOrOptions.baseUrl;
} else {
/** @type {boolean} */
this.isAutoConfigFile = configFileOrOptions === true;
/** @type {string} */
this.configFile = this.isAutoConfigFile
? DEFAULT_CONFIG_FILE
: /** @type {string} */ (configFileOrOptions);
/** @type {TsconfigReference[] | "auto"} */
this.references = [];
/** @type {string | undefined} */
this.baseUrl = undefined;
}
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const aliasTarget = resolver.ensureHook("internal-resolve");
const moduleTarget = resolver.ensureHook("module");
resolver
.getHook("raw-resolve")
.tapAsync("TsconfigPathsPlugin", (request, resolveContext, callback) => {
this._getTsconfigPathsMap(
resolver,
request,
resolveContext,
(err, tsconfigPathsMap) => {
if (err) return callback(err);
if (!tsconfigPathsMap) return callback();
const selectedData = this._selectPathsDataForContext(
request.path,
tsconfigPathsMap,
);
if (!selectedData) return callback();
aliasResolveHandler(
resolver,
selectedData.alias,
aliasTarget,
request,
resolveContext,
(err, result) => {
if (err) return callback(err);
if (result) return callback(null, result);
// https://github.com/webpack/webpack/issues/20944
// Unlike resolve.alias, tsconfig paths should fall through
// when a pattern matches but the mapped path does not exist
// (matching TypeScript's native resolution behavior).
return callback();
},
);
},
);
});
resolver
.getHook("raw-module")
.tapAsync("TsconfigPathsPlugin", (request, resolveContext, callback) => {
this._getTsconfigPathsMap(
resolver,
request,
resolveContext,
(err, tsconfigPathsMap) => {
if (err) return callback(err);
if (!tsconfigPathsMap) return callback();
const selectedData = this._selectPathsDataForContext(
request.path,
tsconfigPathsMap,
);
if (!selectedData) return callback();
modulesResolveHandler(
resolver,
selectedData.modules,
moduleTarget,
request,
resolveContext,
callback,
);
},
);
});
}
/**
* Get TsconfigPathsMap for the request (with caching)
* @param {Resolver} resolver the resolver
* @param {ResolveRequest} request the request
* @param {ResolveContext} resolveContext the resolve context
* @param {(err: Error | null, result?: TsconfigPathsMap | null) => void} callback the callback
* @returns {void}
*/
_getTsconfigPathsMap(resolver, request, resolveContext, callback) {
if (typeof request.tsconfigPathsMap !== "undefined") {
const cached = request.tsconfigPathsMap;
if (!cached) return callback(null, null);
if (resolveContext.fileDependencies) {
for (const fileDependency of cached.fileDependencies) {
resolveContext.fileDependencies.add(fileDependency);
}
}
return callback(null, cached);
}
if (this.isAutoConfigFile) {
this._findTsconfigUpward(
resolver,
request.path || process.cwd(),
(err, result) => {
if (err) {
request.tsconfigPathsMap = null;
return callback(err);
}
if (!result) {
request.tsconfigPathsMap = null;
return callback(null, null);
}
const map = /** @type {TsconfigPathsMap} */ (result);
request.tsconfigPathsMap = map;
if (resolveContext.fileDependencies) {
for (const fileDependency of map.fileDependencies) {
resolveContext.fileDependencies.add(fileDependency);
}
}
callback(null, map);
},
);
return;
}
const absTsconfigPath = resolver.join(
request.path || process.cwd(),
this.configFile,
);
this._loadTsconfigPathsMap(resolver, absTsconfigPath, (err, result) => {
if (err) {
request.tsconfigPathsMap = null;
return callback(err);
}
const map = /** @type {TsconfigPathsMap} */ (result);
request.tsconfigPathsMap = map;
if (resolveContext.fileDependencies) {
for (const fileDependency of map.fileDependencies) {
resolveContext.fileDependencies.add(fileDependency);
}
}
callback(null, map);
});
}
/**
* Walk up from startDir to the filesystem root looking for tsconfig.json.
* Like TypeScript's own `findConfigFile` / `forEachAncestorDirectory`.
* @param {Resolver} resolver the resolver
* @param {string} startDir the directory to start searching from
* @param {(err: Error | null, result?: TsconfigPathsMap | null) => void} callback the callback
* @returns {void}
*/
_findTsconfigUpward(resolver, startDir, callback) {
const { fileSystem } = resolver;
const configFileName = this.configFile;
/**
* @param {string} dir current directory
*/
const check = (dir) => {
const candidate = resolver.join(dir, configFileName);
fileSystem.stat(candidate, (statErr) => {
if (!statErr) {
// Found — load it
this._loadTsconfigPathsMap(resolver, candidate, (loadErr, result) => {
if (loadErr) return callback(loadErr);
callback(null, result);
});
return;
}
// Not found — move to parent
const parentDir = resolver.dirname(dir);
if (parentDir === dir) {
// Reached filesystem root, no tsconfig.json found
return callback(null, null);
}
check(parentDir);
});
};
check(startDir);
}
/**
* Load tsconfig.json and build complete TsconfigPathsMap
* Includes main project paths and all referenced projects
* @param {Resolver} resolver the resolver
* @param {string} absTsconfigPath absolute path to tsconfig.json
* @param {(err: Error | null, result?: TsconfigPathsMap) => void} callback the callback
* @returns {void}
*/
_loadTsconfigPathsMap(resolver, absTsconfigPath, callback) {
/** @type {Set<string>} */
const fileDependencies = new Set();
this._loadTsconfig(
resolver,
absTsconfigPath,
fileDependencies,
undefined,
(err, config) => {
if (err) return callback(err);
const cfg = /** @type {Tsconfig} */ (config);
const compilerOptions = cfg.compilerOptions || {};
const mainContext = resolver.dirname(absTsconfigPath);
const baseUrl =
this.baseUrl !== undefined ? this.baseUrl : compilerOptions.baseUrl;
const main = tsconfigPathsToResolveOptions(
mainContext,
compilerOptions.paths || {},
resolver,
baseUrl,
);
/** @type {{ [baseUrl: string]: TsconfigPathsData }} */
const refs = {};
let referencesToUse = null;
if (this.references === "auto") {
referencesToUse = cfg.references;
} else if (Array.isArray(this.references)) {
referencesToUse = this.references;
}
if (!Array.isArray(referencesToUse)) {
return callback(
null,
buildTsconfigPathsMap(main, mainContext, refs, fileDependencies),
);
}
this._loadTsconfigReferences(
resolver,
mainContext,
referencesToUse,
fileDependencies,
refs,
(refErr) => {
if (refErr) return callback(refErr);
callback(
null,
buildTsconfigPathsMap(main, mainContext, refs, fileDependencies),
);
},
);
},
);
}
/**
* Select the correct TsconfigPathsData based on request.path (context-aware)
* Matches the behavior of tsconfig-paths-webpack-plugin
* @param {string | false} requestPath the request path
* @param {TsconfigPathsMap} tsconfigPathsMap the tsconfig paths map
* @returns {TsconfigPathsData | null} the selected paths data
*/
_selectPathsDataForContext(requestPath, tsconfigPathsMap) {
const { main, allContexts, contextList } = tsconfigPathsMap;
if (!requestPath) {
return main;
}
// Single-context tsconfigs (no project references) hit the loop
// below at most once; in that case the cache lookup costs more
// than the loop itself. Only memoize when there are 2+ contexts
// — that's the monorepo / project-references shape where the
// scan actually walks multiple entries per resolve and the
// `(map, requestPath)` answer can be reused.
/** @type {Map<string, TsconfigPathsData | typeof NULL_CONTEXT> | undefined} */
let perMap;
if (contextList.length >= 2) {
perMap = _contextSelectionCache.get(tsconfigPathsMap);
if (perMap !== undefined) {
const cached = perMap.get(requestPath);
if (cached !== undefined) {
return cached === NULL_CONTEXT
? null
: /** @type {TsconfigPathsData} */ (cached);
}
} else {
perMap = new Map();
_contextSelectionCache.set(tsconfigPathsMap, perMap);
}
}
let longestMatchContext = null;
let longestMatchLength = 0;
// Iterate the pre-computed key list (the previous
// `Object.entries(allContexts)` form allocated a fresh
// `[key, value][]` per resolve). Defer the `allContexts[context]`
// lookup to after we know the context actually matches — non-matches
// are the common case and don't need the property access.
for (let i = 0; i < contextList.length; i++) {
const context = contextList[i];
if (context === requestPath) {
const exact = allContexts[context];
if (perMap !== undefined) perMap.set(requestPath, exact);
return exact;
}
// Cheap integer-compare gate first: a context can only beat the
// current longest match if its own length is strictly greater.
// Skipping `isSubPath` (a `startsWith` + char-code probe) when the
// length already disqualifies the candidate avoids the per-resolve
// scan over every shorter context.
if (
context.length > longestMatchLength &&
isSubPath(context, requestPath)
) {
longestMatchContext = context;
longestMatchLength = context.length;
}
}
const result =
longestMatchContext === null ? null : allContexts[longestMatchContext];
if (perMap !== undefined) {
perMap.set(requestPath, result === null ? NULL_CONTEXT : result);
}
return result;
}
/**
* Load tsconfig from extends path
* @param {Resolver} resolver the resolver
* @param {string} configFilePath current config file path
* @param {string} extendedConfigValue extends value
* @param {Set<string>} fileDependencies the file dependencies
* @param {Set<string>} visitedConfigPaths config paths being loaded (for circular extends detection)
* @param {(err: Error | null, result?: Tsconfig) => void} callback callback
* @returns {void}
*/
_loadTsconfigFromExtends(
resolver,
configFilePath,
extendedConfigValue,
fileDependencies,
visitedConfigPaths,
callback,
) {
const { fileSystem } = resolver;
const currentDir = resolver.dirname(configFilePath);
// Substitute ${configDir} in extends path
extendedConfigValue = substituteConfigDir(extendedConfigValue, currentDir);
// Remember the original value before potentially appending .json
const originalExtendedConfigValue = extendedConfigValue;
if (
typeof extendedConfigValue === "string" &&
!extendedConfigValue.includes(".json")
) {
extendedConfigValue += ".json";
}
const initialExtendedConfigPath = resolver.join(
currentDir,
extendedConfigValue,
);
fileSystem.stat(initialExtendedConfigPath, (existsErr) => {
let extendedConfigPath = initialExtendedConfigPath;
if (existsErr) {
// Handle scoped package extends like "@scope/name" (no sub-path):
// "@scope/name" should resolve to node_modules/@scope/name/tsconfig.json,
// not node_modules/@scope/name.json
// See: test/fixtures/tsconfig-paths/extends-pkg-entry/
if (
typeof originalExtendedConfigValue === "string" &&
originalExtendedConfigValue.startsWith("@") &&
originalExtendedConfigValue.split("/").length === 2
) {
extendedConfigPath = resolver.join(
currentDir,
normalize(
`node_modules/${originalExtendedConfigValue}/${DEFAULT_CONFIG_FILE}`,
),
);
} else if (extendedConfigValue.includes("/")) {
// Handle package sub-path extends like "react/tsconfig":
// "react/tsconfig" resolves to node_modules/react/tsconfig.json
// See: test/fixtures/tsconfig-paths/extends-npm/
extendedConfigPath = resolver.join(
currentDir,
normalize(`node_modules/${extendedConfigValue}`),
);
} else if (
!originalExtendedConfigValue.startsWith(".") &&
!originalExtendedConfigValue.startsWith("/")
) {
// Handle unscoped package extends like "my-base-config" (no sub-path):
// "my-base-config" should resolve to node_modules/my-base-config/tsconfig.json
extendedConfigPath = resolver.join(
currentDir,
normalize(
`node_modules/${originalExtendedConfigValue}/${DEFAULT_CONFIG_FILE}`,
),
);
}
}
this._loadTsconfig(
resolver,
extendedConfigPath,
fileDependencies,
visitedConfigPaths,
(err, config) => {
if (err) return callback(err);
const cfg = /** @type {Tsconfig} */ (config);
const compilerOptions = cfg.compilerOptions || {
baseUrl: undefined,
};
if (compilerOptions.baseUrl) {
const extendedConfigDir = resolver.dirname(extendedConfigPath);
compilerOptions.baseUrl = getAbsoluteBaseUrl(
extendedConfigDir,
resolver,
compilerOptions.baseUrl,
);
}
delete cfg.references;
callback(null, cfg);
},
);
});
}
/**
* Load referenced tsconfig projects and store in referenceMatchMap
* Simple implementation matching tsconfig-paths-webpack-plugin:
* Just load each reference and store independently
* @param {Resolver} resolver the resolver
* @param {string} context the context
* @param {TsconfigReference[]} references array of references
* @param {Set<string>} fileDependencies the file dependencies
* @param {{ [baseUrl: string]: TsconfigPathsData }} referenceMatchMap the map to populate
* @param {(err: Error | null) => void} callback callback
* @param {Set<string>=} visitedRefPaths visited reference config paths (for circular reference detection)
* @returns {void}
*/
_loadTsconfigReferences(
resolver,
context,
references,
fileDependencies,
referenceMatchMap,
callback,
visitedRefPaths,
) {
if (references.length === 0) return callback(null);
const visited = visitedRefPaths || new Set();
let pending = references.length;
const finishOne = () => {
if (--pending === 0) callback(null);
};
for (const ref of references) {
const refPath = substituteConfigDir(ref.path, context);
const refConfigPath = resolver.join(
resolver.join(context, refPath),
DEFAULT_CONFIG_FILE,
);
if (visited.has(refConfigPath)) {
finishOne();
continue;
}
visited.add(refConfigPath);
this._loadTsconfig(
resolver,
refConfigPath,
fileDependencies,
undefined,
(err, refConfig) => {
// Failures are swallowed to match tsconfig-paths-webpack-plugin:
// a broken reference must not abort the main project's resolution.
if (err) return finishOne();
const cfg = /** @type {Tsconfig} */ (refConfig);
if (cfg.compilerOptions && cfg.compilerOptions.paths) {
const refContext = resolver.dirname(refConfigPath);
referenceMatchMap[refContext] = tsconfigPathsToResolveOptions(
refContext,
cfg.compilerOptions.paths || {},
resolver,
cfg.compilerOptions.baseUrl,
);
}
if (this.references === "auto" && Array.isArray(cfg.references)) {
this._loadTsconfigReferences(
resolver,
resolver.dirname(refConfigPath),
cfg.references,
fileDependencies,
referenceMatchMap,
finishOne,
visited,
);
} else {
finishOne();
}
},
);
}
}
/**
* Load tsconfig.json with extends support
* @param {Resolver} resolver the resolver
* @param {string} configFilePath absolute path to tsconfig.json
* @param {Set<string>} fileDependencies the file dependencies
* @param {Set<string> | undefined} visitedConfigPaths config paths being loaded (for circular extends detection)
* @param {(err: Error | null, result?: Tsconfig) => void} callback callback
* @returns {void}
*/
_loadTsconfig(
resolver,
configFilePath,
fileDependencies,
visitedConfigPaths,
callback,
) {
const visited = visitedConfigPaths || new Set();
if (visited.has(configFilePath)) {
return callback(null, /** @type {Tsconfig} */ ({}));
}
visited.add(configFilePath);
readJson(
resolver.fileSystem,
configFilePath,
READ_JSON_OPTIONS,
(err, parsed) => {
if (err) return callback(/** @type {Error} */ (err));
const config = /** @type {Tsconfig} */ (parsed);
fileDependencies.add(configFilePath);
const extendedConfig = config.extends;
if (!extendedConfig) return callback(null, config);
if (!Array.isArray(extendedConfig)) {
this._loadTsconfigFromExtends(
resolver,
configFilePath,
extendedConfig,
fileDependencies,
visited,
(extErr, extendedTsconfig) => {
if (extErr) return callback(extErr);
callback(
null,
mergeTsconfigs(
/** @type {Tsconfig} */ (extendedTsconfig),
config,
),
);
},
);
return;
}
/** @type {Tsconfig} */
let base = {};
let i = 0;
const next = () => {
if (i >= extendedConfig.length) {
return callback(null, mergeTsconfigs(base, config));
}
this._loadTsconfigFromExtends(
resolver,
configFilePath,
extendedConfig[i++],
fileDependencies,
visited,
(extErr, extendedTsconfig) => {
if (extErr) return callback(extErr);
base = mergeTsconfigs(
base,
/** @type {Tsconfig} */ (extendedTsconfig),
);
next();
},
);
};
next();
},
);
}
};

200
node_modules/enhanced-resolve/lib/UnsafeCachePlugin.js generated vendored Normal file
View File

@@ -0,0 +1,200 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { isRelativeRequest } = require("./util/path");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {import("./Resolver").ResolveContextYield} ResolveContextYield */
/** @typedef {{ [k: string]: undefined | ResolveRequest | ResolveRequest[] }} Cache */
/**
* @param {string} relativePath relative path from package root
* @param {string} request relative request
* @param {Resolver} resolver resolver instance
* @returns {string} normalized request with a preserved leading dot
*/
function joinRelativePreservingLeadingDot(relativePath, request, resolver) {
const normalized = resolver.join(relativePath, request);
return isRelativeRequest(normalized) ? normalized : `./${normalized}`;
}
/**
* @param {ResolveRequest} request request
* @returns {string | false | undefined} normalized path
*/
function getCachePath(request) {
if (request.descriptionFileRoot && !request.module) {
return request.descriptionFileRoot;
}
return request.path;
}
/**
* @param {ResolveRequest} request request
* @param {Resolver} resolver resolver instance
* @returns {string | undefined} normalized request string
*/
function getCacheRequest(request, resolver) {
const requestString = request.request;
if (
!requestString ||
!request.relativePath ||
!isRelativeRequest(requestString)
) {
return requestString;
}
return joinRelativePreservingLeadingDot(
request.relativePath,
requestString,
resolver,
);
}
// Cache-key separator: `\0` is safe because paths, requests, queries and
// fragments produced by `parseIdentifier` never contain a raw NUL (the
// \0-escape in identifier.js is decoded back to the original char), and the
// context, when included, is passed through `JSON.stringify`, which escapes
// any NUL to \u0000.
// const SEP = "\0";
/**
* Build the cache id for a request. Called on every `described-resolve`
* invocation when `unsafeCache` is on, so it's a hot path.
*
* Equivalent in meaning to the previous `JSON.stringify({ ... })` form, but
* ~35× faster since we avoid the object allocation and JSON serializer for
* the fields that are already plain strings.
* @param {string} type type of cache
* @param {ResolveRequest} request request
* @param {boolean} withContext cache with context?
* @param {Resolver} resolver resolver instance
* @returns {string} cache id
*/
function getCacheId(type, request, withContext, resolver) {
// TODO use it in the next major release, it is faster
// const contextPart = withContext ? JSON.stringify(request.context) : "";
// const path = getCachePath(request);
// const cacheRequest = getCacheRequest(request, resolver);
// return (
// type +
// SEP +
// contextPart +
// SEP +
// (path || "") +
// SEP +
// (request.query || "") +
// SEP +
// (request.fragment || "") +
// SEP +
// (cacheRequest || "")
// );
return JSON.stringify({
type,
context: withContext ? request.context : "",
path: getCachePath(request),
query: request.query,
fragment: request.fragment,
request: getCacheRequest(request, resolver),
});
}
module.exports = class UnsafeCachePlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {(request: ResolveRequest) => boolean} filterPredicate filterPredicate
* @param {Cache} cache cache
* @param {boolean} withContext withContext
* @param {string | ResolveStepHook} target target
*/
constructor(source, filterPredicate, cache, withContext, target) {
this.source = source;
this.filterPredicate = filterPredicate;
this.withContext = withContext;
this.cache = cache;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("UnsafeCachePlugin", (request, resolveContext, callback) => {
if (!this.filterPredicate(request)) {
return resolver.doResolve(
target,
request,
null,
resolveContext,
callback,
);
}
const isYield = typeof resolveContext.yield === "function";
const cacheId = getCacheId(
isYield ? "yield" : "default",
request,
this.withContext,
resolver,
);
const cacheEntry = this.cache[cacheId];
if (cacheEntry) {
if (isYield) {
const yield_ =
/** @type {ResolveContextYield} */
(resolveContext.yield);
if (Array.isArray(cacheEntry)) {
for (const result of cacheEntry) yield_(result);
} else {
yield_(cacheEntry);
}
return callback(null, null);
}
return callback(null, /** @type {ResolveRequest} */ (cacheEntry));
}
/** @type {ResolveContextYield | undefined} */
let yieldFn;
/** @type {ResolveContextYield | undefined} */
let yield_;
/** @type {ResolveRequest[]} */
const yieldResult = [];
if (isYield) {
yieldFn = resolveContext.yield;
yield_ = (result) => {
yieldResult.push(result);
};
}
resolver.doResolve(
target,
request,
null,
yield_ ? { ...resolveContext, yield: yield_ } : resolveContext,
(err, result) => {
if (err) return callback(err);
if (isYield) {
if (result) yieldResult.push(result);
for (const result of yieldResult) {
/** @type {ResolveContextYield} */
(yieldFn)(result);
}
this.cache[cacheId] = yieldResult;
return callback(null, null);
}
if (result) return callback(null, (this.cache[cacheId] = result));
callback();
},
);
});
}
};

55
node_modules/enhanced-resolve/lib/UseFilePlugin.js generated vendored Normal file
View File

@@ -0,0 +1,55 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class UseFilePlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string} filename filename
* @param {string | ResolveStepHook} target target
*/
constructor(source, filename, target) {
this.source = source;
this.filename = filename;
this.target = target;
}
/**
* @param {Resolver} resolver the resolver
* @returns {void}
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("UseFilePlugin", (request, resolveContext, callback) => {
const filePath = resolver.join(
/** @type {string} */ (request.path),
this.filename,
);
/** @type {ResolveRequest} */
const obj = {
...request,
path: filePath,
relativePath:
request.relativePath &&
resolver.join(request.relativePath, this.filename),
};
resolver.doResolve(
target,
obj,
`using path: ${filePath}`,
resolveContext,
callback,
);
});
}
};

View File

@@ -0,0 +1,56 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
/**
* Build the `ResolveContext` passed into the next hook in the chain.
*
* The caller — `Resolver.doResolve` — runs on every resolve step, so we
* want to allocate as little as possible here. Previously the caller
* constructed a temporary `{ log, yield, fileDependencies, ... }` literal
* and handed it to this helper, which then copied those same fields into
* a second fresh object. That's two allocations per step for what is
* effectively a struct copy with one mutated field (`stack`) and one
* optionally-wrapped field (`log`). Taking the parent context and the
* two things we actually want to change (stack, message) as separate
* arguments lets us allocate exactly one inner context.
* @param {ResolveContext} parent parent resolve context to inherit dependency sets / yield from
* @param {ResolveContext["stack"]} stack new stack tip for the nested call
* @param {null | string} message log message prefix for this step
* @returns {ResolveContext} inner context
*/
module.exports = function createInnerContext(parent, stack, message) {
const parentLog = parent.log;
let innerLog;
if (parentLog) {
if (message) {
let messageReported = false;
/**
* @param {string} msg message
*/
innerLog = (msg) => {
if (!messageReported) {
parentLog(message);
messageReported = true;
}
parentLog(` ${msg}`);
};
} else {
innerLog = parentLog;
}
}
return {
log: innerLog,
yield: parent.yield,
fileDependencies: parent.fileDependencies,
contextDependencies: parent.contextDependencies,
missingDependencies: parent.missingDependencies,
stack,
};
};

50
node_modules/enhanced-resolve/lib/forEachBail.js generated vendored Normal file
View File

@@ -0,0 +1,50 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/**
* @template T
* @template Z
* @callback Iterator
* @param {T} item item
* @param {(err?: null | Error, result?: null | Z) => void} callback callback
* @param {number} i index
* @returns {void}
*/
/**
* @template T
* @template Z
* @param {T[]} array array
* @param {Iterator<T, Z>} iterator iterator
* @param {(err?: null | Error, result?: null | Z, i?: number) => void} callback callback after all items are iterated
* @returns {void}
*/
module.exports = function forEachBail(array, iterator, callback) {
if (array.length === 0) return callback();
let i = 0;
const next = () => {
/** @type {boolean | undefined} */
let loop;
iterator(
array[i++],
(err, result) => {
if (err || result !== undefined || i >= array.length) {
return callback(err, result, i);
}
if (loop === false) while (next());
loop = true;
},
i,
);
if (!loop) loop = false;
return loop;
};
while (next());
};

41
node_modules/enhanced-resolve/lib/getInnerRequest.js generated vendored Normal file
View File

@@ -0,0 +1,41 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { isRelativeRequest } = require("./util/path");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/**
* @param {Resolver} resolver resolver
* @param {ResolveRequest} request string
* @returns {string} inner request
*/
module.exports = function getInnerRequest(resolver, request) {
if (
typeof request.__innerRequest === "string" &&
request.__innerRequest_request === request.request &&
request.__innerRequest_relativePath === request.relativePath
) {
return request.__innerRequest;
}
/** @type {string | undefined} */
let innerRequest;
if (request.request) {
innerRequest = request.request;
if (request.relativePath && isRelativeRequest(innerRequest)) {
innerRequest = resolver.join(request.relativePath, innerRequest);
}
} else {
innerRequest = request.relativePath;
}
// eslint-disable-next-line camelcase
request.__innerRequest_request = request.request;
// eslint-disable-next-line camelcase
request.__innerRequest_relativePath = request.relativePath;
return (request.__innerRequest = /** @type {string} */ (innerRequest));
};

82
node_modules/enhanced-resolve/lib/getPaths.js generated vendored Normal file
View File

@@ -0,0 +1,82 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("./Resolver").FileSystem} FileSystem */
/** @typedef {{ paths: string[], segments: string[] }} GetPathsResult */
/**
* Walk `path` from tip to root, returning every ancestor directory (plus the
* input itself) in `paths`, and each corresponding segment name in `segments`.
*
* The return value may be shared across callers via `getPathsCached` — treat
* it as read-only. Callers that need to mutate (currently only
* `SymlinkPlugin`) should `slice()` the arrays locally before writing.
* @param {string} path path
* @returns {GetPathsResult} paths and segments
*/
function getPaths(path) {
if (path === "/") return { paths: ["/"], segments: [""] };
const parts = path.split(/(.*?[\\/]+)/);
const paths = [path];
const segments = [parts[parts.length - 1]];
let part = parts[parts.length - 1];
path = path.slice(0, Math.max(0, path.length - part.length - 1));
for (let i = parts.length - 2; i > 2; i -= 2) {
paths.push(path);
part = parts[i];
path = path.slice(0, Math.max(0, path.length - part.length)) || "/";
segments.push(part.slice(0, -1));
}
[, part] = parts;
segments.push(part);
paths.push(part);
return {
paths,
segments,
};
}
/**
* Per-filesystem memoization of `getPaths`. Kept in a standalone WeakMap
* rather than being hung off `resolver.pathCache` so that adding this cache
* does not change the hidden-class shape of `pathCache` — which is accessed
* on the hot path of every resolve as `resolver.pathCache.{join,dirname,
* basename}.fn(...)`. CodSpeed caught that shape change as a ~12%
* instruction-count regression on `cache-predicate`, so we keep pathCache
* shape-stable by owning this cache here instead.
*
* The cache lifetime is tied to the filesystem object (same invariant as
* `_pathCacheByFs` in `Resolver.js`): when the user swaps filesystems the
* entries become unreachable and get collected.
* @type {WeakMap<FileSystem, Map<string, GetPathsResult>>}
*/
const _getPathsCacheByFs = new WeakMap();
/**
* Memoized `getPaths`. The returned object is shared across callers — do
* not mutate the `paths` or `segments` arrays in-place; `slice()` first if
* you need a mutable copy.
* @param {FileSystem} fileSystem filesystem used as the cache namespace
* @param {string} path path
* @returns {GetPathsResult} paths and segments
*/
function getPathsCached(fileSystem, path) {
let cache = _getPathsCacheByFs.get(fileSystem);
if (cache === undefined) {
cache = new Map();
_getPathsCacheByFs.set(fileSystem, cache);
} else {
const cached = cache.get(path);
if (cached !== undefined) return cached;
}
const result = getPaths(path);
cache.set(path, result);
return result;
}
module.exports = getPaths;
module.exports.getPathsCached = getPathsCached;

310
node_modules/enhanced-resolve/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,310 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const memoize = require("./util/memoize");
/** @typedef {import("./CachedInputFileSystem").BaseFileSystem} BaseFileSystem */
/** @typedef {import("./PnpPlugin").PnpApiImpl} PnpApi */
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").Context} Context */
/** @typedef {import("./Resolver").FileSystem} FileSystem */
/** @typedef {import("./Resolver").ResolveCallback} ResolveCallback */
/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
/** @typedef {import("./ResolverFactory").Plugin} Plugin */
/** @typedef {import("./ResolverFactory").UserResolveOptions} ResolveOptions */
/**
* @typedef {{
* (context: Context, path: string, request: string, resolveContext: ResolveContext, callback: ResolveCallback): void,
* (context: Context, path: string, request: string, callback: ResolveCallback): void,
* (path: string, request: string, resolveContext: ResolveContext, callback: ResolveCallback): void,
* (path: string, request: string, callback: ResolveCallback): void,
* }} ResolveFunctionAsync
*/
/**
* @typedef {{
* (context: Context, path: string, request: string, resolveContext?: ResolveContext): string | false,
* (path: string, request: string, resolveContext?: ResolveContext): string | false,
* }} ResolveFunction
*/
/**
* @typedef {{
* (context: Context, path: string, request: string, resolveContext?: ResolveContext): Promise<string | false>,
* (path: string, request: string, resolveContext?: ResolveContext): Promise<string | false>,
* }} ResolveFunctionPromise
*/
const getCachedFileSystem = memoize(() => require("./CachedInputFileSystem"));
const getNodeFileSystem = memoize(() => {
const fs = require("graceful-fs");
const CachedInputFileSystem = getCachedFileSystem();
return new CachedInputFileSystem(fs, 4000);
});
const getNodeContext = memoize(() => ({
environments: ["node+es3+es5+process+native"],
}));
const getResolverFactory = memoize(() => require("./ResolverFactory"));
const getAsyncResolver = memoize(() =>
getResolverFactory().createResolver({
conditionNames: ["node"],
extensions: [".js", ".json", ".node"],
fileSystem: getNodeFileSystem(),
}),
);
/**
* @type {ResolveFunctionAsync}
*/
const resolve =
/**
* @param {object | string} context context
* @param {string} path path
* @param {string | ResolveContext | ResolveCallback} request request
* @param {ResolveContext | ResolveCallback=} resolveContext resolve context
* @param {ResolveCallback=} callback callback
*/
(context, path, request, resolveContext, callback) => {
if (typeof context === "string") {
callback = /** @type {ResolveCallback} */ (resolveContext);
resolveContext = /** @type {ResolveContext} */ (request);
request = path;
path = context;
context = getNodeContext();
}
if (typeof callback !== "function") {
callback = /** @type {ResolveCallback} */ (resolveContext);
}
getAsyncResolver().resolve(
context,
path,
/** @type {string} */ (request),
/** @type {ResolveContext} */ (resolveContext),
/** @type {ResolveCallback} */ (callback),
);
};
const getSyncResolver = memoize(() =>
getResolverFactory().createResolver({
conditionNames: ["node"],
extensions: [".js", ".json", ".node"],
useSyncFileSystemCalls: true,
fileSystem: getNodeFileSystem(),
}),
);
/**
* @type {ResolveFunction}
*/
const resolveSync =
/**
* @param {object | string} context context
* @param {string} path path
* @param {string | ResolveContext | undefined} request request
* @param {ResolveContext=} resolveContext resolve context
* @returns {string | false} resolved path
*/
(context, path, request, resolveContext) => {
if (typeof context === "string") {
resolveContext = /** @type {ResolveContext} */ (request);
request = path;
path = context;
context = getNodeContext();
}
return getSyncResolver().resolveSync(
context,
path,
/** @type {string} */ (request),
/** @type {ResolveContext} */ (resolveContext),
);
};
/**
* @type {ResolveFunctionPromise}
*/
const resolvePromise =
/**
* @param {object | string} context context
* @param {string} path path
* @param {string | ResolveContext | undefined} request request
* @param {ResolveContext=} resolveContext resolve context
* @returns {Promise<string | false>} resolved path
*/
(context, path, request, resolveContext) => {
if (typeof context === "string") {
resolveContext = /** @type {ResolveContext} */ (request);
request = path;
path = context;
context = getNodeContext();
}
return getAsyncResolver().resolvePromise(
context,
path,
/** @type {string} */ (request),
/** @type {ResolveContext} */ (resolveContext),
);
};
/** @typedef {Omit<ResolveOptions, "fileSystem"> & Partial<Pick<ResolveOptions, "fileSystem">>} ResolveOptionsOptionalFS */
/**
* @param {ResolveOptionsOptionalFS} options Resolver options
* @returns {ResolveFunctionAsync} Resolver function
*/
function create(options) {
const resolver = getResolverFactory().createResolver({
fileSystem: getNodeFileSystem(),
...options,
});
/**
* @param {object | string} context Custom context
* @param {string} path Base path
* @param {string | ResolveContext | ResolveCallback} request String to resolve
* @param {ResolveContext | ResolveCallback=} resolveContext Resolve context
* @param {ResolveCallback=} callback Result callback
*/
return function create(context, path, request, resolveContext, callback) {
if (typeof context === "string") {
callback = /** @type {ResolveCallback} */ (resolveContext);
resolveContext = /** @type {ResolveContext} */ (request);
request = path;
path = context;
context = getNodeContext();
}
if (typeof callback !== "function") {
callback = /** @type {ResolveCallback} */ (resolveContext);
}
resolver.resolve(
context,
path,
/** @type {string} */ (request),
/** @type {ResolveContext} */ (resolveContext),
callback,
);
};
}
/**
* @param {ResolveOptionsOptionalFS} options Resolver options
* @returns {ResolveFunction} Resolver function
*/
function createSync(options) {
const resolver = getResolverFactory().createResolver({
useSyncFileSystemCalls: true,
fileSystem: getNodeFileSystem(),
...options,
});
/**
* @param {object | string} context custom context
* @param {string} path base path
* @param {string | ResolveContext | undefined} request request to resolve
* @param {ResolveContext=} resolveContext Resolve context
* @returns {string | false} Resolved path or false
*/
return function createSync(context, path, request, resolveContext) {
if (typeof context === "string") {
resolveContext = /** @type {ResolveContext} */ (request);
request = path;
path = context;
context = getNodeContext();
}
return resolver.resolveSync(
context,
path,
/** @type {string} */ (request),
/** @type {ResolveContext} */ (resolveContext),
);
};
}
/**
* @param {ResolveOptionsOptionalFS} options Resolver options
* @returns {ResolveFunctionPromise} Resolver function
*/
function createPromise(options) {
const resolver = getResolverFactory().createResolver({
fileSystem: getNodeFileSystem(),
...options,
});
/**
* @param {object | string} context Custom context
* @param {string} path Base path
* @param {string | ResolveContext | undefined} request String to resolve
* @param {ResolveContext=} resolveContext Resolve context
* @returns {Promise<string | false>} resolved path
*/
return function createPromise(context, path, request, resolveContext) {
if (typeof context === "string") {
resolveContext = /** @type {ResolveContext} */ (request);
request = path;
path = context;
context = getNodeContext();
}
return resolver.resolvePromise(
context,
path,
/** @type {string} */ (request),
/** @type {ResolveContext} */ (resolveContext),
);
};
}
/**
* @template A
* @template B
* @param {A} obj input a
* @param {B} exports input b
* @returns {A & B} merged
*/
const mergeExports = (obj, exports) => {
const descriptors = Object.getOwnPropertyDescriptors(exports);
Object.defineProperties(obj, descriptors);
return /** @type {A & B} */ (Object.freeze(obj));
};
module.exports = mergeExports(resolve, {
get sync() {
return resolveSync;
},
get promise() {
return resolvePromise;
},
create: mergeExports(create, {
get sync() {
return createSync;
},
get promise() {
return createPromise;
},
}),
get ResolverFactory() {
return getResolverFactory();
},
get CachedInputFileSystem() {
return getCachedFileSystem();
},
get CloneBasenamePlugin() {
return require("./CloneBasenamePlugin");
},
get LogInfoPlugin() {
return require("./LogInfoPlugin");
},
get TsconfigPathsPlugin() {
return require("./TsconfigPathsPlugin");
},
get forEachBail() {
return require("./forEachBail");
},
});

745
node_modules/enhanced-resolve/lib/util/entrypoints.js generated vendored Normal file
View File

@@ -0,0 +1,745 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const { parseIdentifier } = require("./identifier");
/** @typedef {string | (string | ConditionalMapping)[]} DirectMapping */
/** @typedef {{ [k: string]: MappingValue }} ConditionalMapping */
/** @typedef {ConditionalMapping | DirectMapping | null} MappingValue */
/** @typedef {Record<string, MappingValue> | ConditionalMapping | DirectMapping} ExportsField */
/** @typedef {Record<string, MappingValue>} ImportsField */
/**
* Processing exports/imports field
* @callback FieldProcessor
* @param {string} request request
* @param {Set<string>} conditionNames condition names
* @returns {[string[], string | null]} resolved paths with used field
*/
/*
Example exports field:
{
".": "./main.js",
"./feature": {
"browser": "./feature-browser.js",
"default": "./feature.js"
}
}
Terminology:
Enhanced-resolve name keys ("." and "./feature") as exports field keys.
If value is string or string[], mapping is called as a direct mapping
and value called as a direct export.
If value is key-value object, mapping is called as a conditional mapping
and value called as a conditional export.
Key in conditional mapping is called condition name.
Conditional mapping nested in another conditional mapping is called nested mapping.
----------
Example imports field:
{
"#a": "./main.js",
"#moment": {
"browser": "./moment/index.js",
"default": "moment"
},
"#moment/": {
"browser": "./moment/",
"default": "moment/"
}
}
Terminology:
Enhanced-resolve name keys ("#a" and "#moment/", "#moment") as imports field keys.
If value is string or string[], mapping is called as a direct mapping
and value called as a direct export.
If value is key-value object, mapping is called as a conditional mapping
and value called as a conditional export.
Key in conditional mapping is called condition name.
Conditional mapping nested in another conditional mapping is called nested mapping.
*/
const slashCode = "/".charCodeAt(0);
const dotCode = ".".charCodeAt(0);
const hashCode = "#".charCodeAt(0);
const patternRegEx = /\*/g;
const DOLLAR_ESCAPE_RE = /\$/g;
/** @typedef {Record<string, MappingValue>} RecordMapping */
/**
* Cached `Object.keys()` for objects whose shape does not change after the
* first observation — i.e. parsed `package.json` fields and the nested
* conditional mappings inside them. `Object.keys` allocates a fresh array
* on every call; since `findMatch` / `conditionalMapping` run on every
* bare-specifier resolve, the allocation adds up quickly.
* @type {WeakMap<RecordMapping, string[]>}
*/
const _keysCache = new WeakMap();
/**
* @param {RecordMapping} obj object to read keys from
* @returns {string[]} cached keys array (DO NOT mutate)
*/
function cachedKeys(obj) {
let keys = _keysCache.get(obj);
if (keys === undefined) {
keys = Object.keys(obj);
_keysCache.set(obj, keys);
}
return keys;
}
/**
* Per-key precomputed info used by `findMatch`. Equivalent to what the
* previous implementation recomputed inline on every resolve.
* @typedef {object} FieldKeyInfo
* @property {string} key the original key
* @property {number} patternIndex position of the single "*" in the key, or -1 when absent
* @property {string} wildcardPrefix substring before "*" (empty when patternIndex === -1)
* @property {string} wildcardSuffix substring after "*" (empty when patternIndex === -1)
* @property {boolean} isLegacySubpath true when key is a legacy `./foo/`-style folder key with no "*"
* @property {boolean} isPattern true when key contains "*"
* @property {boolean} isSubpathMapping true when key ends with "/"
* @property {boolean} isValidPattern true when key has at most one "*"
*/
/**
* Cached per-field key metadata, keyed by the exports/imports field
* object. Computed lazily on first `findMatch` call and reused forever.
* Safe because `package.json` fields are immutable JSON values.
* @type {WeakMap<RecordMapping, FieldKeyInfo[]>}
*/
const _fieldKeyInfoCache = new WeakMap();
/**
* @param {ExportsField | ImportsField} field field object
* @returns {FieldKeyInfo[]} precomputed per-key info
*/
function getFieldKeyInfos(field) {
const fieldKey = /** @type {RecordMapping} */ (field);
let infos = _fieldKeyInfoCache.get(fieldKey);
if (infos !== undefined) return infos;
const keys = Object.getOwnPropertyNames(field);
infos = Array.from({ length: keys.length });
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const patternIndex = key.indexOf("*");
// `isValidPattern` is true when the key has at most one `*`. Searching
// from `patternIndex + 1` stops as soon as a second `*` is found, so
// we avoid the full-string scan that `lastIndexOf` would do — and the
// single-star common case finishes in one pass.
const isValidPattern =
patternIndex === -1 || !key.includes("*", patternIndex + 1);
const keyLen = key.length;
const endsWithSlash =
keyLen > 0 && key.charCodeAt(keyLen - 1) === slashCode;
infos[i] = {
key,
patternIndex,
wildcardPrefix: patternIndex === -1 ? "" : key.slice(0, patternIndex),
wildcardSuffix: patternIndex === -1 ? "" : key.slice(patternIndex + 1),
isLegacySubpath: patternIndex === -1 && endsWithSlash,
isPattern: patternIndex !== -1,
isSubpathMapping: endsWithSlash,
isValidPattern,
};
}
_fieldKeyInfoCache.set(fieldKey, infos);
return infos;
}
/**
* @param {string} a first string
* @param {string} b second string
* @returns {number} compare result
*/
function patternKeyCompare(a, b) {
const aPatternIndex = a.indexOf("*");
const bPatternIndex = b.indexOf("*");
const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1;
const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1;
if (baseLenA > baseLenB) return -1;
if (baseLenB > baseLenA) return 1;
if (aPatternIndex === -1) return 1;
if (bPatternIndex === -1) return -1;
if (a.length > b.length) return -1;
if (b.length > a.length) return 1;
return 0;
}
/** @typedef {[MappingValue, string, boolean, boolean, string] | null} MatchTuple */
/**
* Per-field memoization of `findMatch(request, field)`. For a given field
* the result depends only on the `request` string (it does NOT depend on
* `conditionNames` — that's applied separately by `conditionalMapping`),
* so we can cache the tuple keyed by request.
*
* Typical build traffic runs the same request through the resolver
* repeatedly (same import re-resolved from different source files, module
* graph traversals that revisit a package, etc.), and every one of those
* hits walks the same key list and allocates the same tuple. Caching the
* tuple turns the second-and-onward call into a single Map lookup.
*
* Keyed on the field object via a module-level `WeakMap`, so the cache
* is freed automatically when the owning description file is GC'd.
* @type {WeakMap<RecordMapping, Map<string, MatchTuple>>}
*/
const _findMatchCache = new WeakMap();
/**
* @param {string} request request
* @param {ExportsField | ImportsField} field exports or import field
* @returns {MatchTuple} match result (uncached)
*/
function computeFindMatch(request, field) {
const requestLen = request.length;
const requestEndsWithSlash =
requestLen > 0 && request.charCodeAt(requestLen - 1) === slashCode;
const requestHasStar = request.includes("*");
if (
!requestHasStar &&
!requestEndsWithSlash &&
Object.prototype.hasOwnProperty.call(field, request)
) {
const target = /** @type {{ [k: string]: MappingValue }} */ (field)[
request
];
return [target, "", false, false, request];
}
/** @type {string} */
let bestMatch = "";
/** @type {FieldKeyInfo | null} */
let bestMatchInfo = null;
/** @type {string | undefined} */
let bestMatchSubpath;
const infos = getFieldKeyInfos(field);
for (let i = 0; i < infos.length; i++) {
const info = infos[i];
const { key, patternIndex } = info;
if (patternIndex !== -1) {
if (
!info.isValidPattern ||
!request.startsWith(info.wildcardPrefix) ||
requestLen < key.length ||
!request.endsWith(info.wildcardSuffix) ||
patternKeyCompare(bestMatch, key) !== 1
) {
continue;
}
bestMatch = key;
bestMatchInfo = info;
bestMatchSubpath = request.slice(
patternIndex,
requestLen - info.wildcardSuffix.length,
);
} else if (
info.isLegacySubpath &&
request.startsWith(key) &&
patternKeyCompare(bestMatch, key) === 1
) {
bestMatch = key;
bestMatchInfo = info;
bestMatchSubpath = request.slice(key.length);
}
}
if (bestMatch === "") return null;
const target =
/** @type {{ [k: string]: MappingValue }} */
(field)[bestMatch];
return [
target,
/** @type {string} */ (bestMatchSubpath),
/** @type {FieldKeyInfo} */ (bestMatchInfo).isSubpathMapping,
/** @type {FieldKeyInfo} */ (bestMatchInfo).isPattern,
bestMatch,
];
}
/**
* Trying to match request to field
* @param {string} request request
* @param {ExportsField | ImportsField} field exports or import field
* @returns {MatchTuple} match or null, number is negative and one less when it's a folder mapping, number is request.length + 1 for direct mappings
*/
function findMatch(request, field) {
const fieldKey = /** @type {RecordMapping} */ (field);
let perRequest = _findMatchCache.get(fieldKey);
if (perRequest === undefined) {
perRequest = new Map();
_findMatchCache.set(fieldKey, perRequest);
} else {
// `computeFindMatch` only ever returns `MatchTuple | null` — never
// `undefined` — and `Map.set(k, null)` then `Map.get(k)` returns
// `null`, not `undefined`. So `get(...) === undefined` already
// unambiguously means "not cached yet"; one Map lookup is enough,
// no follow-up `has` needed to disambiguate "cached null".
const cached = perRequest.get(request);
if (cached !== undefined) return cached;
}
const result = computeFindMatch(request, field);
perRequest.set(request, result);
return result;
}
/**
* Sentinel stored in the conditional-mapping cache for inputs whose walk
* returns `null` ("no condition matched"). Using a non-null marker lets the
* cache-hit path be a single `WeakMap.get()` — we distinguish
* "cached null" from "not cached yet" without a second `has` call.
*/
const NULL_RESULT = Symbol("NULL_RESULT");
/**
* Memoization of `conditionalMapping(mapping, conditionNames)`. The result
* depends only on the mapping object (immutable — owned by a parsed
* `package.json`) and the `conditionNames` Set (owned by the resolver's
* options and stable for its lifetime), so it is safe to cache per (mapping,
* conditionNames) pair.
*
* A conditional `exports` entry that appears inside a `directMapping` array
* (the common `"browser": [...fallback list...]` shape, plus nested
* conditions) gets walked on every resolve that traverses the parent entry.
* Without this cache each of those walks re-reads `Object.keys` on the
* mapping and re-visits every condition until one matches, even though the
* inputs are identical.
*
* Outer key is the conditional mapping itself; inner key is the condition
* Set. Both are object references, so WeakMap-of-WeakMap lets both levels
* be collected automatically when the description file or resolver go away.
* @type {WeakMap<ConditionalMapping, WeakMap<Set<string>, DirectMapping | typeof NULL_RESULT>>}
*/
const _conditionalMappingCache = new WeakMap();
/**
* @param {ConditionalMapping} conditionalMapping_ conditional mapping
* @param {Set<string>} conditionNames condition names
* @returns {DirectMapping | null} direct mapping if found (uncached)
*/
function computeConditionalMapping(conditionalMapping_, conditionNames) {
/** @type {[ConditionalMapping, string[], number][]} */
const lookup = [[conditionalMapping_, cachedKeys(conditionalMapping_), 0]];
loop: while (lookup.length > 0) {
const top = lookup[lookup.length - 1];
const [mapping, conditions, j] = top;
for (let i = j; i < conditions.length; i++) {
const condition = conditions[i];
if (condition === "default" || conditionNames.has(condition)) {
const innerMapping = mapping[condition];
if (
innerMapping !== null &&
typeof innerMapping === "object" &&
!Array.isArray(innerMapping)
) {
const nested = /** @type {ConditionalMapping} */ (innerMapping);
top[2] = i + 1;
lookup.push([nested, cachedKeys(nested), 0]);
continue loop;
}
return /** @type {DirectMapping} */ (innerMapping);
}
}
lookup.pop();
}
return null;
}
/**
* @param {ConditionalMapping} conditionalMapping_ conditional mapping
* @param {Set<string>} conditionNames condition names
* @returns {DirectMapping | null} direct mapping if found
*/
function conditionalMapping(conditionalMapping_, conditionNames) {
let perSet = _conditionalMappingCache.get(conditionalMapping_);
if (perSet !== undefined) {
const cached = perSet.get(conditionNames);
if (cached !== undefined) {
return cached === NULL_RESULT
? null
: /** @type {DirectMapping} */ (cached);
}
} else {
perSet = new WeakMap();
_conditionalMappingCache.set(conditionalMapping_, perSet);
}
const result = computeConditionalMapping(conditionalMapping_, conditionNames);
perSet.set(conditionNames, result === null ? NULL_RESULT : result);
return result;
}
/**
* @param {string | undefined} remainingRequest remaining request when folder mapping, undefined for file mappings
* @param {boolean} isPattern true, if mapping is a pattern (contains "*")
* @param {boolean} isSubpathMapping true, for subpath mappings
* @param {string} mappingTarget direct export
* @param {(d: string, f: boolean) => void} assert asserting direct value
* @returns {string} mapping result
*/
function targetMapping(
remainingRequest,
isPattern,
isSubpathMapping,
mappingTarget,
assert,
) {
if (remainingRequest === undefined) {
assert(mappingTarget, false);
return mappingTarget;
}
if (isSubpathMapping) {
assert(mappingTarget, true);
return mappingTarget + remainingRequest;
}
assert(mappingTarget, false);
let result = mappingTarget;
if (isPattern) {
const escapedRemainder = remainingRequest.includes("$")
? remainingRequest.replace(DOLLAR_ESCAPE_RE, "$$")
: remainingRequest;
result = result.replace(patternRegEx, escapedRemainder);
}
return result;
}
/**
* @param {string | undefined} remainingRequest remaining request when folder mapping, undefined for file mappings
* @param {boolean} isPattern true, if mapping is a pattern (contains "*")
* @param {boolean} isSubpathMapping true, for subpath mappings
* @param {DirectMapping | null} mappingTarget direct export
* @param {Set<string>} conditionNames condition names
* @param {(d: string, f: boolean) => void} assert asserting direct value
* @returns {string[]} mapping result
*/
function directMapping(
remainingRequest,
isPattern,
isSubpathMapping,
mappingTarget,
conditionNames,
assert,
) {
if (mappingTarget === null) return [];
if (typeof mappingTarget === "string") {
return [
targetMapping(
remainingRequest,
isPattern,
isSubpathMapping,
mappingTarget,
assert,
),
];
}
/** @type {string[]} */
const targets = [];
for (let i = 0, len = mappingTarget.length; i < len; i++) {
const exp = mappingTarget[i];
if (typeof exp === "string") {
targets.push(
targetMapping(
remainingRequest,
isPattern,
isSubpathMapping,
exp,
assert,
),
);
continue;
}
const mapping = conditionalMapping(exp, conditionNames);
if (!mapping) continue;
const innerExports = directMapping(
remainingRequest,
isPattern,
isSubpathMapping,
mapping,
conditionNames,
assert,
);
for (let j = 0, innerLen = innerExports.length; j < innerLen; j++) {
targets.push(innerExports[j]);
}
}
return targets;
}
/** @type {[string[], null]} */
const EMPTY_NO_MATCH = /** @type {[string[], null]} */ ([[], null]);
/**
* @param {ExportsField | ImportsField} field root
* @param {(s: string) => string} normalizeRequest Normalize request, for `imports` field it adds `#`, for `exports` field it adds `.` or `./`
* @param {(s: string) => string} assertRequest assertRequest
* @param {(s: string, f: boolean) => void} assertTarget assertTarget
* @returns {FieldProcessor} field processor
*/
function createFieldProcessor(
field,
normalizeRequest,
assertRequest,
assertTarget,
) {
return function fieldProcessor(request, conditionNames) {
const match = findMatch(normalizeRequest(assertRequest(request)), field);
if (match === null) return EMPTY_NO_MATCH;
const [mapping, remainingRequest, isSubpathMapping, isPattern, usedField] =
match;
/** @type {DirectMapping | null} */
let direct;
if (
mapping !== null &&
typeof mapping === "object" &&
!Array.isArray(mapping)
) {
direct = conditionalMapping(
/** @type {ConditionalMapping} */ (mapping),
conditionNames,
);
if (direct === null) return EMPTY_NO_MATCH;
} else {
direct = /** @type {DirectMapping} */ (mapping);
}
return [
directMapping(
remainingRequest,
isPattern,
isSubpathMapping,
direct,
conditionNames,
assertTarget,
),
usedField,
];
};
}
/**
* @param {string} request request
* @returns {string} updated request
*/
function assertExportsFieldRequest(request) {
if (request.charCodeAt(0) !== dotCode) {
throw new Error('Request should be relative path and start with "."');
}
if (request.length === 1) return "";
if (request.charCodeAt(1) !== slashCode) {
throw new Error('Request should be relative path and start with "./"');
}
if (request.charCodeAt(request.length - 1) === slashCode) {
throw new Error("Only requesting file allowed");
}
return request.slice(2);
}
/**
* @param {ExportsField} field exports field
* @returns {ExportsField} normalized exports field
*/
function buildExportsField(field) {
// handle syntax sugar, if exports field is direct mapping for "."
if (typeof field === "string" || Array.isArray(field)) {
return { ".": field };
}
const keys = Object.keys(field);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (key.charCodeAt(0) !== dotCode) {
// handle syntax sugar, if exports field is conditional mapping for "."
if (i === 0) {
while (i < keys.length) {
const charCode = keys[i].charCodeAt(0);
if (charCode === dotCode || charCode === slashCode) {
throw new Error(
`Exports field key should be relative path and start with "." (key: ${JSON.stringify(
key,
)})`,
);
}
i++;
}
return { ".": field };
}
throw new Error(
`Exports field key should be relative path and start with "." (key: ${JSON.stringify(
key,
)})`,
);
}
if (key.length === 1) {
continue;
}
if (key.charCodeAt(1) !== slashCode) {
throw new Error(
`Exports field key should be relative path and start with "./" (key: ${JSON.stringify(
key,
)})`,
);
}
}
return field;
}
/**
* @param {string} exp export target
* @param {boolean} expectFolder is folder expected
*/
function assertExportTarget(exp, expectFolder) {
const parsedIdentifier = parseIdentifier(exp);
if (!parsedIdentifier) {
return;
}
const [relativePath] = parsedIdentifier;
const isFolder =
relativePath.charCodeAt(relativePath.length - 1) === slashCode;
if (isFolder !== expectFolder) {
throw new Error(
expectFolder
? `Expecting folder to folder mapping. ${JSON.stringify(
exp,
)} should end with "/"`
: `Expecting file to file mapping. ${JSON.stringify(
exp,
)} should not end with "/"`,
);
}
}
/**
* @param {ExportsField} exportsField the exports field
* @returns {FieldProcessor} process callback
*/
module.exports.processExportsField = function processExportsField(
exportsField,
) {
return createFieldProcessor(
buildExportsField(exportsField),
(request) => (request.length === 0 ? "." : `./${request}`),
assertExportsFieldRequest,
assertExportTarget,
);
};
/**
* @param {string} request request
* @returns {string} updated request
*/
function assertImportsFieldRequest(request) {
if (request.charCodeAt(0) !== hashCode) {
throw new Error('Request should start with "#"');
}
if (request.length === 1) {
throw new Error("Request should have at least 2 characters");
}
// Note: #/ patterns are now allowed per Node.js PR #60864
// https://github.com/nodejs/node/pull/60864
if (request.charCodeAt(request.length - 1) === slashCode) {
throw new Error("Only requesting file allowed");
}
return request.slice(1);
}
/**
* @param {string} imp import target
* @param {boolean} expectFolder is folder expected
*/
function assertImportTarget(imp, expectFolder) {
const parsedIdentifier = parseIdentifier(imp);
if (!parsedIdentifier) {
return;
}
const [relativePath] = parsedIdentifier;
const isFolder =
relativePath.charCodeAt(relativePath.length - 1) === slashCode;
if (isFolder !== expectFolder) {
throw new Error(
expectFolder
? `Expecting folder to folder mapping. ${JSON.stringify(
imp,
)} should end with "/"`
: `Expecting file to file mapping. ${JSON.stringify(
imp,
)} should not end with "/"`,
);
}
}
/**
* @param {ImportsField} importsField the exports field
* @returns {FieldProcessor} process callback
*/
module.exports.processImportsField = function processImportsField(
importsField,
) {
return createFieldProcessor(
importsField,
(request) => `#${request}`,
assertImportsFieldRequest,
assertImportTarget,
);
};

75
node_modules/enhanced-resolve/lib/util/fs.js generated vendored Normal file
View File

@@ -0,0 +1,75 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Natsu @xiaoxiaojx
*/
"use strict";
const stripJsonComments = require("./strip-json-comments");
/** @typedef {import("../Resolver").FileSystem} FileSystem */
/** @typedef {import("../Resolver").JsonObject} JsonObject */
/**
* @typedef {object} ReadJsonOptions
* @property {boolean=} stripComments Whether to strip JSONC comments
*/
/** @type {WeakMap<Buffer, JsonObject>} */
const _stripCommentsCache = new WeakMap();
/**
* Read and parse JSON file (supports JSONC with comments).
* Callback-based so a synchronous `fileSystem` stays synchronous all the
* way through — Promise wrapping would defer resolution by a Promise tick
* and break `resolveSync` when `tsconfig` is used together with
* `useSyncFileSystemCalls: true`.
* @param {FileSystem} fileSystem the file system
* @param {string} jsonFilePath absolute path to JSON file
* @param {ReadJsonOptions} options Options
* @param {(err: NodeJS.ErrnoException | Error | null, content?: JsonObject) => void} callback callback
* @returns {void}
*/
function readJson(fileSystem, jsonFilePath, options, callback) {
const { stripComments = false } = options;
const { readJson: fsReadJson } = fileSystem;
if (fsReadJson && !stripComments) {
fsReadJson(jsonFilePath, (err, content) => {
if (err) return callback(err);
callback(null, /** @type {JsonObject} */ (content));
});
return;
}
fileSystem.readFile(jsonFilePath, (err, data) => {
if (err) return callback(err);
const buf = /** @type {Buffer} */ (data);
if (stripComments) {
const cached = _stripCommentsCache.get(buf);
if (cached !== undefined) return callback(null, cached);
}
let result;
try {
const jsonText = buf.toString();
const jsonWithoutComments = stripComments
? stripJsonComments(jsonText, {
trailingCommas: true,
whitespace: true,
})
: jsonText;
result = JSON.parse(jsonWithoutComments);
} catch (parseErr) {
return callback(/** @type {Error} */ (parseErr));
}
if (stripComments) {
_stripCommentsCache.set(buf, result);
}
callback(null, result);
});
}
module.exports.readJson = readJson;

103
node_modules/enhanced-resolve/lib/util/identifier.js generated vendored Normal file
View File

@@ -0,0 +1,103 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const memorize = require("./memoize");
const getUrl = memorize(() => require("url"));
const PATH_QUERY_FRAGMENT_REGEXP =
/^(#?(?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/;
const ZERO_ESCAPE_REGEXP = /\0(.)/g;
const FILE_REG_EXP = /file:/i;
/**
* Index past a DOS device path prefix (`\\?\…` or `\\.\…`), or 0. Kept
* out of `parseIdentifier` on purpose: inlining it back bloats the caller
* beyond the size where V8's interpreter and JIT both handle it well
* (the cause of the description-files-multi CodSpeed regression).
* @param {string} identifier identifier known to start with `\`
* @returns {number} 4 if identifier starts with a DOS device prefix, else 0
*/
function dosPrefixEnd(identifier) {
if (
identifier.length >= 4 &&
identifier.charCodeAt(1) === 92 &&
identifier.charCodeAt(3) === 92
) {
const c2 = identifier.charCodeAt(2);
if (c2 === 63 || c2 === 46) return 4;
}
return 0;
}
/**
* @param {string} identifier identifier
* @returns {[string, string, string] | null} parsed identifier
*/
function parseIdentifier(identifier) {
if (!identifier) {
return null;
}
if (FILE_REG_EXP.test(identifier)) {
identifier = getUrl().fileURLToPath(identifier);
}
const firstEscape = identifier.indexOf("\0");
// Handle `\0`
if (firstEscape !== -1) {
const match = PATH_QUERY_FRAGMENT_REGEXP.exec(identifier);
if (!match) return null;
return [
match[1].replace(ZERO_ESCAPE_REGEXP, "$1"),
match[2] ? match[2].replace(ZERO_ESCAPE_REGEXP, "$1") : "",
match[3] || "",
];
}
// Fast path for inputs that don't use \0 escaping. DOS device paths
// (`\\?\…`, `\\.\…`) embed a literal `?` / `.` that must not be read
// as a query separator; skip past the prefix when the input actually
// starts with `\`. Gate is a single char-code compare so this function
// stays inside V8's inline budget for its hot callers (resolver parse).
const scanStart =
identifier.charCodeAt(0) === 92 ? dosPrefixEnd(identifier) : 0;
const queryStart = identifier.indexOf("?", scanStart);
// Start at index 1 (or past a DOS prefix) to ignore a possible leading hash.
const fragmentStart = identifier.indexOf("#", scanStart || 1);
if (fragmentStart < 0) {
if (queryStart < 0) {
// No fragment, no query
return [identifier, "", ""];
}
// Query, no fragment
return [identifier.slice(0, queryStart), identifier.slice(queryStart), ""];
}
if (queryStart < 0 || fragmentStart < queryStart) {
// Fragment, no query
return [
identifier.slice(0, fragmentStart),
"",
identifier.slice(fragmentStart),
];
}
// Query and fragment
return [
identifier.slice(0, queryStart),
identifier.slice(queryStart, fragmentStart),
identifier.slice(fragmentStart),
];
}
module.exports.parseIdentifier = parseIdentifier;

37
node_modules/enhanced-resolve/lib/util/memoize.js generated vendored Normal file
View File

@@ -0,0 +1,37 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/**
* @template T
* @typedef {() => T} FunctionReturning
*/
/**
* @template T
* @param {FunctionReturning<T>} fn memorized function
* @returns {FunctionReturning<T>} new function
*/
const memoize = (fn) => {
let cache = false;
/** @type {T | undefined} */
let result;
return () => {
if (cache) {
return /** @type {T} */ (result);
}
result = fn();
cache = true;
// Allow to clean up memory for fn
// and all dependent resources
/** @type {FunctionReturning<T> | undefined} */
(fn) = undefined;
return /** @type {T} */ (result);
};
};
module.exports = memoize;

View File

@@ -0,0 +1,8 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
module.exports = {};

344
node_modules/enhanced-resolve/lib/util/path.js generated vendored Normal file
View File

@@ -0,0 +1,344 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const path = require("path");
const CHAR_HASH = "#".charCodeAt(0);
const CHAR_SLASH = "/".charCodeAt(0);
const CHAR_BACKSLASH = "\\".charCodeAt(0);
const CHAR_A = "A".charCodeAt(0);
const CHAR_Z = "Z".charCodeAt(0);
const CHAR_LOWER_A = "a".charCodeAt(0);
const CHAR_LOWER_Z = "z".charCodeAt(0);
const CHAR_DOT = ".".charCodeAt(0);
const CHAR_COLON = ":".charCodeAt(0);
const CHAR_QUESTION = "?".charCodeAt(0);
const posixNormalize = path.posix.normalize;
const winNormalize = path.win32.normalize;
/**
* @enum {number}
*/
const PathType = Object.freeze({
Empty: 0,
Normal: 1,
Relative: 2,
AbsoluteWin: 3,
AbsolutePosix: 4,
Internal: 5,
});
const deprecatedInvalidSegmentRegEx =
/(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i;
const invalidSegmentRegEx =
/(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))?(\\|\/|$)/i;
/**
* @param {string} maybePath a path known to start with `\\`
* @returns {PathType} AbsoluteWin for `\\?\…` / `\\.\…`, otherwise Normal
*/
const getDosDeviceType = (maybePath) => {
if (maybePath.length >= 4 && maybePath.charCodeAt(3) === CHAR_BACKSLASH) {
const c2 = maybePath.charCodeAt(2);
if (c2 === CHAR_QUESTION || c2 === CHAR_DOT) {
return PathType.AbsoluteWin;
}
}
return PathType.Normal;
};
/**
* @param {string} maybePath a path
* @returns {PathType} type of path
*/
const getType = (maybePath) => {
switch (maybePath.length) {
case 0:
return PathType.Empty;
case 1: {
const c0 = maybePath.charCodeAt(0);
switch (c0) {
case CHAR_DOT:
return PathType.Relative;
case CHAR_SLASH:
return PathType.AbsolutePosix;
case CHAR_HASH:
return PathType.Internal;
}
return PathType.Normal;
}
case 2: {
const c0 = maybePath.charCodeAt(0);
switch (c0) {
case CHAR_DOT: {
const c1 = maybePath.charCodeAt(1);
switch (c1) {
case CHAR_DOT:
case CHAR_SLASH:
return PathType.Relative;
}
return PathType.Normal;
}
case CHAR_SLASH:
return PathType.AbsolutePosix;
case CHAR_HASH:
return PathType.Internal;
}
const c1 = maybePath.charCodeAt(1);
if (
c1 === CHAR_COLON &&
((c0 >= CHAR_A && c0 <= CHAR_Z) ||
(c0 >= CHAR_LOWER_A && c0 <= CHAR_LOWER_Z))
) {
return PathType.AbsoluteWin;
}
return PathType.Normal;
}
}
const c0 = maybePath.charCodeAt(0);
switch (c0) {
case CHAR_DOT: {
const c1 = maybePath.charCodeAt(1);
switch (c1) {
case CHAR_SLASH:
return PathType.Relative;
case CHAR_DOT: {
const c2 = maybePath.charCodeAt(2);
if (c2 === CHAR_SLASH) return PathType.Relative;
return PathType.Normal;
}
}
return PathType.Normal;
}
case CHAR_SLASH:
return PathType.AbsolutePosix;
case CHAR_HASH:
return PathType.Internal;
}
const c1 = maybePath.charCodeAt(1);
if (c1 === CHAR_COLON) {
const c2 = maybePath.charCodeAt(2);
if (
(c2 === CHAR_BACKSLASH || c2 === CHAR_SLASH) &&
((c0 >= CHAR_A && c0 <= CHAR_Z) ||
(c0 >= CHAR_LOWER_A && c0 <= CHAR_LOWER_Z))
) {
return PathType.AbsoluteWin;
}
}
// DOS device paths (`\\?\…`, `\\.\…`) are handled in a cold helper so
// this function stays small — inlining the full check here regressed
// `description-files-multi` under `--no-opt` interpretation. Here we
// only pay the two-byte gate for non-DOS inputs.
if (c0 === CHAR_BACKSLASH && c1 === CHAR_BACKSLASH) {
return getDosDeviceType(maybePath);
}
return PathType.Normal;
};
/**
* @param {string} maybePath a path
* @returns {string} the normalized path
*/
const normalize = (maybePath) => {
switch (getType(maybePath)) {
case PathType.Empty:
return maybePath;
case PathType.AbsoluteWin:
return winNormalize(maybePath);
case PathType.Relative: {
const r = posixNormalize(maybePath);
return getType(r) === PathType.Relative ? r : `./${r}`;
}
}
return posixNormalize(maybePath);
};
/**
* @param {string} rootPath the root path
* @param {string | undefined} request the request path
* @returns {string} the joined path
*/
const join = (rootPath, request) => {
if (!request) return normalize(rootPath);
const requestType = getType(request);
switch (requestType) {
case PathType.AbsolutePosix:
return posixNormalize(request);
case PathType.AbsoluteWin:
return winNormalize(request);
}
switch (getType(rootPath)) {
case PathType.Normal:
case PathType.Relative:
case PathType.AbsolutePosix:
return posixNormalize(`${rootPath}/${request}`);
case PathType.AbsoluteWin:
return winNormalize(`${rootPath}\\${request}`);
}
switch (requestType) {
case PathType.Empty:
return rootPath;
case PathType.Relative: {
const r = posixNormalize(rootPath);
return getType(r) === PathType.Relative ? r : `./${r}`;
}
}
return posixNormalize(rootPath);
};
/**
* @param {string} maybePath a path
* @returns {string} the directory name
*/
const dirname = (maybePath) => {
switch (getType(maybePath)) {
case PathType.AbsoluteWin:
return path.win32.dirname(maybePath);
}
return path.posix.dirname(maybePath);
};
/** @typedef {{ fn: (rootPath: string, request: string) => string, cache: Map<string, Map<string, string | undefined>> }} CachedJoin */
/**
* @returns {CachedJoin} cached join
*/
const createCachedJoin = () => {
/** @type {CachedJoin["cache"]} */
const cache = new Map();
/** @type {CachedJoin["fn"]} */
const fn = (rootPath, request) => {
/** @type {string | undefined} */
let cacheEntry;
let inner = cache.get(rootPath);
if (inner === undefined) {
cache.set(rootPath, (inner = new Map()));
} else {
cacheEntry = inner.get(request);
if (cacheEntry !== undefined) return cacheEntry;
}
cacheEntry = join(rootPath, request);
inner.set(request, cacheEntry);
return cacheEntry;
};
return { fn, cache };
};
/** @typedef {{ fn: (maybePath: string) => string, cache: Map<string, string> }} CachedDirname */
/**
* @returns {CachedDirname} cached dirname
*/
const createCachedDirname = () => {
/** @type {CachedDirname["cache"]} */
const cache = new Map();
/** @type {CachedDirname["fn"]} */
const fn = (maybePath) => {
const cacheEntry = cache.get(maybePath);
if (cacheEntry !== undefined) return cacheEntry;
const result = dirname(maybePath);
cache.set(maybePath, result);
return result;
};
return { fn, cache };
};
/** @typedef {{ fn: (maybePath: string, suffix?: string) => string, cache: Map<string, Map<string | undefined, string | undefined>> }} CachedBasename */
/**
* @returns {CachedBasename} cached basename
*/
const createCachedBasename = () => {
/** @type {CachedBasename["cache"]} */
const cache = new Map();
/** @type {CachedBasename["fn"]} */
const fn = (maybePath, suffix) => {
/** @type {string | undefined} */
let cacheEntry;
let inner = cache.get(maybePath);
if (inner === undefined) {
cache.set(maybePath, (inner = new Map()));
} else {
cacheEntry = inner.get(suffix);
if (cacheEntry !== undefined) return cacheEntry;
}
cacheEntry = path.basename(maybePath, suffix);
inner.set(suffix, cacheEntry);
return cacheEntry;
};
return { fn, cache };
};
/**
* Whether `request` is a relative request — i.e. matches `^\.\.?(?:\/|$)`.
*
* This is called on every `doResolve` via `UnsafeCachePlugin` and
* `getInnerRequest`, so the char-code form is meaningfully faster than the
* equivalent regex test: no regex state machine, no string object churn.
* @param {string} request request string
* @returns {boolean} true if request is relative
*/
const isRelativeRequest = (request) => {
const len = request.length;
if (len === 0 || request.charCodeAt(0) !== CHAR_DOT) return false;
if (len === 1) return true; // "."
const c1 = request.charCodeAt(1);
if (c1 === CHAR_SLASH) return true; // "./..."
if (c1 !== CHAR_DOT) return false; // ".x..."
if (len === 2) return true; // ".."
return request.charCodeAt(2) === CHAR_SLASH; // "../..."
};
/**
* Check if childPath is a subdirectory of parentPath.
*
* Called from `TsconfigPathsPlugin._selectPathsDataForContext` inside a loop
* over every tsconfig-paths context on every resolve, so it's worth keeping
* cheap. Compared to the previous `startsWith(normalize(parent + "/"))`
* version, this: checks the last char with `charCodeAt` instead of two
* `endsWith` calls; and skips `normalize()` entirely in the common case
* (parent has no trailing separator), since all we really need is the same
* anchoring effect — a cheap `startsWith` plus a separator char check on the
* byte immediately after `parentPath.length`.
* @param {string} parentPath parent directory path
* @param {string} childPath child path to check
* @returns {boolean} true if childPath is under parentPath
*/
const isSubPath = (parentPath, childPath) => {
const parentLen = parentPath.length;
if (parentLen === 0) {
// Match the old `normalize("" + "/") === "/"` fallback: an empty
// parent only "contains" a child that starts with a forward slash.
return childPath.length > 0 && childPath.charCodeAt(0) === CHAR_SLASH;
}
const lastChar = parentPath.charCodeAt(parentLen - 1);
if (lastChar === CHAR_SLASH || lastChar === CHAR_BACKSLASH) {
// Parent already ends with a separator — a plain prefix test is enough.
return childPath.startsWith(parentPath);
}
if (childPath.length <= parentLen) return false;
if (!childPath.startsWith(parentPath)) return false;
// Must be followed by a separator so "/app" doesn't match "/app-other".
const nextChar = childPath.charCodeAt(parentLen);
return nextChar === CHAR_SLASH || nextChar === CHAR_BACKSLASH;
};
module.exports.PathType = PathType;
module.exports.createCachedBasename = createCachedBasename;
module.exports.createCachedDirname = createCachedDirname;
module.exports.createCachedJoin = createCachedJoin;
module.exports.deprecatedInvalidSegmentRegEx = deprecatedInvalidSegmentRegEx;
module.exports.dirname = dirname;
module.exports.getType = getType;
module.exports.invalidSegmentRegEx = invalidSegmentRegEx;
module.exports.isRelativeRequest = isRelativeRequest;
module.exports.isSubPath = isSubPath;
module.exports.join = join;
module.exports.normalize = normalize;

View File

@@ -0,0 +1,23 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
module.exports = {
/**
* @type {Record<string, string>}
*/
versions: {},
// eslint-disable-next-line jsdoc/reject-function-type
/** @param {Function} fn function */
nextTick(fn) {
// eslint-disable-next-line prefer-rest-params
const args = Array.prototype.slice.call(arguments, 1);
Promise.resolve().then(() => {
// eslint-disable-next-line prefer-spread
fn.apply(null, args);
});
},
};

View File

@@ -0,0 +1,177 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Natsu @xiaoxiaojx
This file contains code ported from strip-json-comments:
https://github.com/sindresorhus/strip-json-comments
Original license: MIT
Original author: Sindre Sorhus
*/
"use strict";
/**
* @typedef {object} StripJsonCommentsOptions
* @property {boolean=} whitespace Replace comments with whitespace
* @property {boolean=} trailingCommas Strip trailing commas
*/
const singleComment = Symbol("singleComment");
const multiComment = Symbol("multiComment");
/**
* Strip without whitespace (returns empty string)
* @param {string} _string Unused
* @param {number} _start Unused
* @param {number} _end Unused
* @returns {string} Empty string for all input
*/
const stripWithoutWhitespace = (_string, _start, _end) => "";
/**
* Replace all characters except ASCII spaces, tabs and line endings with regular spaces to ensure valid JSON output.
* @param {string} string String to process
* @param {number} start Start index
* @param {number} end End index
* @returns {string} Processed string with comments replaced by whitespace
*/
const stripWithWhitespace = (string, start, end) =>
string.slice(start, end).replace(/[^ \t\r\n]/g, " ");
/**
* Check if a quote is escaped
* @param {string} jsonString JSON string
* @param {number} quotePosition Position of the quote
* @returns {boolean} True if the quote at the given position is escaped
*/
const isEscaped = (jsonString, quotePosition) => {
let index = quotePosition - 1;
let backslashCount = 0;
while (jsonString[index] === "\\") {
index -= 1;
backslashCount += 1;
}
return Boolean(backslashCount % 2);
};
/**
* Strip comments from JSON string
* @param {string} jsonString JSON string with potential comments
* @param {StripJsonCommentsOptions} options Options
* @returns {string} JSON string without comments
*/
function stripJsonComments(
jsonString,
{ whitespace = true, trailingCommas = false } = {},
) {
if (typeof jsonString !== "string") {
throw new TypeError(
`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``,
);
}
const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace;
let isInsideString = false;
/** @type {false | typeof singleComment | typeof multiComment} */
let isInsideComment = false;
let offset = 0;
let buffer = "";
let result = "";
let commaIndex = -1;
for (let index = 0; index < jsonString.length; index++) {
const currentCharacter = jsonString[index];
const nextCharacter = jsonString[index + 1];
if (!isInsideComment && currentCharacter === '"') {
// Enter or exit string
const escaped = isEscaped(jsonString, index);
if (!escaped) {
isInsideString = !isInsideString;
}
}
if (isInsideString) {
continue;
}
if (!isInsideComment && currentCharacter + nextCharacter === "//") {
// Enter single-line comment
buffer += jsonString.slice(offset, index);
offset = index;
isInsideComment = singleComment;
index++;
} else if (
isInsideComment === singleComment &&
currentCharacter + nextCharacter === "\r\n"
) {
// Exit single-line comment via \r\n
index++;
isInsideComment = false;
buffer += strip(jsonString, offset, index);
offset = index;
continue;
} else if (isInsideComment === singleComment && currentCharacter === "\n") {
// Exit single-line comment via \n
isInsideComment = false;
buffer += strip(jsonString, offset, index);
offset = index;
} else if (!isInsideComment && currentCharacter + nextCharacter === "/*") {
// Enter multiline comment
buffer += jsonString.slice(offset, index);
offset = index;
isInsideComment = multiComment;
index++;
continue;
} else if (
isInsideComment === multiComment &&
currentCharacter + nextCharacter === "*/"
) {
// Exit multiline comment
index++;
isInsideComment = false;
buffer += strip(jsonString, offset, index + 1);
offset = index + 1;
continue;
} else if (trailingCommas && !isInsideComment) {
if (commaIndex !== -1) {
if (currentCharacter === "}" || currentCharacter === "]") {
// Strip trailing comma
buffer += jsonString.slice(offset, index);
result += strip(buffer, 0, 1) + buffer.slice(1);
buffer = "";
offset = index;
commaIndex = -1;
} else if (
currentCharacter !== " " &&
currentCharacter !== "\t" &&
currentCharacter !== "\r" &&
currentCharacter !== "\n"
) {
// Hit non-whitespace following a comma; comma is not trailing
buffer += jsonString.slice(offset, index);
offset = index;
commaIndex = -1;
}
} else if (currentCharacter === ",") {
// Flush buffer prior to this point, and save new comma index
result += buffer + jsonString.slice(offset, index);
buffer = "";
offset = index;
commaIndex = index;
}
}
}
const remaining =
isInsideComment === singleComment
? strip(jsonString, offset, jsonString.length)
: jsonString.slice(offset);
return result + buffer + remaining;
}
module.exports = stripJsonComments;

View File

@@ -0,0 +1,21 @@
The MIT License
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,715 @@
# Tapable
The tapable package exposes many Hook classes, which can be used to create hooks for plugins.
```javascript
const {
AsyncParallelBailHook,
AsyncParallelHook,
AsyncSeriesBailHook,
AsyncSeriesHook,
AsyncSeriesWaterfallHook,
SyncBailHook,
SyncHook,
SyncLoopHook,
SyncWaterfallHook
} = require("tapable");
```
## Installation
```shell
npm install --save tapable
```
## Usage
All Hook constructors take one optional argument, which is a list of argument names as strings.
```js
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
```
The best practice is to expose all hooks of a class in a `hooks` property:
```js
class Car {
constructor() {
this.hooks = {
accelerate: new SyncHook(["newSpeed"]),
brake: new SyncHook(),
calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
};
}
/* ... */
}
```
Other people can now use these hooks:
```js
const myCar = new Car();
// Use the tap method to add a consumer (plugin)
myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on());
```
It's required to pass a name to identify the plugin/reason.
You may receive arguments:
```js
myCar.hooks.accelerate.tap("LoggerPlugin", (newSpeed) =>
console.log(`Accelerating to ${newSpeed}`)
);
```
For sync hooks, `tap` is the only valid method to add a plugin. Async hooks also support async plugins:
```js
myCar.hooks.calculateRoutes.tapPromise(
"GoogleMapsPlugin",
(source, target, routesList) =>
// return a promise
google.maps.findRoute(source, target).then((route) => {
routesList.add(route);
})
);
myCar.hooks.calculateRoutes.tapAsync(
"BingMapsPlugin",
(source, target, routesList, callback) => {
bing.findRoute(source, target, (err, route) => {
if (err) return callback(err);
routesList.add(route);
// call the callback
callback();
});
}
);
// You can still use sync plugins
myCar.hooks.calculateRoutes.tap(
"CachedRoutesPlugin",
(source, target, routesList) => {
const cachedRoute = cache.get(source, target);
if (cachedRoute) routesList.add(cachedRoute);
}
);
```
The class declaring these hooks needs to call them:
```js
class Car {
/**
* You won't get returned value from SyncHook or AsyncParallelHook,
* to do that, use SyncWaterfallHook and AsyncSeriesWaterfallHook respectively
*/
setSpeed(newSpeed) {
// following call returns undefined even when you returned values
this.hooks.accelerate.call(newSpeed);
}
useNavigationSystemPromise(source, target) {
const routesList = new List();
return this.hooks.calculateRoutes
.promise(source, target, routesList)
.then((res) =>
// res is undefined for AsyncParallelHook
routesList.getRoutes()
);
}
useNavigationSystemAsync(source, target, callback) {
const routesList = new List();
this.hooks.calculateRoutes.callAsync(source, target, routesList, (err) => {
if (err) return callback(err);
callback(null, routesList.getRoutes());
});
}
}
```
The Hook will compile a method with the most efficient way of running your plugins. It generates code depending on:
- The number of registered plugins (none, one, many)
- The kind of registered plugins (sync, async, promise)
- The used call method (sync, async, promise)
- The number of arguments
- Whether interception is used
This ensures fastest possible execution. See [Code generation](#code-generation) for more details on the runtime compilation.
## Plugin API
A plugin registers a callback on a hook using one of the `tap*` methods. The hook type determines which of these are valid (see [Hook classes](#hook-classes)):
- `hook.tap(nameOrOptions, fn)` — register a synchronous callback.
- `hook.tapAsync(nameOrOptions, fn)` — register a callback-based async callback. The last argument passed to `fn` is a node-style callback `(err, result)`.
- `hook.tapPromise(nameOrOptions, fn)` — register a promise-returning async callback. If `fn` returns something that is not thenable, the hook throws.
The first argument can be either a string (the plugin name) or an options object that also allows influencing the order in which taps run:
```js
hook.tap(
{
name: "MyPlugin",
stage: -10, // lower stages run earlier, default is 0
before: "OtherPlugin" // run before a named tap (string or string[])
},
(...args) => {
/* ... */
}
);
```
| Option | Type | Description |
| -------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name` | `string` | Required. Identifies the tap for debugging, interceptors, and the `before` option. |
| `stage` | `number` | Defaults to `0`. Taps with a lower stage run before taps with a higher stage. Taps with the same stage run in registration order. |
| `before` | `string` \| `string[]` | The tap is inserted before the named tap(s). Unknown names are ignored. Combined with `stage`, `before` wins for the taps it targets; other taps are still ordered by `stage`. |
The `name` is also used by some ecosystems (like webpack) for profiling and error messages. Within a single tap registration, later interceptors' `register` hooks may still replace the tap object (see [Interception](#interception)).
### `hook.withOptions(options)`
`withOptions` returns a facade around the hook whose `tap*` methods automatically merge `options` into every registration. It is useful for libraries that want to pre-configure a `stage` or `before` for all the taps they add:
```js
const lateHook = myCar.hooks.accelerate.withOptions({ stage: 10 });
lateHook.tap("LogAfterOthers", (speed) => console.log("final speed", speed));
// equivalent to: myCar.hooks.accelerate.tap({ name: "LogAfterOthers", stage: 10 }, ...)
```
The returned object does not expose the `call*` methods, so it is safe to hand out to plugins.
A runnable example showing how `withOptions` influences tap ordering:
```js
const { SyncHook } = require("tapable");
const hook = new SyncHook(["value"]);
hook.tap("Default", (v) => console.log("default", v));
// Pre-configure stage: 10 so all taps registered through `late` run last.
const late = hook.withOptions({ stage: 10 });
late.tap("RunLast", (v) => console.log("last", v));
// Pre-configure stage: -10 so these taps run first. Each facade can also
// be further narrowed via `withOptions`.
const early = hook.withOptions({ stage: -10 });
early.tap("RunFirst", (v) => console.log("first", v));
hook.call(1);
// first 1
// default 1
// last 1
```
Per-tap options override values from `withOptions`. For example, `late.tap({ name: "Override", stage: 0 }, fn)` ignores the facade's `stage: 10` and registers `fn` at stage `0`.
## Hook types
Each hook can be tapped with one or several functions. How they are executed depends on the hook type:
- Basic hook (without “Waterfall”, “Bail” or “Loop” in its name). This hook simply calls every function it tapped in a row.
- **Waterfall**. A waterfall hook also calls each tapped function in a row. Unlike the basic hook, it passes a return value from each function to the next function.
- **Bail**. A bail hook allows exiting early. When any of the tapped function returns anything, the bail hook will stop executing the remaining ones.
- **Loop**. When a plugin in a loop hook returns a non-undefined value the hook will restart from the first plugin. It will loop until all plugins return undefined.
Additionally, hooks can be synchronous or asynchronous. To reflect this, therere “Sync”, “AsyncSeries”, and “AsyncParallel” hook classes:
- **Sync**. A sync hook can only be tapped with synchronous functions (using `myHook.tap()`).
- **AsyncSeries**. An async-series hook can be tapped with synchronous, callback-based and promise-based functions (using `myHook.tap()`, `myHook.tapAsync()` and `myHook.tapPromise()`). They call each async method in a row.
- **AsyncParallel**. An async-parallel hook can also be tapped with synchronous, callback-based and promise-based functions (using `myHook.tap()`, `myHook.tapAsync()` and `myHook.tapPromise()`). However, they run each async method in parallel.
The hook type is reflected in its class name. E.g., `AsyncSeriesWaterfallHook` allows asynchronous functions and runs them in series, passing each functions return value into the next function.
## Hook classes
The table below summarizes the 9 built-in hook classes. For each class:
- **Tap methods** are the `tapX` variants that may be used to register a handler.
- **Call methods** are the ways the owner of the hook can trigger it.
- **Result** is the value returned from `call` (or passed to the `callAsync` callback / resolved from the `promise` call).
- **Returned value from tap** describes whether the value returned from a tapped function has an effect.
| Class | Tap methods | Call methods | Result | Returned value from tap |
| -------------------------- | ------------------------------- | ---------------------- | ----------------------------------------------- | ---------------------------------------------------- |
| `SyncHook` | `tap` | `call` | `undefined` | ignored |
| `SyncBailHook` | `tap` | `call` | first non-`undefined` value, or `undefined` | short-circuits the hook |
| `SyncWaterfallHook` | `tap` | `call` | final value (first argument after the last tap) | passed as first argument to the next tap |
| `SyncLoopHook` | `tap` | `call` | `undefined` | non-`undefined` restarts the loop from the first tap |
| `AsyncParallelHook` | `tap`, `tapAsync`, `tapPromise` | `callAsync`, `promise` | `undefined` | ignored |
| `AsyncParallelBailHook` | `tap`, `tapAsync`, `tapPromise` | `callAsync`, `promise` | first non-`undefined` value, or `undefined` | short-circuits the hook |
| `AsyncSeriesHook` | `tap`, `tapAsync`, `tapPromise` | `callAsync`, `promise` | `undefined` | ignored |
| `AsyncSeriesBailHook` | `tap`, `tapAsync`, `tapPromise` | `callAsync`, `promise` | first non-`undefined` value, or `undefined` | short-circuits the hook |
| `AsyncSeriesLoopHook` | `tap`, `tapAsync`, `tapPromise` | `callAsync`, `promise` | `undefined` | non-`undefined` restarts the loop from the first tap |
| `AsyncSeriesWaterfallHook` | `tap`, `tapAsync`, `tapPromise` | `callAsync`, `promise` | final value (first argument after the last tap) | passed as first argument to the next tap |
Detailed behavior of each class:
### SyncHook
A basic synchronous hook. Every tapped function is called in registration order with the arguments passed to `call`. Return values from tapped functions are ignored and `call` returns `undefined`.
- Tap methods: `tap`
- Call methods: `call`
- `tapAsync` and `tapPromise` throw an error.
```js
const hook = new SyncHook(["name"]);
hook.tap("A", (name) => console.log(`hello ${name}`));
hook.tap("B", (name) => console.log(`hi ${name}`));
hook.call("world");
// hello world
// hi world
```
### SyncBailHook
A synchronous hook that allows exiting early. Every tapped function is called in order until one returns a non-`undefined` value; that value becomes the result of `call` and the remaining taps are skipped. If all taps return `undefined`, `call` returns `undefined`.
- Tap methods: `tap`
- Call methods: `call`
```js
const hook = new SyncBailHook(["value"]);
hook.tap("Negative", (v) => (v < 0 ? "negative" : undefined));
hook.tap("Zero", (v) => (v === 0 ? "zero" : undefined));
hook.tap("Positive", (v) => "positive");
hook.call(-1); // "negative" (later taps skipped)
hook.call(5); // "positive"
```
### SyncWaterfallHook
A synchronous hook that threads a value through its tapped functions. The first argument passed to `call` is forwarded to the first tap. If a tap returns a non-`undefined` value it replaces that argument for the next tap; otherwise the previous value is kept. `call` returns the value after the last tap has run. Additional arguments (if any) are passed through unchanged.
- Tap methods: `tap`
- Call methods: `call`
```js
const hook = new SyncWaterfallHook(["value"]);
hook.tap("Double", (v) => v * 2);
hook.tap("PlusOne", (v) => v + 1);
hook.call(3); // 7 -> (3 * 2) + 1
```
### SyncLoopHook
A synchronous hook that keeps re-running its taps until all of them return `undefined` for a full pass. Whenever a tap returns a non-`undefined` value the hook restarts from the first tap. `call` returns `undefined`.
- Tap methods: `tap`
- Call methods: `call`
```js
const hook = new SyncLoopHook(["state"]);
let retries = 3;
hook.tap("Retry", () => {
if (retries-- > 0) return true; // non-undefined restarts the loop
});
hook.tap("Log", () => console.log("pass"));
hook.call({});
// pass (runs once all taps return undefined)
```
### AsyncParallelHook
An asynchronous hook that runs all of its tapped functions in parallel. It completes when every tap has signalled completion (sync return, callback, or promise resolution). Return values and resolution values are ignored; `callAsync`'s callback is invoked with no result and `promise()` resolves to `undefined`. If any tap errors, the error is forwarded and remaining taps still complete but their results are discarded.
- Tap methods: `tap`, `tapAsync`, `tapPromise`
- Call methods: `callAsync`, `promise`
```js
const hook = new AsyncParallelHook(["source"]);
hook.tapPromise("Fetch", (src) => fetch(src));
hook.tapAsync("Log", (src, cb) => {
console.log("fetching", src);
cb();
});
await hook.promise("https://example.com");
```
### AsyncParallelBailHook
Like `AsyncParallelHook`, but designed to bail out with a result. All tapped functions start in parallel; the first tap to produce a non-`undefined` value (synchronously, via its callback, or by resolving its promise) determines the hooks result. The remaining taps continue to run but their results are ignored. Order is determined by tap registration order: an earlier taps value takes precedence over a later ones, even if the later one finishes first.
- Tap methods: `tap`, `tapAsync`, `tapPromise`
- Call methods: `callAsync`, `promise`
```js
const hook = new AsyncParallelBailHook(["key"]);
hook.tapPromise("Cache", async (key) => cache.get(key));
hook.tapPromise("Db", async (key) => db.lookup(key));
const value = await hook.promise("user:42");
// First non-undefined result (by registration order) wins.
```
### AsyncSeriesHook
An asynchronous hook that runs tapped functions one after another, waiting for each to finish before starting the next. Results are ignored; `callAsync`'s callback is invoked with no result and `promise()` resolves to `undefined`. The first error aborts the series.
- Tap methods: `tap`, `tapAsync`, `tapPromise`
- Call methods: `callAsync`, `promise`
```js
const hook = new AsyncSeriesHook(["request"]);
hook.tapPromise("Authenticate", async (req) => authenticate(req));
hook.tapPromise("Log", async (req) => logger.info(req.url));
await hook.promise(request);
```
### AsyncSeriesBailHook
An asynchronous series hook that allows exiting early. Tapped functions run one after another; as soon as one produces a non-`undefined` value, that value becomes the hooks result and the remaining taps are skipped.
- Tap methods: `tap`, `tapAsync`, `tapPromise`
- Call methods: `callAsync`, `promise`
```js
const hook = new AsyncSeriesBailHook(["id"]);
hook.tapPromise("Memory", async (id) => memory.get(id));
hook.tapPromise("Disk", async (id) => disk.read(id));
const value = await hook.promise("doc-1");
// Stops at the first tap that produces a value.
```
### AsyncSeriesLoopHook
An asynchronous series hook that loops. Tapped functions run one after another; whenever a tap produces a non-`undefined` value the hook restarts from the first tap. The hook completes once a full pass yields `undefined` from every tap. The result is always `undefined`.
- Tap methods: `tap`, `tapAsync`, `tapPromise`
- Call methods: `callAsync`, `promise`
```js
const hook = new AsyncSeriesLoopHook(["job"]);
hook.tapPromise("Process", async (job) => {
const more = await job.step();
if (more) return true; // restart the loop
});
await hook.promise(job);
```
### AsyncSeriesWaterfallHook
An asynchronous series hook that threads a value through its taps. The first argument passed to `callAsync` / `promise` is forwarded to the first tap. A tap's non-`undefined` return / callback / resolution value replaces it for the next tap; `undefined` keeps the previous value. The hook completes with the value after the last tap.
- Tap methods: `tap`, `tapAsync`, `tapPromise`
- Call methods: `callAsync`, `promise`
```js
const hook = new AsyncSeriesWaterfallHook(["source"]);
hook.tapPromise("Read", async (src) => fs.readFile(src, "utf8"));
hook.tapPromise("Trim", async (text) => text.trim());
const output = await hook.promise("./input.txt");
```
## Interception
All hooks expose an `intercept(interceptor)` method. An interceptor is a plain object whose methods are invoked at specific points during the lifetime of the hook. Interceptors are invoked in registration order before the taps, and are useful for logging, tracing, profiling, or re-mapping tap options.
```js
myCar.hooks.calculateRoutes.intercept({
name: "LoggingInterceptor",
call: (source, target, routesList) => {
console.log("Starting to calculate routes");
},
tap: (tapInfo) => {
// tapInfo = { type: "promise", name: "GoogleMapsPlugin", fn: ..., stage: 0 }
console.log(`${tapInfo.name} is running`);
},
register: (tapInfo) => {
// Called once per tap (and for each tap already registered when the
// interceptor is added). Return a new tapInfo object to replace it.
console.log(`${tapInfo.name} is registered`);
return tapInfo;
}
});
```
| Handler | Signature | When it runs |
| ---------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `call` | `(...args) => void` | Before the hook starts executing its taps. Receives the arguments passed to `call` / `callAsync` / `promise`. |
| `tap` | `(tap: Tap) => void` | Before each tap runs. The `tap` object is a snapshot — mutations are ignored. |
| `loop` | `(...args) => void` | At the start of each iteration of a `SyncLoopHook` / `AsyncSeriesLoopHook`. |
| `error` | `(err: Error) => void` | Whenever a tap throws, rejects, or calls its callback with an error. |
| `result` | `(result: any) => void` | When a bail or waterfall hook produces a value, or when a tap produces one for a loop hook. |
| `done` | `() => void` | When the hook finishes successfully (no error, no early bail). |
| `register` | `(tap: Tap) => Tap \| undefined` | Once per tap at registration time (including taps that existed before the interceptor was added). Return a new `Tap` object to replace it. |
| `name` | `string` | Optional label used by ecosystems for debugging. |
| `context` | `boolean` | Opt into the shared `context` object. See [Context](#context). |
Adding an interceptor invalidates the hook's compiled call function — the next `call` / `callAsync` / `promise` recompiles it so that the new interceptor is woven in.
## Context
Plugins and interceptors can opt-in to access an optional `context` object, which can be used to pass arbitrary values to subsequent plugins and interceptors.
```js
myCar.hooks.accelerate.intercept({
context: true,
tap: (context, tapInfo) => {
// tapInfo = { type: "sync", name: "NoisePlugin", fn: ... }
console.log(`${tapInfo.name} is doing it's job`);
// `context` starts as an empty object if at least one plugin uses `context: true`.
// If no plugins use `context: true`, then `context` is undefined.
if (context) {
// Arbitrary properties can be added to `context`, which plugins can then access.
context.hasMuffler = true;
}
}
});
myCar.hooks.accelerate.tap(
{
name: "NoisePlugin",
context: true
},
(context, newSpeed) => {
if (context && context.hasMuffler) {
console.log("Silence...");
} else {
console.log("Vroom!");
}
}
);
```
## HookMap
A `HookMap` is a helper class that lazily creates hooks per key. The constructor takes a factory function; the first time a key is requested via `for(key)`, the factory is called and the resulting hook is cached.
```js
const keyedHook = new HookMap((key) => new SyncHook(["arg"]));
```
Plugins use `for(key)` to obtain the hook for a specific key (creating it on demand) and then `tap` on it as usual:
```js
keyedHook.for("some-key").tap("MyPlugin", (arg) => {
/* ... */
});
keyedHook.for("some-key").tapAsync("MyPlugin", (arg, callback) => {
/* ... */
});
keyedHook.for("some-key").tapPromise("MyPlugin", (arg) => {
/* ... */
});
```
The owner of the `HookMap` uses `get(key)` to look up an existing hook without creating one. This is typically preferred on the calling side so that keys no plugin cares about are never materialized:
```js
const hook = keyedHook.get("some-key");
if (hook !== undefined) {
hook.callAsync("arg", (err) => {
/* ... */
});
}
```
A `HookMap` can also be intercepted. `intercept({ factory })` wraps the factory so you can customize or replace the hook returned for each new key.
## Code generation
Tapable does not iterate over taps at call time. Instead, the first time `call`, `callAsync` or `promise` is invoked after the hook has been modified, the hook compiles a specialized function using `new Function(...)` and caches it on the instance. This is what the README means by "evals in code": the hook's dispatch logic is generated as a string and turned into a real JavaScript function the engine can inline and optimize.
The generated function is tailored to:
- **Call type** — whether the owner called `call` (sync), `callAsync` (callback), or `promise`. Each produces a different skeleton — e.g. `promise()` wraps the body in `new Promise((_resolve, _reject) => { ... })`.
- **Tap types** — for each tap, the generator emits the right invocation pattern: direct call for `tap`, node-style callback wrapping for `tapAsync`, and `.then(...)` chaining for `tapPromise`.
- **Hook class** — `SyncHook` emits a straight-line sequence of calls; `SyncBailHook` emits early-return checks; `SyncWaterfallHook` threads a value through calls; loop hooks wrap the body in a re-entry loop; `AsyncParallel*` fans the taps out and counts completions; `AsyncSeries*` chains them.
- **Interceptors** — if interceptors are attached, calls to their `call`/`tap`/`loop`/`error`/`result`/`done` handlers are spliced into the generated body; otherwise they cost nothing.
- **Context** — a `_context` object is only created when at least one tap or interceptor opts into it with `context: true`.
- **Arity** — the generated code hard-codes the number of arguments declared when the hook was constructed, so no `arguments`/rest handling happens at runtime.
The compiled function is invalidated (reset back to a one-shot "recompile then call" trampoline) whenever the hook's shape changes — i.e. on any new `tap*` or `intercept` call. Steady-state calls therefore run straight through the cached function with no per-tap branching.
### Why this matters
- You only pay for features you use. An interceptor-free, sync-only hook compiles down to a short sequence of direct function calls.
- Debugging a hook means reading the generated source. If you need to see it, `Hook.prototype.compile` returns the `new Function(...)` result — log `hook._createCall("sync").toString()` (or `"async"` / `"promise"`) to inspect the body.
- Because the dispatch is code-generated, a hook's behavior is fully determined at compile time. Mutating tap options after registration (for example, changing `stage` on an existing `Tap` object) will not reorder taps until you cause a recompile.
## Hook/HookMap interface
Public (callable by anyone holding a reference to the hook, i.e. the plugins):
```ts
interface Hook {
tap: (name: string | Tap, fn: (context?, ...args) => Result) => void;
tapAsync: (
name: string | Tap,
fn: (
context?,
...args,
callback: (err: Error | null, result: Result) => void
) => void
) => void;
tapPromise: (
name: string | Tap,
fn: (context?, ...args) => Promise<Result>
) => void;
intercept: (interceptor: HookInterceptor) => void;
withOptions: (
options: TapOptions
) => Omit<Hook, "call" | "callAsync" | "promise">;
}
interface HookInterceptor {
name?: string;
call?: (context?, ...args) => void;
loop?: (context?, ...args) => void;
tap?: (context?, tap: Tap) => void;
error?: (err: Error) => void;
result?: (result: any) => void;
done?: () => void;
register?: (tap: Tap) => Tap | undefined;
context?: boolean;
}
interface HookMap {
for: (key: any) => Hook;
intercept: (interceptor: HookMapInterceptor) => void;
}
interface HookMapInterceptor {
factory: (key: any, hook: Hook) => Hook;
}
interface Tap {
name: string;
type: "sync" | "async" | "promise";
fn: Function;
stage: number;
context: boolean;
before?: string | Array<string>;
}
```
Protected (only for the class containing the hook — it owns the right to trigger it):
```ts
interface Hook {
isUsed: () => boolean;
call: (...args) => Result;
promise: (...args) => Promise<Result>;
callAsync: (
...args,
callback: (err: Error | null, result: Result) => void
) => void;
}
interface HookMap {
get: (key: any) => Hook | undefined;
for: (key: any) => Hook;
}
```
`isUsed()` returns `true` when the hook has at least one tap or interceptor registered. Hook owners can use it to skip expensive argument preparation when no plugin is listening:
```js
class Car {
// ...
setSpeed(newSpeed) {
if (this.hooks.accelerate.isUsed()) {
this.hooks.accelerate.call(newSpeed);
}
}
// ...
}
```
## MultiHook
A `MultiHook` is a Hook-like facade that forwards `tap`, `tapAsync`, `tapPromise`, `intercept`, and `withOptions` to several underlying hooks at once. It does not expose `call*` methods — only the owners of the wrapped hooks decide when each of them runs. It is the typical way a class exposes a "happens on any of these events" listening surface without having the plugin wire itself up to every hook individually.
### Fan out a tap to several hooks
```js
const { MultiHook, SyncHook } = require("tapable");
class Car {
constructor() {
const accelerate = new SyncHook(["newSpeed"]);
const brake = new SyncHook();
this.hooks = {
accelerate,
brake,
// `anyMovement` is not a real hook — it simply re-registers taps
// on both `accelerate` and `brake`.
anyMovement: new MultiHook([accelerate, brake])
};
}
}
const car = new Car();
car.hooks.anyMovement.tap("Telemetry", () => console.log("car moved"));
car.hooks.accelerate.call(42); // "car moved"
car.hooks.brake.call(); // "car moved"
```
The `MultiHook` has no state of its own: the tap above ends up inside `accelerate.taps` and `brake.taps`.
### Forwarding async taps
`tapAsync` / `tapPromise` forward to every wrapped hook — it is the plugin's job to make sure they are all compatible. Registering a `tapPromise` on a `MultiHook` that wraps a `SyncHook` will throw at registration time for that hook.
```js
const build = new AsyncSeriesHook(["stats"]);
const rebuild = new AsyncSeriesHook(["stats"]);
const anyBuild = new MultiHook([build, rebuild]);
anyBuild.tapPromise("Report", async (stats) => report.send(stats));
```
### Shared interceptors and options
`intercept` and `withOptions` are also forwarded, so a `MultiHook` can be used to attach the same interceptor or pre-configured options to a group of hooks:
```js
const anyBuild = new MultiHook([build, rebuild]);
anyBuild.intercept({
call: () => console.log("build started"),
done: () => console.log("build done")
});
// Every tap added through `late` is staged late on both underlying hooks.
const late = anyBuild.withOptions({ stage: 10 });
late.tap("RunLast", () => {
/* ... */
});
```
### `isUsed`
`isUsed()` returns `true` if any of the wrapped hooks has at least one tap or interceptor, which lets the owner cheaply skip work when no one is listening on any of them:
```js
if (this.hooks.anyMovement.isUsed()) {
// expensive telemetry payload is only built when a plugin actually cares
this.hooks.accelerate.call(computeSpeed());
}
```

View File

@@ -0,0 +1,87 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class AsyncParallelBailHookCodeFactory extends HookCodeFactory {
content({ onError, onResult, onDone }) {
let code = "";
code += `var _results = new Array(${this.options.taps.length});\n`;
code += "var _checkDone = function() {\n";
code += "for(var i = 0; i < _results.length; i++) {\n";
code += "var item = _results[i];\n";
code += "if(item === undefined) return false;\n";
code += "if(item.result !== undefined) {\n";
code += onResult("item.result");
code += "return true;\n";
code += "}\n";
code += "if(item.error) {\n";
code += onError("item.error");
code += "return true;\n";
code += "}\n";
code += "}\n";
code += "return false;\n";
code += "}\n";
code += this.callTapsParallel({
onError: (i, err, done, doneBreak) => {
let code = "";
code += `if(${i} < _results.length && ((_results.length = ${
i + 1
}), (_results[${i}] = { error: ${err} }), _checkDone())) {\n`;
code += doneBreak(true);
code += "} else {\n";
code += done();
code += "}\n";
return code;
},
onResult: (i, result, done, doneBreak) => {
let code = "";
code += `if(${i} < _results.length && (${result} !== undefined && (_results.length = ${
i + 1
}), (_results[${i}] = { result: ${result} }), _checkDone())) {\n`;
code += doneBreak(true);
code += "} else {\n";
code += done();
code += "}\n";
return code;
},
onTap: (i, run, done, _doneBreak) => {
let code = "";
if (i > 0) {
code += `if(${i} >= _results.length) {\n`;
code += done();
code += "} else {\n";
}
code += run();
if (i > 0) code += "}\n";
return code;
},
onDone
});
return code;
}
}
const factory = new AsyncParallelBailHookCodeFactory();
function COMPILE(options) {
factory.setup(this, options);
return factory.create(options);
}
function AsyncParallelBailHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = AsyncParallelBailHook;
hook.compile = COMPILE;
hook._call = undefined;
hook.call = undefined;
return hook;
}
AsyncParallelBailHook.prototype = null;
module.exports = AsyncParallelBailHook;

View File

@@ -0,0 +1,37 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class AsyncParallelHookCodeFactory extends HookCodeFactory {
content({ onError, onDone }) {
return this.callTapsParallel({
onError: (i, err, done, doneBreak) => onError(err) + doneBreak(true),
onDone
});
}
}
const factory = new AsyncParallelHookCodeFactory();
function COMPILE(options) {
factory.setup(this, options);
return factory.create(options);
}
function AsyncParallelHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = AsyncParallelHook;
hook.compile = COMPILE;
hook._call = undefined;
hook.call = undefined;
return hook;
}
AsyncParallelHook.prototype = null;
module.exports = AsyncParallelHook;

View File

@@ -0,0 +1,42 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class AsyncSeriesBailHookCodeFactory extends HookCodeFactory {
content({ onError, onResult, resultReturns, onDone }) {
return this.callTapsSeries({
onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
onResult: (i, result, next) =>
`if(${result} !== undefined) {\n${onResult(
result
)}\n} else {\n${next()}}\n`,
resultReturns,
onDone
});
}
}
const factory = new AsyncSeriesBailHookCodeFactory();
function COMPILE(options) {
factory.setup(this, options);
return factory.create(options);
}
function AsyncSeriesBailHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = AsyncSeriesBailHook;
hook.compile = COMPILE;
hook._call = undefined;
hook.call = undefined;
return hook;
}
AsyncSeriesBailHook.prototype = null;
module.exports = AsyncSeriesBailHook;

View File

@@ -0,0 +1,37 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class AsyncSeriesHookCodeFactory extends HookCodeFactory {
content({ onError, onDone }) {
return this.callTapsSeries({
onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
onDone
});
}
}
const factory = new AsyncSeriesHookCodeFactory();
function COMPILE(options) {
factory.setup(this, options);
return factory.create(options);
}
function AsyncSeriesHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = AsyncSeriesHook;
hook.compile = COMPILE;
hook._call = undefined;
hook.call = undefined;
return hook;
}
AsyncSeriesHook.prototype = null;
module.exports = AsyncSeriesHook;

View File

@@ -0,0 +1,37 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class AsyncSeriesLoopHookCodeFactory extends HookCodeFactory {
content({ onError, onDone }) {
return this.callTapsLooping({
onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
onDone
});
}
}
const factory = new AsyncSeriesLoopHookCodeFactory();
function COMPILE(options) {
factory.setup(this, options);
return factory.create(options);
}
function AsyncSeriesLoopHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = AsyncSeriesLoopHook;
hook.compile = COMPILE;
hook._call = undefined;
hook.call = undefined;
return hook;
}
AsyncSeriesLoopHook.prototype = null;
module.exports = AsyncSeriesLoopHook;

View File

@@ -0,0 +1,48 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class AsyncSeriesWaterfallHookCodeFactory extends HookCodeFactory {
content({ onError, onResult, _onDone }) {
return this.callTapsSeries({
onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
onResult: (i, result, next) => {
let code = "";
code += `if(${result} !== undefined) {\n`;
code += `${this._args[0]} = ${result};\n`;
code += "}\n";
code += next();
return code;
},
onDone: () => onResult(this._args[0])
});
}
}
const factory = new AsyncSeriesWaterfallHookCodeFactory();
function COMPILE(options) {
factory.setup(this, options);
return factory.create(options);
}
function AsyncSeriesWaterfallHook(args = [], name = undefined) {
if (args.length < 1) {
throw new Error("Waterfall hooks must have at least one argument");
}
const hook = new Hook(args, name);
hook.constructor = AsyncSeriesWaterfallHook;
hook.compile = COMPILE;
hook._call = undefined;
hook.call = undefined;
return hook;
}
AsyncSeriesWaterfallHook.prototype = null;
module.exports = AsyncSeriesWaterfallHook;

View File

@@ -0,0 +1,233 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const util = require("util");
const deprecateContext = util.deprecate(
() => {},
"Hook.context is deprecated and will be removed"
);
function CALL_DELEGATE(...args) {
this.call = this._createCall("sync");
return this.call(...args);
}
function CALL_ASYNC_DELEGATE(...args) {
this.callAsync = this._createCall("async");
return this.callAsync(...args);
}
function PROMISE_DELEGATE(...args) {
this.promise = this._createCall("promise");
return this.promise(...args);
}
class Hook {
constructor(args = [], name = undefined) {
this._args = args;
this.name = name;
this.taps = [];
this.interceptors = [];
this._call = CALL_DELEGATE;
this.call = CALL_DELEGATE;
this._callAsync = CALL_ASYNC_DELEGATE;
this.callAsync = CALL_ASYNC_DELEGATE;
this._promise = PROMISE_DELEGATE;
this.promise = PROMISE_DELEGATE;
this._x = undefined;
// eslint-disable-next-line no-self-assign
this.compile = this.compile;
// eslint-disable-next-line no-self-assign
this.tap = this.tap;
// eslint-disable-next-line no-self-assign
this.tapAsync = this.tapAsync;
// eslint-disable-next-line no-self-assign
this.tapPromise = this.tapPromise;
}
compile(_options) {
throw new Error("Abstract: should be overridden");
}
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type
});
}
_tap(type, options, fn) {
if (typeof options === "string") {
// Fast path: a string options ("name") is by far the most common
// case. Build the final descriptor in a single allocation instead
// of creating `{ name }` and then `Object.assign`ing it.
const name = options.trim();
if (name === "") {
throw new Error("Missing name for tap");
}
options = { type, fn, name };
} else {
if (typeof options !== "object" || options === null) {
throw new Error("Invalid tap options");
}
let { name } = options;
if (typeof name === "string") {
name = name.trim();
}
if (typeof name !== "string" || name === "") {
throw new Error("Missing name for tap");
}
if (typeof options.context !== "undefined") {
deprecateContext();
}
// Fast path: only `name` is set. Build the descriptor as a literal
// so `_insert` and downstream consumers see the same hidden class
// as the string-options path, avoiding a polymorphic call site.
// Scan with `for...in` (cheaper than allocating `Object.keys`)
// to verify no other user-provided properties exist - e.g.
// webpack's `additionalAssets` - otherwise they'd be dropped.
let onlyName = true;
for (const key in options) {
if (key !== "name") {
onlyName = false;
break;
}
}
if (onlyName) {
options = { type, fn, name };
} else {
options.name = name;
// Preserve previous precedence: user-provided keys win over the internal `type`/`fn`.
options = Object.assign({ type, fn }, options);
}
}
options = this._runRegisterInterceptors(options);
this._insert(options);
}
tap(options, fn) {
this._tap("sync", options, fn);
}
tapAsync(options, fn) {
this._tap("async", options, fn);
}
tapPromise(options, fn) {
this._tap("promise", options, fn);
}
_runRegisterInterceptors(options) {
const { interceptors } = this;
const { length } = interceptors;
// Common case: no interceptors.
if (length === 0) return options;
for (let i = 0; i < length; i++) {
const interceptor = interceptors[i];
if (interceptor.register) {
const newOptions = interceptor.register(options);
if (newOptions !== undefined) {
options = newOptions;
}
}
}
return options;
}
withOptions(options) {
const mergeOptions = (opt) =>
Object.assign({}, options, typeof opt === "string" ? { name: opt } : opt);
return {
name: this.name,
tap: (opt, fn) => this.tap(mergeOptions(opt), fn),
tapAsync: (opt, fn) => this.tapAsync(mergeOptions(opt), fn),
tapPromise: (opt, fn) => this.tapPromise(mergeOptions(opt), fn),
intercept: (interceptor) => this.intercept(interceptor),
isUsed: () => this.isUsed(),
withOptions: (opt) => this.withOptions(mergeOptions(opt))
};
}
isUsed() {
return this.taps.length > 0 || this.interceptors.length > 0;
}
intercept(interceptor) {
this._resetCompilation();
this.interceptors.push(Object.assign({}, interceptor));
if (interceptor.register) {
for (let i = 0; i < this.taps.length; i++) {
this.taps[i] = interceptor.register(this.taps[i]);
}
}
}
_resetCompilation() {
this.call = this._call;
this.callAsync = this._callAsync;
this.promise = this._promise;
}
_insert(item) {
this._resetCompilation();
const { taps } = this;
const stage = typeof item.stage === "number" ? item.stage : 0;
// Fast path: the overwhelmingly common `hook.tap("name", fn)` case
// has no `before` and default stage 0. If the list is empty or the
// last tap's stage is <= the new item's stage the item belongs at
// the end - append in O(1), skipping the Set allocation and the
// shift loop.
if (!(typeof item.before === "string" || Array.isArray(item.before))) {
const n = taps.length;
if (n === 0 || (taps[n - 1].stage || 0) <= stage) {
taps[n] = item;
return;
}
}
let before;
if (typeof item.before === "string") {
before = new Set([item.before]);
} else if (Array.isArray(item.before)) {
before = new Set(item.before);
}
let i = taps.length;
while (i > 0) {
i--;
const tap = taps[i];
taps[i + 1] = tap;
const xStage = tap.stage || 0;
if (before) {
if (before.has(tap.name)) {
before.delete(tap.name);
continue;
}
if (before.size > 0) {
continue;
}
}
if (xStage > stage) {
continue;
}
i++;
break;
}
taps[i] = item;
}
}
Object.setPrototypeOf(Hook.prototype, null);
module.exports = Hook;

View File

@@ -0,0 +1,490 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
class HookCodeFactory {
constructor(config) {
this.config = config;
this.options = undefined;
this._args = undefined;
}
create(options) {
this.init(options);
let fn;
switch (options.type) {
case "sync":
fn = new Function(
this.args(),
`"use strict";\n${this.header()}${this.contentWithInterceptors({
onError: (err) => `throw ${err};\n`,
onResult: (result) => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})}`
);
break;
case "async":
fn = new Function(
this.args({
after: "_callback"
}),
`"use strict";\n${this.header()}${this.contentWithInterceptors({
onError: (err) => `_callback(${err});\n`,
onResult: (result) => `_callback(null, ${result});\n`,
onDone: () => "_callback();\n"
})}`
);
break;
case "promise": {
let errorHelperUsed = false;
const content = this.contentWithInterceptors({
onError: (err) => {
errorHelperUsed = true;
return `_error(${err});\n`;
},
onResult: (result) => `_resolve(${result});\n`,
onDone: () => "_resolve();\n"
});
let code = "";
code += '"use strict";\n';
code += this.header();
code += "return new Promise((function(_resolve, _reject) {\n";
if (errorHelperUsed) {
code += "var _sync = true;\n";
code += "function _error(_err) {\n";
code += "if(_sync)\n";
code +=
"_resolve(Promise.resolve().then((function() { throw _err; })));\n";
code += "else\n";
code += "_reject(_err);\n";
code += "};\n";
}
code += content;
if (errorHelperUsed) {
code += "_sync = false;\n";
}
code += "}));\n";
fn = new Function(this.args(), code);
break;
}
}
this.deinit();
return fn;
}
setup(instance, options) {
const { taps } = options;
const { length } = taps;
const fns = Array.from({ length });
for (let i = 0; i < length; i++) {
fns[i] = taps[i].fn;
}
instance._x = fns;
}
/**
* @param {{ type: "sync" | "promise" | "async", taps: Array<Tap>, interceptors: Array<Interceptor> }} options
*/
init(options) {
this.options = options;
// `_args` is only read (length / join / [0]) - never mutated - so we
// can share the caller's array directly instead of paying for a copy
// on every compile.
this._args = options.args;
this._joinedArgs = undefined;
}
deinit() {
this.options = undefined;
this._args = undefined;
this._joinedArgs = undefined;
}
contentWithInterceptors(options) {
if (this.options.interceptors.length > 0) {
const { onError, onResult, onDone } = options;
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.call) {
code += `${this.getInterceptor(i)}.call(${this.args({
before: interceptor.context ? "_context" : undefined
})});\n`;
}
}
code += this.content(
Object.assign(options, {
onError:
onError &&
((err) => {
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.error) {
code += `${this.getInterceptor(i)}.error(${err});\n`;
}
}
code += onError(err);
return code;
}),
onResult:
onResult &&
((result) => {
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.result) {
code += `${this.getInterceptor(i)}.result(${result});\n`;
}
}
code += onResult(result);
return code;
}),
onDone:
onDone &&
(() => {
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.done) {
code += `${this.getInterceptor(i)}.done();\n`;
}
}
code += onDone();
return code;
})
})
);
return code;
}
return this.content(options);
}
header() {
let code = "";
code += this.needContext() ? "var _context = {};\n" : "var _context;\n";
code += "var _x = this._x;\n";
if (this.options.interceptors.length > 0) {
code += "var _taps = this.taps;\n";
code += "var _interceptors = this.interceptors;\n";
}
return code;
}
needContext() {
const { taps } = this.options;
for (let i = 0; i < taps.length; i++) {
if (taps[i].context) return true;
}
return false;
}
callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
let code = "";
let hasTapCached = false;
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.tap) {
if (!hasTapCached) {
code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
hasTapCached = true;
}
code += `${this.getInterceptor(i)}.tap(${
interceptor.context ? "_context, " : ""
}_tap${tapIndex});\n`;
}
}
code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
const tap = this.options.taps[tapIndex];
switch (tap.type) {
case "sync":
if (!rethrowIfPossible) {
code += `var _hasError${tapIndex} = false;\n`;
code += "try {\n";
}
if (onResult) {
code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
} else {
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
}
if (!rethrowIfPossible) {
code += "} catch(_err) {\n";
code += `_hasError${tapIndex} = true;\n`;
code += onError("_err");
code += "}\n";
code += `if(!_hasError${tapIndex}) {\n`;
}
if (onResult) {
code += onResult(`_result${tapIndex}`);
}
if (onDone) {
code += onDone();
}
if (!rethrowIfPossible) {
code += "}\n";
}
break;
case "async": {
let cbCode = "";
cbCode += onResult
? `(function(_err${tapIndex}, _result${tapIndex}) {\n`
: `(function(_err${tapIndex}) {\n`;
cbCode += `if(_err${tapIndex}) {\n`;
cbCode += onError(`_err${tapIndex}`);
cbCode += "} else {\n";
if (onResult) {
cbCode += onResult(`_result${tapIndex}`);
}
if (onDone) {
cbCode += onDone();
}
cbCode += "}\n";
cbCode += "})";
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined,
after: cbCode
})});\n`;
break;
}
case "promise":
code += `var _hasResult${tapIndex} = false;\n`;
code += `var _promise${tapIndex} = _fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
code += `if (!_promise${tapIndex} || !_promise${tapIndex}.then)\n`;
code += ` throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise${tapIndex} + ')');\n`;
code += `_promise${tapIndex}.then((function(_result${tapIndex}) {\n`;
code += `_hasResult${tapIndex} = true;\n`;
if (onResult) {
code += onResult(`_result${tapIndex}`);
}
if (onDone) {
code += onDone();
}
code += `}), function(_err${tapIndex}) {\n`;
code += `if(_hasResult${tapIndex}) throw _err${tapIndex};\n`;
code += onError(
`!_err${tapIndex} ? new Error('Tap function (tapPromise) rejects "' + _err${tapIndex} + '" value') : _err${tapIndex}`
);
code += "});\n";
break;
}
return code;
}
callTapsSeries({
onError,
onResult,
resultReturns,
onDone,
doneReturns,
rethrowIfPossible
}) {
const { taps } = this.options;
const tapsLength = taps.length;
if (tapsLength === 0) return onDone();
// Inlined findIndex to avoid the callback allocation.
let firstAsync = -1;
for (let i = 0; i < tapsLength; i++) {
if (taps[i].type !== "sync") {
firstAsync = i;
break;
}
}
const somethingReturns = resultReturns || doneReturns;
// doneBreak doesn't depend on the loop variable - hoist to allocate once.
const doneBreak = (skipDone) => {
if (skipDone) return "";
return onDone();
};
let code = "";
let current = onDone;
let unrollCounter = 0;
for (let j = tapsLength - 1; j >= 0; j--) {
const i = j;
const unroll =
current !== onDone && (taps[i].type !== "sync" || unrollCounter++ > 20);
if (unroll) {
unrollCounter = 0;
code += `function _next${i}() {\n`;
code += current();
code += "}\n";
current = () => `${somethingReturns ? "return " : ""}_next${i}();\n`;
}
const done = current;
const content = this.callTap(i, {
onError: (error) => onError(i, error, done, doneBreak),
onResult:
onResult && ((result) => onResult(i, result, done, doneBreak)),
onDone: !onResult && done,
rethrowIfPossible:
rethrowIfPossible && (firstAsync < 0 || i < firstAsync)
});
current = () => content;
}
code += current();
return code;
}
callTapsLooping({ onError, onDone, rethrowIfPossible }) {
if (this.options.taps.length === 0) return onDone();
const syncOnly = this.options.taps.every((t) => t.type === "sync");
let code = "";
if (!syncOnly) {
code += "var _looper = (function() {\n";
code += "var _loopAsync = false;\n";
}
code += "var _loop;\n";
code += "do {\n";
code += "_loop = false;\n";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.loop) {
code += `${this.getInterceptor(i)}.loop(${this.args({
before: interceptor.context ? "_context" : undefined
})});\n`;
}
}
code += this.callTapsSeries({
onError,
onResult: (i, result, next, doneBreak) => {
let code = "";
code += `if(${result} !== undefined) {\n`;
code += "_loop = true;\n";
if (!syncOnly) code += "if(_loopAsync) _looper();\n";
code += doneBreak(true);
code += "} else {\n";
code += next();
code += "}\n";
return code;
},
onDone:
onDone &&
(() => {
let code = "";
code += "if(!_loop) {\n";
code += onDone();
code += "}\n";
return code;
}),
rethrowIfPossible: rethrowIfPossible && syncOnly
});
code += "} while(_loop);\n";
if (!syncOnly) {
code += "_loopAsync = true;\n";
code += "});\n";
code += "_looper();\n";
}
return code;
}
callTapsParallel({
onError,
onResult,
onDone,
rethrowIfPossible,
onTap = (i, run) => run()
}) {
const { taps } = this.options;
const tapsLength = taps.length;
if (tapsLength <= 1) {
return this.callTapsSeries({
onError,
onResult,
onDone,
rethrowIfPossible
});
}
// done and doneBreak don't depend on the loop variable - hoist them
// so they're allocated once per compile instead of once per tap.
const done = () => {
if (onDone) return "if(--_counter === 0) _done();\n";
return "--_counter;";
};
const doneBreak = (skipDone) => {
if (skipDone || !onDone) return "_counter = 0;\n";
return "_counter = 0;\n_done();\n";
};
let code = "";
code += "do {\n";
code += `var _counter = ${tapsLength};\n`;
if (onDone) {
code += "var _done = (function() {\n";
code += onDone();
code += "});\n";
}
for (let i = 0; i < tapsLength; i++) {
code += "if(_counter <= 0) break;\n";
code += onTap(
i,
() =>
this.callTap(i, {
onError: (error) => {
let code = "";
code += "if(_counter > 0) {\n";
code += onError(i, error, done, doneBreak);
code += "}\n";
return code;
},
onResult:
onResult &&
((result) => {
let code = "";
code += "if(_counter > 0) {\n";
code += onResult(i, result, done, doneBreak);
code += "}\n";
return code;
}),
onDone: !onResult && (() => done()),
rethrowIfPossible
}),
done,
doneBreak
);
}
code += "} while(false);\n";
return code;
}
args({ before, after } = {}) {
// Hot during code generation. Join `_args` once and cache the result,
// then build the customized variants via string concat instead of
// allocating temporary `[before, ...allArgs]` / `[...allArgs, after]`
// arrays and re-joining.
let joined = this._joinedArgs;
if (joined === undefined) {
joined = this._args.length === 0 ? "" : this._args.join(", ");
this._joinedArgs = joined;
}
if (!before && !after) return joined;
if (joined.length === 0) {
if (before && after) return `${before}, ${after}`;
return before || after;
}
if (before && after) return `${before}, ${joined}, ${after}`;
if (before) return `${before}, ${joined}`;
return `${joined}, ${after}`;
}
getTapFn(idx) {
return `_x[${idx}]`;
}
getTap(idx) {
return `_taps[${idx}]`;
}
getInterceptor(idx) {
return `_interceptors[${idx}]`;
}
}
module.exports = HookCodeFactory;

View File

@@ -0,0 +1,73 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const util = require("util");
const defaultFactory = (key, hook) => hook;
class HookMap {
constructor(factory, name = undefined) {
this._map = new Map();
this.name = name;
this._factory = factory;
this._interceptors = [];
}
get(key) {
return this._map.get(key);
}
for(key) {
// Hot path: inline the map lookup to skip the `this.get(key)`
// indirection. This gets hit on every hook access in consumers
// like webpack.
const map = this._map;
const hook = map.get(key);
if (hook !== undefined) {
return hook;
}
let newHook = this._factory(key);
const interceptors = this._interceptors;
for (let i = 0; i < interceptors.length; i++) {
newHook = interceptors[i].factory(key, newHook);
}
map.set(key, newHook);
return newHook;
}
intercept(interceptor) {
this._interceptors.push(
Object.assign(
{
factory: defaultFactory
},
interceptor
)
);
}
}
HookMap.prototype.tap = util.deprecate(function tap(key, options, fn) {
return this.for(key).tap(options, fn);
}, "HookMap#tap(key,…) is deprecated. Use HookMap#for(key).tap(…) instead.");
HookMap.prototype.tapAsync = util.deprecate(function tapAsync(
key,
options,
fn
) {
return this.for(key).tapAsync(options, fn);
}, "HookMap#tapAsync(key,…) is deprecated. Use HookMap#for(key).tapAsync(…) instead.");
HookMap.prototype.tapPromise = util.deprecate(function tapPromise(
key,
options,
fn
) {
return this.for(key).tapPromise(options, fn);
}, "HookMap#tapPromise(key,…) is deprecated. Use HookMap#for(key).tapPromise(…) instead.");
module.exports = HookMap;

View File

@@ -0,0 +1,57 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
class MultiHook {
constructor(hooks, name = undefined) {
this.hooks = hooks;
this.name = name;
}
tap(options, fn) {
const { hooks } = this;
for (let i = 0; i < hooks.length; i++) {
hooks[i].tap(options, fn);
}
}
tapAsync(options, fn) {
const { hooks } = this;
for (let i = 0; i < hooks.length; i++) {
hooks[i].tapAsync(options, fn);
}
}
tapPromise(options, fn) {
const { hooks } = this;
for (let i = 0; i < hooks.length; i++) {
hooks[i].tapPromise(options, fn);
}
}
isUsed() {
const { hooks } = this;
for (let i = 0; i < hooks.length; i++) {
if (hooks[i].isUsed()) return true;
}
return false;
}
intercept(interceptor) {
const { hooks } = this;
for (let i = 0; i < hooks.length; i++) {
hooks[i].intercept(interceptor);
}
}
withOptions(options) {
return new MultiHook(
this.hooks.map((hook) => hook.withOptions(options)),
this.name
);
}
}
module.exports = MultiHook;

View File

@@ -0,0 +1,51 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class SyncBailHookCodeFactory extends HookCodeFactory {
content({ onError, onResult, resultReturns, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onResult: (i, result, next) =>
`if(${result} !== undefined) {\n${onResult(
result
)};\n} else {\n${next()}}\n`,
resultReturns,
onDone,
rethrowIfPossible
});
}
}
const factory = new SyncBailHookCodeFactory();
const TAP_ASYNC = () => {
throw new Error("tapAsync is not supported on a SyncBailHook");
};
const TAP_PROMISE = () => {
throw new Error("tapPromise is not supported on a SyncBailHook");
};
function COMPILE(options) {
factory.setup(this, options);
return factory.create(options);
}
function SyncBailHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = SyncBailHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
hook.compile = COMPILE;
return hook;
}
SyncBailHook.prototype = null;
module.exports = SyncBailHook;

View File

@@ -0,0 +1,46 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class SyncHookCodeFactory extends HookCodeFactory {
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
const factory = new SyncHookCodeFactory();
const TAP_ASYNC = () => {
throw new Error("tapAsync is not supported on a SyncHook");
};
const TAP_PROMISE = () => {
throw new Error("tapPromise is not supported on a SyncHook");
};
function COMPILE(options) {
factory.setup(this, options);
return factory.create(options);
}
function SyncHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = SyncHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
hook.compile = COMPILE;
return hook;
}
SyncHook.prototype = null;
module.exports = SyncHook;

View File

@@ -0,0 +1,46 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class SyncLoopHookCodeFactory extends HookCodeFactory {
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsLooping({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
const factory = new SyncLoopHookCodeFactory();
const TAP_ASYNC = () => {
throw new Error("tapAsync is not supported on a SyncLoopHook");
};
const TAP_PROMISE = () => {
throw new Error("tapPromise is not supported on a SyncLoopHook");
};
function COMPILE(options) {
factory.setup(this, options);
return factory.create(options);
}
function SyncLoopHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = SyncLoopHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
hook.compile = COMPILE;
return hook;
}
SyncLoopHook.prototype = null;
module.exports = SyncLoopHook;

View File

@@ -0,0 +1,58 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class SyncWaterfallHookCodeFactory extends HookCodeFactory {
content({ onError, onResult, resultReturns, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onResult: (i, result, next) => {
let code = "";
code += `if(${result} !== undefined) {\n`;
code += `${this._args[0]} = ${result};\n`;
code += "}\n";
code += next();
return code;
},
onDone: () => onResult(this._args[0]),
doneReturns: resultReturns,
rethrowIfPossible
});
}
}
const factory = new SyncWaterfallHookCodeFactory();
const TAP_ASYNC = () => {
throw new Error("tapAsync is not supported on a SyncWaterfallHook");
};
const TAP_PROMISE = () => {
throw new Error("tapPromise is not supported on a SyncWaterfallHook");
};
function COMPILE(options) {
factory.setup(this, options);
return factory.create(options);
}
function SyncWaterfallHook(args = [], name = undefined) {
if (args.length < 1) {
throw new Error("Waterfall hooks must have at least one argument");
}
const hook = new Hook(args, name);
hook.constructor = SyncWaterfallHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
hook.compile = COMPILE;
return hook;
}
SyncWaterfallHook.prototype = null;
module.exports = SyncWaterfallHook;

View File

@@ -0,0 +1,19 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
module.exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
module.exports.AsyncParallelHook = require("./AsyncParallelHook");
module.exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
module.exports.AsyncSeriesHook = require("./AsyncSeriesHook");
module.exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
module.exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
module.exports.HookMap = require("./HookMap");
module.exports.MultiHook = require("./MultiHook");
module.exports.SyncBailHook = require("./SyncBailHook");
module.exports.SyncHook = require("./SyncHook");
module.exports.SyncLoopHook = require("./SyncLoopHook");
module.exports.SyncWaterfallHook = require("./SyncWaterfallHook");
module.exports.__esModule = true;

View File

@@ -0,0 +1,18 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
module.exports.deprecate = (fn, msg) => {
let once = true;
return function deprecate() {
if (once) {
// eslint-disable-next-line no-console
console.warn(`DeprecationWarning: ${msg}`);
once = false;
}
// eslint-disable-next-line prefer-rest-params
return fn.apply(this, arguments);
};
};

View File

@@ -0,0 +1,65 @@
{
"name": "tapable",
"version": "2.3.3",
"description": "Just a little module for plugins.",
"homepage": "https://github.com/webpack/tapable",
"repository": {
"type": "git",
"url": "http://github.com/webpack/tapable.git"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"license": "MIT",
"author": "Tobias Koppers @sokra",
"main": "lib/index.js",
"browser": {
"util": "./lib/util-browser.js"
},
"types": "./tapable.d.ts",
"files": [
"lib",
"tapable.d.ts"
],
"scripts": {
"lint": "npm run lint:code && npm run fmt:check",
"lint:code": "eslint --cache .",
"fmt": "npm run fmt:base -- --log-level warn --write",
"fmt:check": "npm run fmt:base -- --check",
"fmt:base": "node ./node_modules/prettier/bin/prettier.cjs --cache --ignore-unknown .",
"fix": "npm run fix:code && npm run fmt",
"fix:code": "npm run lint:code -- --fix",
"test": "jest",
"benchmark": "node --max-old-space-size=4096 --hash-seed=1 --random-seed=1 --no-opt --predictable --predictable-gc-schedule --interpreted-frames-native-stack --allow-natives-syntax --expose-gc --no-concurrent-sweeping ./benchmark/run.mjs",
"version": "changeset version",
"release": "changeset publish"
},
"jest": {
"transform": {
"test[\\\\/].+\\.js$": "babel-jest"
},
"snapshotFormat": {
"escapeString": true,
"printBasicPrototype": true
}
},
"devDependencies": {
"@babel/core": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"@changesets/cli": "^2.31.0",
"@changesets/get-github-info": "^0.8.0",
"@codspeed/core": "^5.3.0",
"@stylistic/eslint-plugin": "^5.2.3",
"babel-jest": "^30.3.0",
"eslint": "^9.28.0",
"eslint-config-webpack": "^4.6.3",
"jest": "^30.3.0",
"prettier": "^3.8.3",
"prettier-1": "npm:prettier@^1",
"tinybench": "^6.0.0"
},
"engines": {
"node": ">=6"
}
}

View File

@@ -0,0 +1,174 @@
type FixedSizeArray<T extends number, U> = T extends 0
? void[]
: ReadonlyArray<U> & {
0: U;
length: T;
};
type Measure<T extends number> = T extends 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
? T
: never;
type Append<T extends any[], U> = {
0: [U];
1: [T[0], U];
2: [T[0], T[1], U];
3: [T[0], T[1], T[2], U];
4: [T[0], T[1], T[2], T[3], U];
5: [T[0], T[1], T[2], T[3], T[4], U];
6: [T[0], T[1], T[2], T[3], T[4], T[5], U];
7: [T[0], T[1], T[2], T[3], T[4], T[5], T[6], U];
8: [T[0], T[1], T[2], T[3], T[4], T[5], T[6], T[7], U];
}[Measure<T["length"]>];
type AsArray<T> = T extends any[] ? T : [T];
declare class UnsetAdditionalOptions {
_UnsetAdditionalOptions: true;
}
type IfSet<X> = X extends UnsetAdditionalOptions ? {} : X;
type Callback<E, T> = (error: E | null, result?: T) => void;
type InnerCallback<E, T> = (error?: E | null | false, result?: T) => void;
type FullTap = Tap & {
type: "sync" | "async" | "promise";
fn: Function;
};
type Tap = TapOptions & {
name: string;
};
type TapOptions = {
before?: string;
stage?: number;
};
interface HookInterceptor<T, R, AdditionalOptions = UnsetAdditionalOptions> {
name?: string;
tap?: (tap: FullTap & IfSet<AdditionalOptions>) => void;
call?: (...args: any[]) => void;
loop?: (...args: any[]) => void;
error?: (err: Error) => void;
result?: (result: R) => void;
done?: () => void;
register?: (
tap: FullTap & IfSet<AdditionalOptions>
) => FullTap & IfSet<AdditionalOptions>;
}
type ArgumentNames<T extends any[]> = FixedSizeArray<T["length"], string>;
declare class Hook<T, R, AdditionalOptions = UnsetAdditionalOptions> {
constructor(args?: ArgumentNames<AsArray<T>>, name?: string);
name: string | undefined;
interceptors: HookInterceptor<T, R, AdditionalOptions>[];
taps: FullTap[];
intercept(interceptor: HookInterceptor<T, R, AdditionalOptions>): void;
isUsed(): boolean;
callAsync(...args: Append<AsArray<T>, Callback<Error, R>>): void;
promise(...args: AsArray<T>): Promise<R>;
tap(
options: string | (Tap & IfSet<AdditionalOptions>),
fn: (...args: AsArray<T>) => R
): void;
withOptions(
options: TapOptions & IfSet<AdditionalOptions>
): Omit<this, "call" | "callAsync" | "promise">;
}
export class SyncHook<
T,
R = void,
AdditionalOptions = UnsetAdditionalOptions
> extends Hook<T, R, AdditionalOptions> {
call(...args: AsArray<T>): R;
}
export class SyncBailHook<
T,
R,
AdditionalOptions = UnsetAdditionalOptions
> extends SyncHook<T, R, AdditionalOptions> {}
export class SyncLoopHook<
T,
AdditionalOptions = UnsetAdditionalOptions
> extends SyncHook<T, void, AdditionalOptions> {}
export class SyncWaterfallHook<
T,
R = AsArray<T>[0],
AdditionalOptions = UnsetAdditionalOptions
> extends SyncHook<T, R, AdditionalOptions> {}
declare class AsyncHook<
T,
R,
AdditionalOptions = UnsetAdditionalOptions
> extends Hook<T, R, AdditionalOptions> {
tapAsync(
options: string | (Tap & IfSet<AdditionalOptions>),
fn: (...args: Append<AsArray<T>, InnerCallback<Error, R>>) => void
): void;
tapPromise(
options: string | (Tap & IfSet<AdditionalOptions>),
fn: (...args: AsArray<T>) => Promise<R>
): void;
}
export class AsyncParallelHook<
T,
AdditionalOptions = UnsetAdditionalOptions
> extends AsyncHook<T, void, AdditionalOptions> {}
export class AsyncParallelBailHook<
T,
R,
AdditionalOptions = UnsetAdditionalOptions
> extends AsyncHook<T, R, AdditionalOptions> {}
export class AsyncSeriesHook<
T,
AdditionalOptions = UnsetAdditionalOptions
> extends AsyncHook<T, void, AdditionalOptions> {}
export class AsyncSeriesBailHook<
T,
R,
AdditionalOptions = UnsetAdditionalOptions
> extends AsyncHook<T, R, AdditionalOptions> {}
export class AsyncSeriesLoopHook<
T,
AdditionalOptions = UnsetAdditionalOptions
> extends AsyncHook<T, void, AdditionalOptions> {}
export class AsyncSeriesWaterfallHook<
T,
R = AsArray<T>[0],
AdditionalOptions = UnsetAdditionalOptions
> extends AsyncHook<T, R, AdditionalOptions> {}
type HookFactory<H, K = any> = (key: K) => H;
interface HookMapInterceptor<H, K = any> {
factory?: (key: K, hook: H) => H;
}
export class HookMap<H> {
constructor(factory: HookFactory<H>, name?: string);
name: string | undefined;
get(key: any): H | undefined;
for(key: any): H;
intercept(interceptor: HookMapInterceptor<H>): void;
}
type AnyHook = Hook<any, any>;
export class TypedHookMap<M extends Record<any, AnyHook>> {
constructor(factory: HookFactory<M[keyof M], keyof M>, name?: string);
name: string | undefined;
get<K extends keyof M>(key: K): M[K] | undefined;
for<K extends keyof M>(key: K): M[K];
intercept(interceptor: HookMapInterceptor<M[keyof M], keyof M>): void;
}
export class MultiHook<H> {
constructor(hooks: H[], name?: string);
name: string | undefined;
tap(options: string | Tap, fn?: Function): void;
tapAsync(options: string | Tap, fn?: Function): void;
tapPromise(options: string | Tap, fn?: Function): void;
}

83
node_modules/enhanced-resolve/package.json generated vendored Normal file
View File

@@ -0,0 +1,83 @@
{
"name": "enhanced-resolve",
"version": "5.22.1",
"description": "Offers a async require.resolve function. It's highly configurable.",
"homepage": "http://github.com/webpack/enhanced-resolve",
"repository": {
"type": "git",
"url": "git://github.com/webpack/enhanced-resolve.git"
},
"license": "MIT",
"author": "Tobias Koppers @sokra",
"main": "lib/index.js",
"browser": {
"process": "./lib/util/process-browser.js",
"module": "./lib/util/module-browser.js"
},
"types": "types.d.ts",
"files": [
"lib",
"types.d.ts",
"LICENSE"
],
"scripts": {
"prepare": "husky",
"lint": "npm run lint:code && npm run lint:types && npm run lint:types-test && npm run lint:special && npm run fmt:check && npm run lint:spellcheck",
"lint:code": "eslint --cache .",
"lint:special": "node node_modules/tooling/inherit-types && node node_modules/tooling/generate-types",
"lint:types": "tsc",
"lint:types-test": "tsc -p tsconfig.types.test.json",
"lint:spellcheck": "cspell --cache --no-must-find-files --quiet \"**/*.*\"",
"fmt": "npm run fmt:base -- --log-level warn --write",
"fmt:check": "npm run fmt:base -- --check",
"fmt:base": "node_modules/prettier/bin/prettier.cjs --cache --ignore-unknown .",
"fix": "npm run fix:code && npm run fix:special",
"fix:code": "npm run lint:code -- --fix",
"fix:special": "node node_modules/tooling/inherit-types --write && node node_modules/tooling/generate-types --write",
"type-report": "rimraf coverage && npm run cover:types && npm run cover:report && open-cli coverage/lcov-report/index.html",
"pretest": "npm run lint",
"test": "npm run test:coverage",
"test:only": "jest",
"test:watch": "npm run test:only -- --watch",
"test:coverage": "npm run test:only -- --collectCoverageFrom=\"lib/**/*.js\" --coverage",
"version": "changeset version",
"release": "changeset publish",
"benchmark": "node --max-old-space-size=4096 --hash-seed=1 --random-seed=1 --no-opt --predictable --predictable-gc-schedule --interpreted-frames-native-stack --allow-natives-syntax --expose-gc --no-concurrent-sweeping ./benchmark/run.mjs"
},
"lint-staged": {
"*.{js,cjs,mjs}": [
"eslint --cache --fix"
],
"*": [
"prettier --cache --write --ignore-unknown",
"cspell --cache --no-must-find-files"
]
},
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.3.3"
},
"devDependencies": {
"@changesets/cli": "^2.30.0",
"@changesets/get-github-info": "^0.8.0",
"@codspeed/core": "^5.2.0",
"@types/graceful-fs": "^4.1.6",
"@types/jest": "^30.0.0",
"@types/node": "^24.10.4",
"cspell": "^10.0.0",
"eslint": "^9.39.2",
"eslint-config-webpack": "^4.9.5",
"husky": "^9.1.7",
"jest": "^30.3.0",
"lint-staged": "^17.0.4",
"memfs": "^4.56.11",
"prettier": "^3.7.4",
"prettier-2": "npm:prettier@^2",
"tooling": "webpack/tooling#v1.26.1",
"tinybench": "^6.0.0",
"typescript": "^6.0.2"
},
"engines": {
"node": ">=10.13.0"
}
}

2007
node_modules/enhanced-resolve/types.d.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff