CodeVetter · Repo Unpack · Contributor handoff
webpack/webpack
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).
- `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`.
- 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).
- 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.
- 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.
- 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.
- 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`.
- 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).
- 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.
- 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.
- 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.
- 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.
- 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.
- 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).
- 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.
- 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.