perf: validate options with a precompiled schema - #2413
Conversation
Validating the options compiles the schema with ajv on the first call, which cost 159ms of the first `middleware()` against 0.08ms for each call after it. Every webpack-dev-server start pays it once. `src/options.check.js` is that schema precompiled by `ajv`'s standalone codegen. `internalValidate` asks it first and only falls through to `compiler.validate`/`schema-utils` when it rejects, so the happy path never loads ajv, `options.json` stays unread until something is actually wrong, and an invalid config still gets the same message. The generated validator agrees with `schema-utils` on all 76 cases of the `validation-options` corpus. Checking here rather than passing the validator to `compiler.validate` as its fourth argument keeps this working across the whole supported webpack range, not only versions carrying that parameter. Validation drops to 1.9ms, and a dev server's time-to-listening for a hello-world falls from 523 +/- 25ms to 438 +/- 17ms.
🦋 Changeset detectedLatest commit: 31d61d7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. WalkthroughThe PR adds an Ajv generator and a generated options validator. Priority: ⬇️ Low 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
`compiler.hooks.validate` and `compiler.validate`'s lazy-schema and precompiled-check parameters landed together in webpack 5.106, so the hook doubles as the feature probe for them. When it is there, the precompiled check is handed to the compiler the way webpack's own plugins hand it theirs, rather than being run before it. The direct path stays as the fallback, keeping the existing TODO to drop it once the minimum supported webpack carries the hook.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2413 +/- ##
==========================================
+ Coverage 97.16% 97.29% +0.12%
==========================================
Files 12 13 +1
Lines 1660 1664 +4
==========================================
+ Hits 1613 1619 +6
+ Misses 47 45 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
codecov flagged the fallback as the uncovered part of the diff: CI installs webpack 5.110, where `compiler.hooks.validate` always exists, so nothing reached the path taken on webpack < 5.106. Replacing the frozen `compiler.hooks` with a copy that lacks `validate` exercises it, and the spy asserts the fallback validated rather than the compiler. Without these two tests, lines 232, 233, 236 and 238 are uncovered; with them, every statement in `internalValidate` is.
Summary
Validating the options compiles the schema with ajv on the first call, which cost 159ms of the first
middleware()against 0.08ms for each call after it. Every webpack-dev-server start pays it once.src/options.check.jsis that schema precompiled by ajv's standalone codegen, generated byscripts/generate-schema-check.mjsand verified current bylint:schema-check.internalValidateasks it first and only falls through tocompiler.validate/schema-utilswhen it rejects, so the happy path never loads ajv,options.jsonstays unread until something is actually wrong, and an invalid config still gets the same message — the same approach webpack uses for its own schema. Validation drops to 1.9ms.Pairs with webpack/webpack-dev-server#5746, which does the same for its own schema. The two are worth reading together: fixing only the dev server moves ~76ms into this package, because it then pays the ajv load the dev server's constructor used to. On its own that is −7.3% of a hello-world's time-to-listening; with this one it is −22.4% (565 ± 14ms → 438 ± 17ms, eight interleaved runs per arm).
Checking here rather than passing the validator to
compiler.validateas its fourth argument is deliberate: that parameter exists in webpack 5.110, but the peer range is^5.101.0and I could not confirm it across all of it. Checking first is version-agnostic and behaviourally identical, including forcompiler.options.validate === false.What kind of change does this PR introduce?
perf
Did you add tests for your changes?
Yes,
test/schema-check.test.js. The existingvalidation-optionssuite already catches a validator that wrongly accepts invalid options; it cannot see one that wrongly rejects valid options, because validation then falls back to the real schema, which accepts them — every test passes while the startup cost silently returns. The generated validator was also checked to agree withschema-utilson all 76 cases of thevalidation-optionscorpus, with no mismatches, and the 35 error-message snapshots are unchanged.Does this PR introduce a breaking change?
No. Validation accepts and rejects exactly what it did before, with identical error messages.
If relevant, what needs to be documented once your changes are merged or what have you already documented?
n/a — no public behaviour or option changes.
src/options.check.jsis generated;npm run fix:schema-checkregenerates it andnpm run lint:schema-check(part oflint) fails if it is stale.Use of AI
AI-assisted (Claude Code). It was used to profile startup, to write the generator and tests, and to run the differential and A/B measurements. Every number quoted here came from a run, not an estimate, with arms interleaved to absorb machine drift. The full suite was run before and after: the 74 failures in
test/logging.test.jsare pre-existing and environmental (jest needs Node ≥24.9 torequireESMexeca), confirmed identical on a clean checkout. All output was reviewed before committing.🤖 Generated with Claude Code
https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
Generated by Claude Code
Summary by CodeRabbit
Performance
Validation
Compatibility
Release