# webpack/webpack — codebase unpack

> Canonical page: https://codevetter.com/unpack/webpack

This is a deterministic Repo Unpack scan of webpack/webpack — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: 87a62dec2304cd2c163c41feeeb43fbe3e5c21dd (2026-09-22)
- Last upstream commit: refactor: move the last single-owner helpers out of lib/util (#22253)
- Generated by codevetter 1.12.0 on 2026-09-22
- 3999 files scanned · 15.0 MB

## Languages

- JavaScript: 16040 files
- CSS: 976 files
- JSON: 688 files
- HTML: 410 files
- Markdown: 242 files
- TypeScript: 108 files
- YAML: 25 files
- Shell: 4 files

## Repository structure

- test/ — 2199 files
- lib/ — 771 files
- examples/ — 763 files
- schemas/ — 122 files
- .changeset/ — 42 files
- tooling/ — 25 files
- .github/ — 21 files
- hot/ — 12 files

Probable source entrypoint: `lib/` (inferred from the tree, not upstream docs)

## Package

- package.json: webpack v5.111.1 — 80 declared dependencies

## Structural health signals

Average score 6.5/10 across 96 files; 47 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- 87a62dec2304 (2026-09-22) refactor: move the last single-owner helpers out of lib/util (#22253)
- 95930fdc7cb6 (2026-09-22) test(javascript): assert what load() owes, not what Deno can import (#22252)
- dd69da694de7 (2026-09-22) build(ci): install the JavaScript comparison corpus on every pull request (#22250)
- 306c34ffe0b9 (2026-09-22) fix(html): read a foreign script body as character data (#22249)
- 69cc45e3d86c (2026-09-22) fix(css): read an rgb() separator grammar before folding it (#22251)

## Overview

webpack is the JavaScript module bundler (v5.111.1, MIT, `main: lib/index.js`, CLI at `bin/webpack.js`): a `Compiler` builds a module graph from entry dependencies, a `Compilation` seals that graph into chunks, and the result is emitted as static assets for web, Node, worker, Deno, Bun, Electron and universal targets. Everything is `tapable` hooks over CommonJS + JSDoc source — the public config API is defined by JSON Schema in `schemas/` and every type, validator and declaration is code-generated from it by `tooling/generate-types.js`. What is distinctive about this tree is how much webpack now owns itself: its own acorn-derived ECMAScript parser (`lib/javascript/syntax-parser.js`, 17.8k lines), its own CSS and HTML parser/printer/minifier pairs, generated lookup-table modules (`lib/{css,html,javascript}/data.js`), and a comparison harness in `tooling/` that benchmarks those against the ecosystem and gates printer invariants in CI. It is aimed at application developers via config, and at the plugin/loader ecosystem via ~100 exported classes and several hundred hooks.

## System map

The public surface is `lib/index.js` — a lazily-evaluated getter object exporting the `webpack()` factory plus ~100 plugin/class names and namespaced sub-objects (`webpack.cli`, `webpack.javascript.syntax`, `webpack.container`, `webpack.sharing`). `lib/webpack.js` normalizes, defaults and validates options, constructs a `Compiler`, and hands off to `WebpackOptionsApply`, which is the single place that translates resolved options into the ~60 plugins a build implies. Runtime boundaries are the filesystem (`lib/fs/fs.js` declares `InputFileSystem`/`OutputFileSystem`, `NodeEnvironmentPlugin` supplies the Node implementations), the loader sandbox (`lib/loaders/LoaderRunner.js`), and optional network I/O (`lib/schemes/HttpUriPlugin.js`, `lib/hmr/lazyCompilationBackend.js`). Persistence is the filesystem cache pack under `lib/cache/` plus the custom binary serializer in `lib/serialization/`. Build/test is all yarn scripts in `package.json`, and CI is 13 GitHub Actions workflows with `test.yml` as the hub.
- `lib/index.js` is the public export surface: every entry is a memoized getter so `require("webpack")` does not eagerly load the ~770 files under `lib/` — `webpack.cli` and `webpack.util.terminalColors` are split specifically so reading a terminal color does not pull in the options schema. — [lib/index.js#L1-30](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/index.js#L1-L30) [lib/index.js#L250-420](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/index.js#L250-L420)
- The `webpack(options, callback)` factory in `lib/webpack.js` runs a precompiled schema check (`schemas/WebpackOptions.check`) first and only falls back to the full `validateSchema` (emitting a `DEP_WEBPACK_PRE_COMPILED_SCHEMA_INVALID` deprecation) when the fast check disagrees; an array config routes to `createMultiCompiler`. — [lib/webpack.js#L255-300](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/webpack.js#L255-L300) [lib/webpack.js#L108-140](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/webpack.js#L108-L140)
- `createCompiler` fixes the startup order: `getNormalizedWebpackOptions` → `applyWebpackOptionsBaseDefaults` → `new Compiler` → `NodeEnvironmentPlugin` → user plugins → `applyWebpackOptionsDefaults` → `environment`/`afterEnvironment` hooks → `WebpackOptionsApply.process` → `initialize` hook. User plugins are therefore applied *before* the mode/target-dependent defaults are filled in. — [lib/webpack.js#L143-197](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/webpack.js#L143-L197) [lib/config/WebpackOptionsApply.js#L1-70](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/WebpackOptionsApply.js#L1-L70)
- `lib/config/WebpackOptionsApply.js` (1,274 lines) is the option→plugin translation layer: it statically requires the always-on plugins (`JavascriptModulesPlugin`, `APIPlugin`, `ConstPlugin`, `RuntimePlugin`, `DefaultStatsFactoryPlugin`, …) and lazily requires target-specific ones (`NodeTargetPlugin`, `DenoTargetPlugin`, `BunTargetPlugin`, `ElectronTargetPlugin`, `EnableChunkLoadingPlugin`) inside the branches that need them. — [lib/config/WebpackOptionsApply.js#L8-63](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/WebpackOptionsApply.js#L8-L63) [lib/config/WebpackOptionsApply.js#L99-401](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/WebpackOptionsApply.js#L99-L401)
- `lib/config/target.js` holds the target preset table: `TARGETS` is a list of `[name, description, regExp, handler]` tuples covering `browserslist[:…]`, `web`, `webworker`, `node[X.Y]`, `async-node`, `electron[X.Y]-main/preload/renderer`, `nwjs`/`node-webkit`, `deno`, `bun`, and `getTargetProperties` throws a message enumerating all of them for an unknown target. — [lib/config/target.js#L128-560](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/target.js#L128-L560) [lib/config/target.js#L545-612](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/target.js#L545-L612)
- The storage layer is a two-tier cache: `MemoryCachePlugin`/`MemoryWithGcCachePlugin` for in-process, and `IdleFileCachePlugin` wrapping `PackFileCacheStrategy` (1,893 lines, with `Pack`, `PackContent` and `PackContainer` classes) for `cache.type: "filesystem"`, storing on an idle timer after the compiler goes idle. — [lib/cache/IdleFileCachePlugin.js#L48-115](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/IdleFileCachePlugin.js#L48-L115) [lib/cache/PackFileCacheStrategy.js#L57-130](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/PackFileCacheStrategy.js#L57-L130) [lib/cache/PackFileCacheStrategy.js#L1162-1180](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/PackFileCacheStrategy.js#L1162-L1180)
- Cache persistence rides on a hand-written binary serializer in `lib/serialization/` (`BinaryMiddleware`, `ObjectMiddleware`, `FileMiddleware`, plus per-type serializers for Map/Set/RegExp/Date/Error); classes opt in through `makeSerializable(Class, "webpack/lib/…")`, used across 144 files in `lib/`. — [lib/util/makeSerializable.js#L60-70](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/util/makeSerializable.js#L60-L70) [lib/serialization/ObjectMiddleware.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/serialization/ObjectMiddleware.js) [lib/serialization/FileMiddleware.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/serialization/FileMiddleware.js) [lib/util/serialization.js#L25-45](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/util/serialization.js#L25-L45)
- Deserialization resolves a class by request prefix: `ObjectMiddleware.registerLoader(/^webpack\/lib\//, …)` looks the suffix up in the generated `lib/util/internalSerializables.js` map and warns `<req> not found in internalSerializables` when a class was moved without regenerating — which is why `yarn fix:serializables` exists. — [lib/util/serialization.js#L25-50](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/util/serialization.js#L25-L50) [package.json#L52-53](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L52-L53)
- External integrations are deliberately few: runtime dependencies are `tapable` (hooks), `enhanced-resolve` (resolution), `watchpack` (watching), `webpack-sources` (source objects), `terser` + `minimizer-webpack-plugin` (minification worker pool), `@webassemblyjs/*` (wasm), `browserslist`, `es-module-lexer`, `mime-db`, `schema-utils` and `graceful-fs`. — [package.json#L120-140](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L120-L140)
- All commands are yarn scripts: `yarn fix` (`fix:code` + `fix:special` + `fmt`), `yarn lint` (a nine-stage `&&` chain ending in `fmt:check` and `cspell`), `yarn test:base` (the only sanctioned jest wrapper — it carries `--experimental-vm-modules --expose-gc --max-old-space-size=4096`), and `yarn fix:special`, which runs all seven generators. — [package.json#L36-56](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L36-L56) [package.json#L96-99](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L96-L99)
- Code generation is a first-class build step: `tooling/generate-types.js` reads `schemas/**/*.json` through Ajv `standaloneCode` and `json-schema-to-typescript` to emit precompiled validators, `declarations/*.d.ts` and `types.d.ts`, while `generate-{css,html,js}-data.js` derive the parser/minifier lookup tables from `mdn-data`, `@webref/*` and the pinned `acorn`. — [tooling/generate-types.js#L1-40](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tooling/generate-types.js#L1-L40) [package.json#L40-41](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L40-L41) [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md)
- CI is 13 workflows; `test.yml` gates everything expensive behind three cheap jobs (`integration: needs: [lint, basic, unit]`, same for `test262`, `parser`, `syntax-equivalence`, the Deno/Bun runtime matrix and the benchmark shards), and the integration matrix spans Node 10.x through current with sharded `cover:integration:a|b` uploads to Codecov. — [.github/workflows/test.yml#L247-310](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/test.yml#L247-L310) [.github/workflows/test.yml#L579-660](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/test.yml#L579-L660) [.github/workflows/benchmarks.yml](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/benchmarks.yml)
- Deployment shape is an npm package, not a service: `files` publishes only `lib/`, `bin/`, `hot/`, `schemas/`, `module.d.ts` and `types.d.ts`, releases are cut from `.changeset/` entries via `yarn version:release`, and `release.yml` / `publish-to-pkg-pr-new.yml` handle publishing and PR preview builds. — [package.json#L21-31](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L21-L31) [package.json#L44](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L44) [.github/workflows/release.yml](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/release.yml) [.changeset/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.changeset/)
- `hot/` is a separate shipped surface — browser-side HMR runtime (`dev-server.js`, `poll.js`, `signal.js`, `log-apply-result.js`) plus the three lazy-compilation clients (`lazy-compilation-node.js`, `-web.js`, `-universal.js`) — type-checked by its own `tsconfig.hot.json` rather than the `lib/` project. — [hot/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/hot/) [package.json#L39](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L39) [tsconfig.json#L14-28](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tsconfig.json#L14-L28)

## Feature catalog

There are no routes or screens; the user-facing surface is the config schema, the CLI shim, and the exported plugin classes. `schemas/WebpackOptions.json` defines 36 top-level options and 12 `experiments` flags, each of which maps to plugins wired in `WebpackOptionsApply`/`defaults.js`. Language support is organized as parser+generator+printer triples per module type (javascript, css, html, json, asset, wasm, typescript), most of which webpack implements itself rather than delegating. Beyond bundling proper, the shipped features include Module Federation, HMR and lazy compilation, persistent caching, source maps, code splitting, library output formats, and a stats/reporting layer.
- The config API exposes 36 top-level options — `amd, bail, cache, context, dependencies, devServer, devtool, dotenv, entry, experiments, extends, externals, externalsPresets, externalsType, ignoreWarnings, infrastructureLogging, loader, mode, module, name, node, optimization, output, parallelism, performance, plugins, profile, recordsInputPath, recordsOutputPath, recordsPath, resolve, resolveLoader, snapshot, stats, target, validate, watch, watchOptions` — all validated against one schema. — [schemas/WebpackOptions.json](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/schemas/WebpackOptions.json)
- Twelve experiment flags gate unreleased surfaces: `asyncWebAssembly, backCompat, buildHttp, cacheUnaffected, css, deferImport, futureDefaults, html, lazyCompilation, sourceImport, syncWebAssembly, typescript`. — [schemas/WebpackOptions.json](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/schemas/WebpackOptions.json) [lib/config/defaults.js#L2551-2560](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/defaults.js#L2551-L2560)
- Native CSS and HTML are full pipelines, not loaders: `lib/css/` ships `syntax-parser.js` (14,973 lines), `syntax-printer.js` (4,730) and a generated `data.js` (8,003) of MDN-derived tables; `lib/html/` ships `syntax-parser.js` (13,197, including §13.2 tree construction) and `HtmlGenerator.js`. — [lib/css/syntax-parser.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/css/syntax-parser.js) [lib/css/syntax-printer.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/css/syntax-printer.js) [lib/html/syntax-parser.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/html/syntax-parser.js) [lib/html/HtmlGenerator.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/html/HtmlGenerator.js)
- JavaScript is parsed by webpack's own acorn-derived parser (`lib/javascript/syntax-parser.js`, 17,844 lines) feeding `JavascriptParser.js` (7,604 lines) which raises the hook surface every dependency plugin taps (`evaluateIdentifier`, `call`, `expression`, …). — [lib/javascript/syntax-parser.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/javascript/syntax-parser.js) [lib/javascript/JavascriptParser.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/javascript/JavascriptParser.js) [lib/hmr/HotModuleReplacementPlugin.js#L254-316](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/hmr/HotModuleReplacementPlugin.js#L254-L316)
- Minification defaults to one `minimizer-webpack-plugin` instance sharing a single worker-thread pool across up to three `minify` functions dispatched by extension — `jsMinify` (`[cm]?js`), `cssMinify` (`css`) and `htmlMinify` (`html`) — and steps aside entirely when `getClaimedAssetTypes` finds a user-configured minimizer for that type. — [lib/config/defaults.js#L2521-2627](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/defaults.js#L2521-L2627) [lib/javascript/jsMinify.js#L30-70](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/javascript/jsMinify.js#L30-L70)
- `jsMinify` reaches terser through `webpack.javascript.syntax.printer.load()` rather than `require("terser")`, so phases webpack has reimplemented replace terser's own; it is re-required as `require("webpack")` inside the worker because the plugin ships the function to the pool as source. — [lib/javascript/jsMinify.js#L44-70](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/javascript/jsMinify.js#L44-L70) [lib/javascript/jsMinify.js#L241-252](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/javascript/jsMinify.js#L241-L252)
- Module Federation ships as 18 files in `lib/container/` (`ModuleFederationPlugin`, `ContainerPlugin`, `ContainerReferencePlugin`, `RemoteModule`, `RemoteRuntimeModule`, `HoistContainerReferencesPlugin`) plus 15 in `lib/sharing/` (`SharePlugin`, `ConsumeSharedPlugin`, `ProvideSharedPlugin`, `ShareRuntimeModule`, and a bundled `semver.js`). — [lib/container/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/container/) [lib/sharing/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/sharing/) [lib/sharing/semver.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/sharing/semver.js)
- HMR is `HotModuleReplacementPlugin` (1,000+ lines) taps on `compilation.hooks.record`, `fullHash`, `processAssets` and `additionalTreeRuntimeRequirements`, plus three generated runtime files and a `parserHooks.js` module that exposes the `module.hot` parser surface to other plugins. — [lib/hmr/HotModuleReplacementPlugin.js#L101-320](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/hmr/HotModuleReplacementPlugin.js#L101-L320) [lib/hmr/HotModuleReplacementPlugin.js#L512-980](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/hmr/HotModuleReplacementPlugin.js#L512-L980) [lib/hmr/parserHooks.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/hmr/parserHooks.js)
- Lazy compilation runs a real HTTP(S) server in-process: `lazyCompilationBackend.js` creates a server (protocol from `options.protocol`, `https` when configured), tracks `activeModules` refcounts keyed by module, and destroys sockets on close. — [lib/hmr/lazyCompilationBackend.js#L32-140](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/hmr/lazyCompilationBackend.js#L32-L140) [lib/hmr/LazyCompilationPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/hmr/LazyCompilationPlugin.js)
- `experiments.buildHttp` turns on `HttpUriPlugin`, which fetches `http:`/`https:` modules, honours `options.proxy` or `http_proxy`/`HTTP_PROXY`, enforces an `allowedUris` policy on both the initial URL and every redirect, and records resolutions into a lockfile (`lockfileLocation`) with bodies cached under `<lockfile>.data`. — [lib/schemes/HttpUriPlugin.js#L546-600](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/schemes/HttpUriPlugin.js#L546-L600) [lib/schemes/HttpUriPlugin.js#L1095-1135](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/schemes/HttpUriPlugin.js#L1095-L1135) [lib/schemes/HttpUriPlugin.js#L640-660](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/schemes/HttpUriPlugin.js#L640-L660)
- Output-shaping plugins are grouped in `lib/output/`: `CopyPlugin` (the `output.copy` option), `CleanPlugin` (the `output.clean` option, which unlinks and rmdirs inside `output.path`), `BannerPlugin`, and `ManifestPlugin`. — [lib/output/CleanPlugin.js#L172-330](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/output/CleanPlugin.js#L172-L330) [lib/output/CopyPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/output/CopyPlugin.js) [lib/output/ManifestPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/output/ManifestPlugin.js)
- `DotenvPlugin` is a first-class option (`dotenv` at the top level) that vendors dotenv v17.4.2 parsing and dotenv-expand v13.0.0 expansion, reading `.env`, `.env.local`, `.env.[mode]`, `.env.[mode].local` and feeding the values through `DefinePlugin`. — [lib/define/DotenvPlugin.js#L1-65](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/define/DotenvPlugin.js#L1-L65) [lib/define/DotenvPlugin.js#L120-215](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/define/DotenvPlugin.js#L120-L215)
- Progress reporting is auto-applied from `infrastructureLogging.progress`: `"auto"` enables it only when not `appendOnly` (i.e. a TTY), `true` forces it unless logging level is `none`, and a user-supplied `ProgressPlugin` suppresses the default one; a `MultiCompiler` gets one aggregated reporter rather than one per child. — [lib/webpack.js#L52-95](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/webpack.js#L52-L95) [lib/webpack.js#L110-140](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/webpack.js#L110-L140)
- The CLI binary is a shim, not the CLI: `bin/webpack.js` enables Node's on-disk compile cache, checks whether `webpack-cli` is installed, and `cp.spawn`s the package manager to install it interactively if not. — [bin/webpack.js#L1-60](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/bin/webpack.js#L1-L60)
- Stats are a separate reporting subsystem: `Stats`/`MultiStats` render through `DefaultStatsFactoryPlugin` (2,903 lines), `DefaultStatsPresetPlugin` and `DefaultStatsPrinterPlugin` (1,777 lines), all applied unconditionally by `WebpackOptionsApply`. — [lib/stats/DefaultStatsFactoryPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/stats/DefaultStatsFactoryPlugin.js) [lib/stats/DefaultStatsPrinterPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/stats/DefaultStatsPrinterPlugin.js) [lib/config/WebpackOptionsApply.js#L57-59](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/WebpackOptionsApply.js#L57-L59)

## Data flow

Input boundaries are the config object (validated against JSON Schema), the input filesystem behind `InputFileSystem`, and optionally the network (`HttpUriPlugin`) or a data/virtual URL scheme. A request becomes a module through `NormalModuleFactory.create` (resolve → rules match → factory hooks), is read and transformed by the loader chain in `LoaderRunner`, then parsed into `Dependency` objects that populate the `ModuleGraph`. At seal time the `ModuleGraph` is frozen and `buildChunkGraph` fills a `ChunkGraph`; code generation renders each module through its `Generator` and `DependencyTemplate`s into `Source` objects, which are hashed, assembled into assets, passed through the `processAssets` stage pipeline, and finally written by `Compiler.emitAssets` through `OutputFileSystem`. Everything durable — module graph, code generation results, resolver results — round-trips through `lib/serialization/` into the filesystem cache pack.
- Input normalization is two distinct layers: `normalization.js` canonicalizes the user's config shape (shorthand → full form, 828 lines) and `defaults.js` fills mode/target-dependent values (2,889 lines); `applyWebpackOptionsBaseDefaults` runs before plugins and `applyWebpackOptionsDefaults` after. — [lib/config/normalization.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/normalization.js) [lib/config/defaults.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/defaults.js) [lib/webpack.js#L143-175](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/webpack.js#L143-L175)
- `NormalModuleFactory.create` builds a single mutable `ResolveData` object carrying `contextInfo`, `request`, `phase`, import `attributes`, `createData`, and three `LazySet`s of file/missing/context dependencies, then drives it through `beforeResolve` → `factorize` → `resolve` → `afterResolve` → `createModule` → `module`. — [lib/module/NormalModuleFactory.js#L1269-1345](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModuleFactory.js#L1269-L1345) [lib/module/NormalModuleFactory.js#L628-676](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModuleFactory.js#L628-L676)
- Loader selection is a compiled rule set: `NormalModuleFactory`'s constructor compiles `options.defaultRules` and `options.rules` together via `ruleSetCompiler.compile`, so `module.rules` matching is a precompiled matcher rather than a per-request scan. — [lib/module/NormalModuleFactory.js#L679-690](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModuleFactory.js#L679-L690) [lib/rules/RuleSetCompiler.js#L141-200](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/rules/RuleSetCompiler.js#L141-L200)
- Source text enters through `NormalModule._doBuild`, which calls the vendored `runLoaders` with a `processResource` callback; the loader chain is executed as a pitching pass then a normal pass (`iteratePitchingLoaders` → `processResource` → unwind), and each loader module is loaded by `loadLoader`. — [lib/module/NormalModule.js#L1339-1470](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModule.js#L1339-L1470) [lib/loaders/LoaderRunner.js#L503-580](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/loaders/LoaderRunner.js#L503-L580) [lib/loaders/LoaderRunner.js#L812-870](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/loaders/LoaderRunner.js#L812-L870)
- The loader chain can bypass parsing entirely: a loader may return `webpackAST` in its `additionalData`, which `LoaderRunner` types as `AdditionalDataKnown.webpackAST` — "the AST webpack should parse instead of the source". — [lib/loaders/LoaderRunner.js#L49-56](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/loaders/LoaderRunner.js#L49-L56)
- Cacheability is data that flows back out of the loader chain: `RunLoaderResult` carries `cacheable`, `notCacheableReasons`, and the `fileDependencies`/`contextDependencies`/`missingDependencies` lists that `FileSystemInfo` later snapshots to decide staleness. — [lib/loaders/LoaderRunner.js#L36-46](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/loaders/LoaderRunner.js#L36-L46) [lib/loaders/LoaderRunner.js#L710-715](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/loaders/LoaderRunner.js#L710-L715) [lib/fs/FileSystemInfo.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/fs/FileSystemInfo.js)
- State ownership is explicit: the `Compilation` owns `modules`, `entries`, `chunks`, the `ModuleGraph` and `ChunkGraph`, and five work queues (`factorizeQueue`, `buildQueue`, `rebuildQueue`, `processDependenciesQueue`, `addModuleQueue`) which `seal`'s `finalCallback` clears on completion. — [lib/Compilation.js#L3612-3632](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L3612-L3632) [lib/graph/ModuleGraph.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/graph/ModuleGraph.js) [lib/graph/ChunkGraph.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/graph/ChunkGraph.js)
- `seal` freezes the module graph with `this.moduleGraph.freeze("seal")` before building chunks, then materializes one `Chunk` + `Entrypoint` per entry and seeds `chunkGraphInit` with each entry's resolved modules, validating `dependOn`/`runtime` conflicts and circular `dependOn` into `compilation.errors`. — [lib/Compilation.js#L3648-3700](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L3648-L3700) [lib/Compilation.js#L3717-3790](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L3717-L3790)
- After chunking, the ordered tail of `seal` is `processRuntimeRequirements` → `_attachPendingRuntimeModules` → `createHash` (which returns the code-generation jobs) → `_runCodeGenerationJobs` → `createModuleAssets` → `createChunkAssets` → `processAssets` → freeze `this.assets` → `summarizeDependencies` → `afterSeal`. — [lib/Compilation.js#L3900-3999](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L3900-L3999) [lib/Compilation.js#L5174-5200](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L5174-L5200) [lib/Compilation.js#L5883-5900](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L5883-L5900)
- `this.assets` is frozen at the end of `processAssets` — literally `Object.freeze` when `backCompat` is off, and a `soonFrozenObjectDeprecation` proxy carrying `DEP_WEBPACK_COMPILATION_ASSETS` when it is on — so post-seal asset mutation is a hard boundary. — [lib/Compilation.js#L3945-3965](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L3945-L3965)
- The output boundary is `Compiler.emitAssets`, which writes at most 15 assets concurrently, recomputes `immutable` when a query/hash suffix is stripped, special-cases Windows drive-absolute target paths via `WINDOWS_ABS_PATH_REGEXP`, and refuses to write two assets whose paths differ only by casing or query string. — [lib/Compiler.js#L786-900](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compiler.js#L786-L900)
- Emitted-asset writes are deduplicated through two caches on the compiler — `_assetEmittingWrittenFiles` (path → generation) and `_assetEmittingSourceCache` (Source → `{ sizeOnlySource, writtenTo }`) — so an unchanged `Source` in a watch rebuild is compared rather than rewritten. — [lib/Compiler.js#L848-900](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compiler.js#L848-L900)
- Records are a separate persisted side-channel from the cache: `Compiler.readRecords` runs before `compile` and `emitRecords` after `emitAssets`, and `RecordIdsPlugin` uses them to keep module/chunk ids stable across builds via `recordsPath`. — [lib/Compiler.js#L1302-1400](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compiler.js#L1302-L1400) [lib/Compiler.js#L688-700](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compiler.js#L688-L700) [lib/ids/RecordIdsPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/ids/RecordIdsPlugin.js)
- Build dependencies close the loop into the cache: after `done`, `run`'s final step is `this.cache.storeBuildDependencies(compilation.buildDependencies, compilation.optionalBuildDependencies, …)`, which `PackFileCacheStrategy` snapshots into the `PackContainer` alongside a version string. — [lib/Compiler.js#L672-686](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compiler.js#L672-L686) [lib/cache/PackFileCacheStrategy.js#L57-110](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/PackFileCacheStrategy.js#L57-L110) [lib/cache/IdleFileCachePlugin.js#L99-110](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/IdleFileCachePlugin.js#L99-L110)

## Behavior traces

Four flows are worth walking: a one-shot `webpack(config, cb)` run, a single module's resolve→build→parse path, a watch rebuild, and the persistent-cache store/restore path. Each is a callback chain rather than promises, with `tapable` hooks at every joint and `logger.time`/`timeEnd` pairs marking the phases you see in `stats` profiling output. The names below are the actual functions in call order.
- Startup (one-shot): `webpack(options, cb)` → `create()` → schema check → `createCompiler` → `compiler.run(cb)`; `run` calls `cache.endIdle` if idle, then `hooks.beforeRun` → `hooks.run` → `readRecords` → `compile(onCompiled)`. — [lib/webpack.js#L255-330](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/webpack.js#L255-L330) [lib/Compiler.js#L578-710](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compiler.js#L578-L710)
- `Compiler.compile` runs `newCompilationParams()` → `hooks.beforeCompile` → `hooks.compile` → `newCompilation(params)` → `hooks.make` → `hooks.finishMake` → `process.nextTick` → `compilation.finish` → `compilation.seal` → `hooks.afterCompile`, timing each phase with the `webpack.Compiler` logger. — [lib/Compiler.js#L1602-1648](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compiler.js#L1602-L1648)
- Completion: `onCompiled` checks `hooks.shouldEmit`; if emitting it defers a `process.nextTick`, calls `emitAssets`, re-enters `compile` when `needAdditionalPass` is true, then `emitRecords` → build `new Stats(compilation)` → `hooks.done` → `cache.storeBuildDependencies` → `finalCallback`, which sets `cache.beginIdle()` and fires `hooks.afterDone`. — [lib/Compiler.js#L596-700](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compiler.js#L596-L700)
- The callback form of `webpack()` always closes the compiler for you — `compiler.run((err, stats) => compiler.close((err2) => callback(err || err2, stats)))` — which is why plugin authors using the no-callback form must call `close` themselves. — [lib/webpack.js#L300-320](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/webpack.js#L300-L320)
- Module creation: `Compilation.handleModuleCreation` → `factorizeQueue` → `NormalModuleFactory.create` → `hooks.beforeResolve` (a `false` result short-circuits to an ignored module) → `hooks.factorize`/`resolveResource` → `hooks.afterResolve` → `hooks.createModule`/`createModuleClass` → `hooks.module` waterfall. — [lib/Compilation.js#L2441-2500](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L2441-L2500) [lib/module/NormalModuleFactory.js#L1269-1340](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModuleFactory.js#L1269-L1340) [lib/module/NormalModuleFactory.js#L1381-1460](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModuleFactory.js#L1381-L1460)
- Module build: `NormalModule.build` → `_doBuild` → `runLoaders` (pitch pass, `processResource`, normal pass) → on success the parser runs and dependencies are collected; every failure path funnels through `markModuleAsErrored(err)` followed by `_initBuildHash(compilation)` so an errored module still has a hash. — [lib/module/NormalModule.js#L1676-1760](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModule.js#L1676-L1760) [lib/module/NormalModule.js#L1339-1470](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModule.js#L1339-L1470) [lib/module/NormalModule.js#L1596-1675](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModule.js#L1596-L1675)
- Parser and generator instances are resolved per module type through `NormalModuleFactory.getParser`/`createParser` and `getGenerator`/`createGenerator`, cached in `parserCache`/`generatorCache` as `Map<type, WeakMap<options, instance>>`, and a type's one-time async setup runs through the `prepareModuleType` HookMap with in-flight callers queued. — [lib/module/NormalModuleFactory.js#L1946-2040](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModuleFactory.js#L1946-L2040) [lib/module/NormalModuleFactory.js#L690-702](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModuleFactory.js#L690-L702) [lib/module/NormalModuleFactory.js#L656-657](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModuleFactory.js#L656-L657)
- Seal, in order: clear queues in `finalCallback`, optionally re-attach `ChunkGraph` per module for `backCompat`, `hooks.seal`, loop `hooks.optimizeDependencies` until it returns falsy, `hooks.beforeChunks`, `moduleGraph.freeze("seal")`, entry→chunk creation with `assignDepths`, then the optimize/hash/codegen/assets tail. — [lib/Compilation.js#L3612-3700](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L3612-L3700) [lib/Compilation.js#L3900-3999](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L3900-L3999)
- Watch rebuild: `Watching._invalidate` → `_go(fileTimeInfoEntries, contextTimeInfoEntries, changedFiles, removedFiles)` → `compiler.compile` → `_done(err, compilation)`, which re-enters `_go` when `this.invalid` was set during the run; `_done` is pre-bound in the constructor and the first pass is kicked off by `_invalidate` when `_initial`. — [lib/watch/Watching.js#L26-130](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/watch/Watching.js#L26-L130) [lib/watch/Watching.js#L273-320](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/watch/Watching.js#L273-L320) [lib/watch/Watching.js#L450-470](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/watch/Watching.js#L450-L470)
- Persistence: `IdleFileCachePlugin` taps `cache.hooks.store` to queue `strategy.store(identifier, etag, data)` and `cache.hooks.get` to `strategy.restore(...)`, then flushes on an idle timer whose delay is the smaller of `idleTimeout` and `idleTimeoutForInitialStore`, with `idleTimeoutAfterLargeChanges` as a third setting. — [lib/cache/IdleFileCachePlugin.js#L21-120](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/IdleFileCachePlugin.js#L21-L120)
- A cache pack is read lazily by location: `Pack.get(identifier, etag)` looks the identifier up in `itemInfo`, finds its `location`, and only then materializes that `PackContent`'s map — so restoring one module does not deserialize the whole pack. — [lib/cache/PackFileCacheStrategy.js#L203-235](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/PackFileCacheStrategy.js#L203-L235) [lib/cache/PackFileCacheStrategy.js#L903-990](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/PackFileCacheStrategy.js#L903-L990)
- Shutdown: `Compiler.close` first closes any active `watching`, fires `hooks.shutdown`, then defers `_releaseUnusedCompilationData(lastCompilation)` behind `Promise.resolve().then(...)` so a `close()` made inside the run callback cannot release `codeGenerationResults` before `afterDone` fires on the same stack, and finally calls `cache.shutdown(callback)`. — [lib/Compiler.js#L1654-1690](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compiler.js#L1654-L1690)
- Minification is a late `processAssets` stage, not part of codegen: `optimization.minimizer` defaults to a plugin wrapper that lazily requires `minimizer-webpack-plugin` at apply time and only constructs it once it knows which of JS/CSS/HTML are actually enabled (returning early if none are). — [lib/config/defaults.js#L2521-2575](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/defaults.js#L2521-L2575)
- Test execution mirrors the real runtime rather than mocking it: `test/templates/ConfigTestCases.js` runs a real `webpack()` build, resolves the bundle via a per-case `findBundle`, and executes it in a `vm` context — `test/harness/runner/asModule.js` even builds `vm.SyntheticModule`/`SourceTextModule` wrappers so ESM output is linked and evaluated for real. — [test/templates/ConfigTestCases.js#L94-260](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/templates/ConfigTestCases.js#L94-L260) [test/templates/ConfigTestCases.js#L600-640](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/templates/ConfigTestCases.js#L600-L640) [test/harness/runner/asModule.js#L1-60](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/harness/runner/asModule.js#L1-L60)

## Testing signals

Jest is the only runner and it must be invoked through the `test:base` wrapper, which supplies `--experimental-vm-modules` (bare jest crashes the ESM and test262 suites). Coverage is broad and integration-first: ~2,257 leaf `configCases/` directories, 148 `*.unittest.js` files, 144 `statsCases/`, 37 `hotCases/`, 23 `watchCases/`, 44 `benchmarkCases/`, plus four upstream spec corpora pulled in as git submodules. The same `configCases/` corpus is re-run under ~20 option variants declared in one file, and again under `cache.type: "filesystem"` by `ConfigCacheTestCases`, which is what makes cache correctness a tested property rather than a hope. The main gaps are environment-dependent: browser tiers, Deno/Bun runtimes and spec corpora only run where their prerequisites exist, and three `lib/*Minify.js` files are explicitly excluded from coverage instrumentation.
- `jest.config.js` matches five patterns (`test/*.test.js`, `*.basictest.js`, `*.longtest.js`, `unitCases/*.unittest.js`, `specCases/*.spectest.js`), uses a custom `testEnvironment` (`test/harness/patch-node-env.js`), a custom `snapshotResolver`, a 30s default timeout and `forceExit`. — [jest.config.js#L1-66](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/jest.config.js#L1-L66)
- Three `minify` entry points are deliberately excluded from coverage — `lib/css/cssMinify.js`, `lib/html/htmlMinify.js`, `lib/javascript/jsMinify.js` — because they are serialized to the minimizer worker pool as source and instrumentation counters would not resolve there. — [jest.config.js#L46-52](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/jest.config.js#L46-L52)
- The suite is built from thin shims over four drivers: `test/*.test.js`/`*.basictest.js`/`*.longtest.js` each call `describeCases(config)` from `test/templates/{TestCases,ConfigTestCases,HotTestCases,WatchTestCases}.js` — e.g. `ConfigTestCases.basictest.js` is five lines. — [test/ConfigTestCases.basictest.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/ConfigTestCases.basictest.js) [test/templates/ConfigTestCases.js#L94-100](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/templates/ConfigTestCases.js#L94-L100) [test/templates/TestCases.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/templates/TestCases.js)
- The option-set matrix lives in one file, `test/templates/variants.js`: `normal`, `development`, `production`, `production-global-used`, `module` (target node14 + `module: true`), `hot`, nine devtool variants, `minimized-source-map`, and `cache-pack` — one `test/` shim per entry because jest parallelizes per file. — [test/templates/variants.js#L1-111](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/templates/variants.js#L1-L111)
- Scale: 2,257 leaf directories under `test/configCases/` across 107 categories, 148 files in `test/unitCases/`, 144 `statsCases/`, 37 `hotCases/`, 23 `watchCases/`, 44 `benchmarkCases/`, 4 `typesCases/` and 1 `memoryLimitCases/`. — [test/configCases/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/configCases/) [test/unitCases/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/unitCases/) [test/statsCases/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/statsCases/) [test/benchmarkCases/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/benchmarkCases/)
- Cache correctness is enforced by re-running the whole config corpus: `ConfigCacheTestCases.basictest.js` runs every `configCases/` case with `cache.type: "filesystem"` and fails on `Pack got invalid because of write to: <identifier>`, so a class missing `makeSerializable` surfaces as a test failure rather than as a user's slow rebuild. — [test/ConfigCacheTestCases.basictest.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/ConfigCacheTestCases.basictest.js) [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [lib/util/makeSerializable.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/util/makeSerializable.js)
- Spec conformance is imported wholesale via four git submodules under `test/external/` — test262, html5lib-tests, WPT (~161k files, shallow-cloned) and css-parsing-tests — each with its own `specCases/*.spectest.js` runner and its own CI job. — [TESTING_DOCS.md#L36-58](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/TESTING_DOCS.md#L36-L58) [test/specCases/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/specCases/) [.github/workflows/test.yml#L305-370](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/test.yml#L305-L370)
- acorn's own corpus is vendored rather than submoduled (`test/fixtures/acorn-corpus.json`) because acorn's npm tarball ships no tests; `test/unitCases/WebpackParser.unittest.js` both replays it against webpack's parser and regenerates it under `WEBPACK_UPDATE_ACORN_CORPUS=1`. — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [test/unitCases/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/unitCases/)
- Non-Node runtimes are covered by dedicated wrappers: `test:base:deno` runs jest under Deno with an explicit permission list and an import map, `test:base:bun` runs it under `bun --bun --smol --workerThreads` with a preload file, both routed through `test/harness/runtimeCrashReporter.js`. — [package.json#L86-95](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L86-L95) [test/harness/runtimes/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/harness/runtimes/) [test/harness/runtimeCrashReporter.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/harness/runtimeCrashReporter.js)
- Assertion helpers are centralized in `test/helpers/` (40+ files): `checkArrayExpectation` for per-case `errors.js`/`warnings.js`, `deprecationTracking`/`expectNoDeprecations`, `FakeDocument`/`createFakeWorker`/`createFakeWorklet` for browser-API simulation, and `launchBrowser` for the syntax-equivalence tiers. — [test/helpers/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/helpers/) [test/templates/ConfigTestCases.js#L37](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/templates/ConfigTestCases.js#L37) [test/helpers/checkArrayExpectation.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/helpers/checkArrayExpectation.js)
- CI gates the expensive suites on three cheap ones — `integration`, `test262`, `parser`, `syntax-equivalence`, the Deno/Bun matrix and the benchmark shards all declare `needs: [lint, basic, unit]`, so a red lint leaves most of the run reporting `skipped` rather than a result. — [.github/workflows/test.yml#L247-260](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/test.yml#L247-L260) [.github/workflows/test.yml#L305-312](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/test.yml#L305-L312) [.github/workflows/test.yml#L665-672](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/test.yml#L665-L672)
- The integration matrix retries once before failing (`yarn cover:integration:${part} --ci … || yarn cover:integration:${part} --ci … -f`) and only uploads coverage on the non-legacy Node versions, so Node 10/12/14/16/18/20 legs run `test:integration` without instrumentation. — [.github/workflows/test.yml#L636-660](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/test.yml#L636-L660)
- Beyond correctness, CI runs measuring suites as separate workflows: `benchmarks.yml` (sharded CodSpeed runs over `test/benchmarkCases/`), `code-size.yml` (`test:size` diffed against main's uploaded report), `deep-imports.yml` (`find-deep-imports:check` against the recorded `tooling/deep-webpack-imports.json`) and `examples.yml`. — [.github/workflows/benchmarks.yml](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/benchmarks.yml) [.github/workflows/code-size.yml](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/code-size.yml) [.github/workflows/deep-imports.yml](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/deep-imports.yml) [tooling/deep-webpack-imports.json](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tooling/deep-webpack-imports.json)
- Printer invariants are a standalone gate: the `invariants` job runs `yarn test:invariants` (`compare-css-tools.js --invariants` and `compare-html-tools.js --invariants`) on every PR, and also installs all three comparison corpora so a Dependabot bump that breaks an install reds on its own PR. — [.github/workflows/test.yml#L107-172](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/test.yml#L107-L172) [package.json#L59-64](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L59-L64) [tooling/compare-css-tools.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tooling/compare-css-tools.js)
- Type checking is tested as five separate `tsc` projects (`tsconfig.json` for `lib`, plus `tsconfig.types.test.json`, `tsconfig.types.benchmark.json`, `tsconfig.hot.json`, `tsconfig.module.test.json`) with `strict: true` and `checkJs: true` over JSDoc, and `tsconfig.validation.json` re-checks against an older TypeScript in CI. — [tsconfig.json](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tsconfig.json) [package.json#L36-43](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L36-L43) [.github/workflows/test.yml#L55-60](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/test.yml#L55-L60)

## Risk map

webpack's real security surface is that it executes arbitrary third-party code by design — loaders, plugins and config files all run with full Node privileges in the developer's process — so the interesting risks are the paths that widen that, not the bundler core. Concretely: `HttpUriPlugin` fetches remote code over the network, `lazyCompilationBackend` opens a listening HTTP server, `CleanPlugin` deletes files, `DotenvPlugin` reads secrets into the bundle, and the cache deserializer reconstructs arbitrary registered classes from disk. Operationally, the largest fragility is size and coupling: `Compilation.js` is 6,724 lines with a seal pipeline whose phase ordering is load-bearing, the three `syntax-parser.js` files total ~46k lines with V8-inlining-budget constraints baked into specific methods, and several correctness properties (cache round-trip, generated-file freshness, deep-import compatibility) are enforced only by conventions that a careless edit silently violates.
- Arbitrary code execution is the design, not a bug: `loadLoader` requires loader modules from the resolved graph and `runLoaders` invokes them with a full loader context, so any dependency reachable as a loader runs with the developer's privileges — there is no sandbox in this path. — [lib/loaders/loadLoader.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/loaders/loadLoader.js) [lib/loaders/LoaderRunner.js#L528-580](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/loaders/LoaderRunner.js#L528-L580) [lib/loaders/LoaderRunner.js#L812-870](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/loaders/LoaderRunner.js#L812-L870)
- `HttpUriPlugin` is the highest-value security path: it fetches module source over `http:`/`https:`, routes through `options.proxy` or the `http_proxy`/`HTTP_PROXY` environment variables, and its `allowedUris` policy must be re-checked after every redirect — the redirect check at L773-790 is the control that stops an allowed host from redirecting to an arbitrary one. — [lib/schemes/HttpUriPlugin.js#L546-560](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/schemes/HttpUriPlugin.js#L546-L560) [lib/schemes/HttpUriPlugin.js#L773-790](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/schemes/HttpUriPlugin.js#L773-L790) [lib/schemes/HttpUriPlugin.js#L1095-1135](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/schemes/HttpUriPlugin.js#L1095-L1135)
- Fetched remote bodies are written to disk under `cacheLocation` (defaulting to `<lockfileLocation>.data`) keyed by a computed `key`, so a poisoned or unvalidated response becomes a persisted build input. — [lib/schemes/HttpUriPlugin.js#L573-600](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/schemes/HttpUriPlugin.js#L573-L600) [lib/schemes/HttpUriPlugin.js#L737-745](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/schemes/HttpUriPlugin.js#L737-L745) [lib/schemes/HttpUriPlugin.js#L1244-1270](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/schemes/HttpUriPlugin.js#L1244-L1270)
- `lazyCompilationBackend` binds a real HTTP/HTTPS listener in the dev process and keeps a `Set` of live sockets; its request handler mutates an `activeModules` refcount from client-supplied keys, so exposure of that port exposes compile triggers. — [lib/hmr/lazyCompilationBackend.js#L32-140](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/hmr/lazyCompilationBackend.js#L32-L140)
- `CleanPlugin` performs `unlink` and `rmdir` under `output.path` based on a filesystem diff, with `keep` (a `SyncBailHook` plus user matcher) and `dry` as the only brakes — a misconfigured `output.path` makes this a destructive operation on a directory the user did not intend. — [lib/output/CleanPlugin.js#L92-120](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/output/CleanPlugin.js#L92-L120) [lib/output/CleanPlugin.js#L180-340](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/output/CleanPlugin.js#L180-L340) [lib/output/CleanPlugin.js#L376-382](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/output/CleanPlugin.js#L376-L382)
- `DotenvPlugin` reads `.env`, `.env.local`, `.env.[mode]` and `.env.[mode].local` and feeds the parsed, `dotenv-expand`-expanded values into `DefinePlugin` — i.e. straight into the emitted bundle — so a missing `prefix` filter ships secrets to the browser. — [lib/define/DotenvPlugin.js#L55-65](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/define/DotenvPlugin.js#L55-L65) [lib/define/DotenvPlugin.js#L199-215](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/define/DotenvPlugin.js#L199-L215) [lib/define/EnvironmentPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/define/EnvironmentPlugin.js)
- The cache deserializer instantiates classes named by strings read off disk: `registerLoader(/^webpack\/lib\//, …)` maps a request to a loader in the generated `internalSerializables`, and an unknown request only produces a `console.warn` — a cache file is therefore a trusted input. — [lib/util/serialization.js#L25-50](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/util/serialization.js#L25-L50) [lib/serialization/ObjectMiddleware.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/serialization/ObjectMiddleware.js) [lib/cache/PackFileCacheStrategy.js#L1162-1180](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/PackFileCacheStrategy.js#L1162-L1180)
- Deserialization re-enters constructors with no arguments, so a constructor that dereferences a parameter throws during restore and manifests only as a `Restoring failed for <identifier> from pack` debug line — a silent-by-default failure mode documented as a recurring trap. — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [lib/util/makeSerializable.js#L45-58](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/util/makeSerializable.js#L45-L58)
- Blast-radius hotspot #1 is `lib/Compilation.js` (6,724 lines): a single class owning the module graph, chunk graph, five async queues, the whole seal pipeline, asset emission and the hook surface — any reordering inside `seal` changes what plugins observe. — [lib/Compilation.js#L3612-3999](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L3612-L3999) [lib/Compilation.js#L4484-4530](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L4484-L4530) [lib/Compilation.js#L5174-5200](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L5174-L5200)
- Blast-radius hotspot #2 is the parser trio: `javascript/syntax-parser.js` (17,844 lines), `css/syntax-parser.js` (14,973) and `html/syntax-parser.js` (13,197), whose hot methods (`readWord`, `readString`, `finishToken`) are sized to fit V8's ~460-byte inlining budget — adding one argument to `readWord` once cost 1.4%. — [lib/javascript/syntax-parser.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/javascript/syntax-parser.js) [lib/css/syntax-parser.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/css/syntax-parser.js) [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md)
- Fragile coupling: five `lib/*/data.js` modules and every `types.d.ts`/`declarations/*.d.ts`/`schemas/**/*.check.js` file are generated, and the `lint` job fails with only `… need to be updated` — a message that reads like a code problem even when the cause is a reworded JSDoc paragraph. — [lib/css/data.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/css/data.js) [lib/html/data.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/html/data.js) [lib/javascript/data.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/javascript/data.js) [package.json#L40-41](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L40-L41) [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md)
- Moving any file in `lib/` risks breaking the published ecosystem's deep imports; the only record of which paths are load-bearing is `tooling/deep-webpack-imports.json` (with `perKeyword`, `requests` and a `removed` map), checked by a CI job and mirrored by `test/unitCases/deepPathShims.unittest.js`. — [tooling/deep-webpack-imports.json](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tooling/deep-webpack-imports.json) [tooling/find-deep-webpack-imports.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tooling/find-deep-webpack-imports.js) [.github/workflows/deep-imports.yml](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/deep-imports.yml)
- Legacy surface is explicit but live: `lib/wasm-sync/` is kept only until the next major, and `lib/index.js` re-exports `SingleEntryPlugin`, `LibraryTemplatePlugin`, `WebpackOptionsDefaulter` and `JavascriptModulesPlugin` behind `util.deprecate` with `DEP_WEBPACK_*` codes; root-level shims like `lib/NormalModule.js` (10 lines) exist purely as re-exports. — [lib/index.js#L315-330](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/index.js#L315-L330) [lib/index.js#L380-420](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/index.js#L380-L420) [lib/NormalModule.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/NormalModule.js) [lib/wasm-sync/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/wasm-sync/)
- The Node 10.13 baseline is an under-enforced trap: `lib/` and `hot/` ship as raw source with no build step, and the baseline binds test fixtures too — a `configCases/` fixture using newer syntax passes locally and fails the Node 10 CI job with a bare `SyntaxError` inside the emitted bundle. — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [.github/workflows/test.yml#L579-645](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.github/workflows/test.yml#L579-L645) [tsconfig.json#L2-4](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tsconfig.json#L2-L4)
- Instance-shape discipline is a correctness-adjacent perf invariant enforced only by review: every field must be initialized in the constructor and never `delete`d, because a late first assignment forces a V8 hidden-class transition and deopts the inline caches on hot property reads. — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [lib/graph/Dependency.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/graph/Dependency.js)

## Extension points

Extension is the architecture: everything user-visible is a plugin that taps `tapable` hooks, and webpack's own features are implemented the same way user plugins are. The four main seams are (1) the hook objects on `Compiler`, `Compilation` and `NormalModuleFactory`, including `HookMap`s keyed by module type; (2) the `Dependency` + `DependencyTemplate` + `makeSerializable` triple for new dependency kinds; (3) `RuntimeGlobals` + `RuntimeModule` + `runtimeRequirementInTree` for new runtime code; and (4) the plugin-based `RuleSetCompiler` for new `module.rules` vocabulary. Generators of tables and types are themselves extension points — a new CSS math function or HTML attribute is a descriptor added to a `tooling/generate-*-data.js` SUPPLEMENT, not code added to a printer.
- `NormalModuleFactory.hooks` is the richest factory seam: `resolve`, `factorize`, `beforeResolve`, `afterResolve`, `createModule`, `module`, plus five `HookMap`s keyed by scheme or module type (`resolveForScheme`, `resolveInScheme`, `prepareModuleType`, `createParser`/`parser`, `createGenerator`/`generator`, `createModuleClass`), several annotated with `@since` versions. — [lib/module/NormalModuleFactory.js#L628-676](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModuleFactory.js#L628-L676)
- New URL schemes plug in through `resolveForScheme`/`resolveInScheme` — the pattern used by `DataUriPlugin`, `FileUriPlugin`, `HttpUriPlugin` and `VirtualUrlPlugin` in `lib/schemes/`. — [lib/schemes/DataUriPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/schemes/DataUriPlugin.js) [lib/schemes/VirtualUrlPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/schemes/VirtualUrlPlugin.js) [lib/module/NormalModuleFactory.js#L632-642](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModuleFactory.js#L632-L642)
- A new module language is a parser/generator pair registered on the type-keyed HookMaps and cached per options object in `parserCache`/`generatorCache`; `lib/module/Parser.js` and `lib/module/Generator.js` are the base classes, both re-exported publicly as `webpack.Parser` / `webpack.Generator`. — [lib/module/NormalModuleFactory.js#L1946-2040](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModuleFactory.js#L1946-L2040) [lib/module/Parser.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/Parser.js) [lib/module/Generator.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/Generator.js) [lib/index.js#L360-365](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/index.js#L360-L365)
- A new dependency type is the documented triple: a `Dependency` subclass in `lib/dependencies/`, a `DependencyTemplate` registered into `compilation.dependencyTemplates`, and a `makeSerializable(Class, "webpack/lib/…")` call — the pattern appears in 144 files. — [lib/graph/Dependency.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/graph/Dependency.js) [lib/DependencyTemplate.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/DependencyTemplate.js) [lib/util/makeSerializable.js#L60-70](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/util/makeSerializable.js#L60-L70) [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md)
- New runtime code plugs in via `lib/runtime/RuntimeGlobals.js` (97 exported symbols), a `RuntimeModule` subclass, and a tap on `runtimeRequirementInTree`/`additionalTreeRuntimeRequirements`; `RuntimePlugin` is what injects modules for the requirements a build collects. — [lib/runtime/RuntimeGlobals.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/runtime/RuntimeGlobals.js) [lib/runtime/RuntimeModule.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/runtime/RuntimeModule.js) [lib/runtime/RuntimePlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/runtime/RuntimePlugin.js) [lib/Compilation.js#L4484-4530](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L4484-L4530)
- `module.rules` vocabulary is itself pluggable: `RuleSetCompiler` is constructed from rule plugins (`BasicMatcherRulePlugin`, `ObjectMatcherRulePlugin`, `GlobMatcherRulePlugin`, `BasicEffectRulePlugin`, `UseEffectRulePlugin`), so a new condition or effect keyword is a new plugin rather than a branch in the matcher. — [lib/rules/RuleSetCompiler.js#L141-200](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/rules/RuleSetCompiler.js#L141-L200) [lib/rules/GlobMatcherRulePlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/rules/GlobMatcherRulePlugin.js) [lib/rules/UseEffectRulePlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/rules/UseEffectRulePlugin.js)
- Target support is a table-driven registry: adding a target means adding a `[name, description, regExp, handler]` tuple to `TARGETS` in `lib/config/target.js`, and `mergeTargetProperties` plus `UNSUPPORTED_WHEN_ABSENT` decide how multi-target arrays like `["web", "node"]` resolve each capability. — [lib/config/target.js#L128-545](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/target.js#L128-L545) [lib/config/target.js#L562-612](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/target.js#L562-L612)
- Chunk-loading, wasm-loading and library formats are enabled through dedicated dispatch plugins — `EnableChunkLoadingPlugin`, `EnableWasmLoadingPlugin`, `EnableLibraryPlugin` — which `WebpackOptionsApply` calls per configured type, so a new format registers itself with one of these rather than editing the option branch. — [lib/config/WebpackOptionsApply.js#L355-385](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/WebpackOptionsApply.js#L355-L385) [lib/javascript/EnableChunkLoadingPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/javascript/EnableChunkLoadingPlugin.js) [lib/library/EnableLibraryPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/library/EnableLibraryPlugin.js)
- Stats output is extensible through the factory/preset/printer split: `DefaultStatsFactoryPlugin` builds the object graph, `DefaultStatsPresetPlugin` defines named presets, and `DefaultStatsPrinterPlugin` renders it — each a separate tap point. — [lib/stats/DefaultStatsFactoryPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/stats/DefaultStatsFactoryPlugin.js) [lib/stats/DefaultStatsPresetPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/stats/DefaultStatsPresetPlugin.js) [lib/stats/DefaultStatsPrinterPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/stats/DefaultStatsPrinterPlugin.js)
- Module Federation exposes its own hook surface in `lib/container/moduleFederationHooks.js` and `lib/hmr/parserHooks.js` does the same for HMR, so third-party federation/HMR tooling taps a published hook object rather than reaching into the plugin. — [lib/container/moduleFederationHooks.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/container/moduleFederationHooks.js) [lib/hmr/parserHooks.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/hmr/parserHooks.js)
- Caches are pluggable at the `Cache`/`CacheFacade` layer: `MemoryCachePlugin`, `MemoryWithGcCachePlugin`, `IdleFileCachePlugin` and `ResolverCachePlugin` all tap `compiler.cache.hooks.{get,store,storeBuildDependencies}`, so an alternative backing store is a plugin. — [lib/cache/Cache.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/Cache.js) [lib/cache/CacheFacade.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/CacheFacade.js) [lib/cache/IdleFileCachePlugin.js#L60-110](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/IdleFileCachePlugin.js#L60-L110) [lib/cache/ResolverCachePlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/cache/ResolverCachePlugin.js)
- Per-construct minifier behaviour is table-driven by design: the generators own the descriptors (`MATH_FUNCTION_FOLD` in `tooling/generate-css-data.js`, `SUPPLEMENT`/`PARSER_TABLES` in `generate-html-data.js`) and the printers implement only the shared engine keyed by them, so adding a foldable function is adding a generator entry. — [tooling/generate-css-data.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tooling/generate-css-data.js) [tooling/generate-html-data.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tooling/generate-html-data.js) [lib/css/data.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/css/data.js) [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md)
- Custom filesystems plug in behind the `InputFileSystem`/`OutputFileSystem` interfaces declared in `lib/fs/fs.js`; `NodeEnvironmentPlugin` supplies the Node-backed defaults and a target may supply its own watcher (`NodeWatchFileSystem`). — [lib/fs/fs.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/fs/fs.js) [lib/node/NodeEnvironmentPlugin.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/node/NodeEnvironmentPlugin.js) [lib/node/NodeWatchFileSystem.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/node/NodeWatchFileSystem.js)
- Schema-driven options are their own extension point: adding a property to `schemas/WebpackOptions.json` regenerates the CLI flag list (`webpack.cli.getArguments`), the precompiled validator, the declaration and the `types.d.ts` member in one `yarn fix:special` run. — [schemas/WebpackOptions.json](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/schemas/WebpackOptions.json) [lib/config/cli.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/cli.js) [tooling/generate-types.js#L1-40](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tooling/generate-types.js#L1-L40) [lib/index.js#L14-28](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/index.js#L14-L28)

## Contributor handoff

This repo has an unusually strict, written contract in `AGENTS.md` (symlinked as `CLAUDE.md`) with `> [!REQUIRED]` sections that reviewers enforce literally — read it before touching anything. The three rules that bite hardest: never invoke bare `jest` (use `yarn test:base`), never hand-edit generated files (run `yarn fix:special` and diff), and never sweep whole suites locally (push and read CI). Edits are safe inside `lib/` subdirectories and `test/`; they are dangerous at `lib/` root (deep-import breakage), inside generated `data.js`/`types.d.ts`/`*.check.js`, and in the hot parser methods sized to V8's inlining budget. Every user-facing change needs a `.changeset/NNN-*.md` entry and, for bug fixes, a failing test written first.
- Read first, in order: `AGENTS.md` (the binding contract, including the mandatory PR template), `TESTING_DOCS.md` (suite layout and how to run one case), `lib/index.js` (the public surface), `lib/config/WebpackOptionsApply.js` (option→plugin map), and `lib/Compilation.js#L3612-3999` (the seal pipeline everything else hangs off). — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [TESTING_DOCS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/TESTING_DOCS.md) [lib/index.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/index.js) [lib/config/WebpackOptionsApply.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/WebpackOptionsApply.js) [lib/Compilation.js#L3612-3999](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/Compilation.js#L3612-L3999)
- `lib/` is CommonJS only with types as JSDoc `@typedef`/`@import` — no `import`/`export` syntax, no TypeScript syntax in `.js` files — and the annotations are compiled to `types.d.ts` by `yarn fix:special`. — [lib/webpack.js#L1-45](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/webpack.js#L1-L45) [tsconfig.json#L1-12](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tsconfig.json#L1-L12) [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md)
- Adding or renaming an option requires four ordered edits plus regeneration: `schemas/WebpackOptions.json` → `lib/config/defaults.js` → `lib/config/normalization.js` → the consuming site → `yarn fix:special`, then the snapshots the option's name leaks into (`test/__snapshots__/Cli.basictest.js.snap`, `Defaults.unittest.js`, `Validation.unittest.js`, the browserslist config cases). — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [schemas/WebpackOptions.json](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/schemas/WebpackOptions.json) [lib/config/defaults.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/defaults.js) [lib/config/normalization.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/config/normalization.js)
- Never hand-edit the generated set: `types.d.ts`, `declarations/**/*.d.ts`, `schemas/**/*.check.{js,d.ts}`, generated runtime code, and `lib/{css,html,javascript}/data.js`. A new lookup table belongs in the matching `tooling/generate-*-data.js`, derived from a published dataset where possible. — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [lib/css/data.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/css/data.js) [tooling/generate-css-data.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tooling/generate-css-data.js) [package.json#L40-41](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L40-L41)
- Run targeted tests only: `yarn test:base --testPathPatterns="<pattern>"` or `yarn test:basic --testPathPatterns="ConfigTestCases" --testNamePattern="<category> <case>"`. Never pipe a jest run through `grep`/`tail` — that discards the exit code and a red run reads as green. — [package.json#L96-101](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L96-L101) [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [TESTING_DOCS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/TESTING_DOCS.md)
- A snapshot can live in two suites: `ConfigTestCases` and `ConfigCacheTestCases` both snapshot `configCases/`, and `--testPathPatterns=ConfigTestCases` misses the second — update with `yarn test:basic --testNamePattern="<case>" -u` and no path filter. — [test/ConfigTestCases.basictest.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/ConfigTestCases.basictest.js) [test/ConfigCacheTestCases.basictest.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/ConfigCacheTestCases.basictest.js) [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md)
- Writing a `configCases/` case: it is a mini project (`index.js` with assertions, `webpack.config.js`), the emitted bundle is actually executed in a `vm`, and optional `errors.js`/`warnings.js`/`test.filter.js`/`test.config.js` files control expected diagnostics, skipping and `findBundle`. — [test/templates/ConfigTestCases.js#L94-300](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/templates/ConfigTestCases.js#L94-L300) [test/templates/ConfigTestCases.js#L600-640](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/templates/ConfigTestCases.js#L600-L640) [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md)
- Never silence the cache suite with `test.filter.js` returning `!config.cache`; the sanctioned escape is an `infrastructure-log.js` returning `[/Pack got invalid because of write to/]` for cases whose subject is a build error (a module with `this.error` set always rebuilds by design). — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [test/ConfigCacheTestCases.basictest.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/ConfigCacheTestCases.basictest.js) [lib/module/NormalModule.js#L2059-2090](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/NormalModule.js#L2059-L2090)
- Moving a file out of `lib/` root has six carriers to rewrite — `require`/`require.resolve`, `@import` JSDoc, `@typedef {import(...)}`, `tsType` in schemas, the `makeSerializable` request, and paths written into `tooling/generate-runtime-code.js` or `eslint.config.mjs` — and `yarn find-deep-imports:check` must be run before the commit. — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [tooling/find-deep-webpack-imports.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tooling/find-deep-webpack-imports.js) [tooling/deep-webpack-imports.json](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/tooling/deep-webpack-imports.json) [lib/util/makeSerializable.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/util/makeSerializable.js)
- Comment budget is enforced by lint: a plain `//` or `/* */` comment is capped at three lines by the `webpack/comment-length` rule (JSDoc tag blocks, the file-header block, and `// WHY:` comments are the exemptions), and `examples/` is the only directory excluded. — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [eslint.config.mjs#L122-150](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/eslint.config.mjs#L122-L150)
- Deferred work uses exactly one wording — `TODO in the next major release: <what to do>` — never a version number, so `grep -rn "TODO in the next major release" codecov.yml bin hot lib setup test tooling` lists the whole backlog. — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [lib/wasm-sync/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/wasm-sync/) [lib/index.js#L315-330](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/index.js#L315-L330)
- Never enumerate module/source/dependency types in `lib/`: ask the object (`module.getSourceTypes()`, `chunkGraph.getModuleSourceTypes(module)`, `moduleGraph.getParentModule(dependency)`) or match the class (`dependency instanceof URLDependency`), and when a branch must name a type, write it so an unknown type takes the safe side. — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [lib/graph/ModuleGraph.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/graph/ModuleGraph.js) [lib/module/Module.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/lib/module/Module.js)
- Performance claims need the cheap, deterministic kinds of evidence first (counting > CPU-profile attribution > retained heap > wall timing); `FILTER="<case>" yarn benchmark` drives `test/benchmarkCases/`, `yarn test:size` counts emitted bytes, and gzip is the column a size change is judged on. — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [package.json#L57-58](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L57-L58) [test/CodeSizeTestCases.size.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/CodeSizeTestCases.size.js) [test/benchmarkCases/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/benchmarkCases/)
- Every user-facing change needs one `.changeset/NNN-<name>.md` with a `patch`/`minor`/`major` bump and a single capitalized imperative sentence ≤80 chars; the numeric prefix controls ordering in `CHANGELOG.md`, and `yarn validate:changeset` checks the format (there are 42 pending entries today). — [.changeset/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.changeset/) [package.json#L43](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L43) [.changeset/changeset-validate.mjs](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/.changeset/changeset-validate.mjs) [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md)
- Before pushing, run the full `yarn lint` (nine stages, `&&`-chained so the first failure hides the rest) — `yarn fix` alone runs no type check and no spellcheck; cspell reads every Markdown file in the repo, so even a docs-only change can red the `lint` job. — [package.json#L36-43](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/package.json#L36-L43) [cspell.json](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/cspell.json) [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md)
- A local failure is only yours if it does not reproduce on `main` — `Cli createColors`, `profiling-plugin`, the `many-replacements` cases and the generated-declaration check are known sandbox-environment failures, so check with `git worktree add <dir> origin/main` before chasing one. — [AGENTS.md](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/AGENTS.md) [test/Cli.basictest.js](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/Cli.basictest.js) [test/unitCases/](https://github.com/webpack/webpack/blob/87a62dec2304cd2c163c41feeeb43fbe3e5c21dd/test/unitCases/)

Source repository: https://github.com/webpack/webpack
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/webpack.json

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
