perf(webpack-cli): cache schema arguments and use map lookups for options#4760
Conversation
…ions Building CLI argument metadata from the webpack/dev-server schema walks a large JSON schema and was repeated within a single run (once per command and again in loadConfig). Memoize it per webpack module and schema. Replace the linear `.find()` scans over the full option list with map/set lookups when matching CLI options in loadConfig and the serve command. https://claude.ai/code/session_01PEtzv6Xqv2yXQaQsZaeoSF
…dConfig `loadConfig` rebuilt a full `schemaToOptions` array (~850 objects) and a lookup map on every run just to match passed CLI options. The cached `getArguments()` result is already a name-keyed map of exactly the metadata `processArguments` consumes, so index it directly. Also hoist the per-call `flagsWithAlias` array in `makeOption` to a module-level Set. This roughly halves the time spent in `loadConfig` and removes ~850 object allocations plus a Map per build invocation. https://claude.ai/code/session_01PEtzv6Xqv2yXQaQsZaeoSF
When no `--config` is passed, config discovery probed every `<name><ext>` combination (3 base names x ~35 extensions = ~100 paths) with a separate sequential `fs.access` call. For config-less builds these all fail, costing ~5-6ms of serialized syscalls. Read each candidate directory once with `readdir`, match names in the same priority order in memory, and confirm the chosen file with a single `access` (preserving exact existence semantics for e.g. broken symlinks). This keeps the common "config exists" path fast while cutting the no-config case from ~100 syscalls to ~2. https://claude.ai/code/session_01PEtzv6Xqv2yXQaQsZaeoSF
… unused `#createColors` loads the webpack package to obtain color helpers, but the constructor ran it eagerly on every invocation. Commands like `version` and `info` never otherwise need webpack, so they paid for loading it. Make `colors` a lazy getter so the webpack require only happens when colored output is actually produced. https://claude.ai/code/session_01PEtzv6Xqv2yXQaQsZaeoSF
The per-schema argument metadata cached for option parsing is large (~1MB for webpack, ~1.85MB including dev-server) but is only needed while setting up a command. Storing it via WeakRef lets the GC reclaim it once setup completes, which matters for long-running serve/watch sessions. A cache miss simply rebuilds it; in practice V8 keeps the target alive across the short setup window, so the dedup within a run (only one schema walk) is preserved. https://claude.ai/code/session_01PEtzv6Xqv2yXQaQsZaeoSF
…ession The serve command stashed the full webpack and dev-server option arrays (~900KB) in its command context, which lives for the entire dev-server session even though the arrays are only needed during setup. Build the arrays transiently in the `options` callback for registration, and derive the action's lightweight lookups (built-in option name set, dev-server arg metadata) from the cached `getArguments` map instead. The large arrays are now reclaimable after startup. https://claude.ai/code/session_01PEtzv6Xqv2yXQaQsZaeoSF
…tive The readdir-based discovery matched entry names case-sensitively, which differed from the previous `fs.access` behavior on case-insensitive filesystems (a config named e.g. `Webpack.Config.js` would no longer be found). Lowercase the directory entries for the membership check and rely on the existing `access` confirm for exact existence semantics, so behavior matches the old `fs.access` on both case-sensitive and case-insensitive filesystems while keeping the fast no-config path. https://claude.ai/code/session_01PEtzv6Xqv2yXQaQsZaeoSF
Setting up a command registered all ~850 webpack/dev-server options on commander on every run, which dominated CLI setup time (~28ms) and retained ~1.4MB of Option objects. Register only the options actually present in the argument tokens instead; unrecognized flags still error, and "did you mean" suggestions use the full option-name list stashed on the command. Help still registers every option so it can list them all. Cuts command-setup time roughly in half and CLI overhead by ~35% for a typical build, and drops retained heap by ~1.4MB. https://claude.ai/code/session_01PEtzv6Xqv2yXQaQsZaeoSF
🦋 Changeset detectedLatest commit: 1d130ba 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 |
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4760 +/- ##
==========================================
+ Coverage 92.37% 92.77% +0.39%
==========================================
Files 14 14
Lines 4736 4911 +175
Branches 698 734 +36
==========================================
+ Hits 4375 4556 +181
+ Misses 359 353 -6
Partials 2 2
... and 2 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR optimizes webpack-cli startup and command execution by caching expensive schema-derived CLI argument metadata and reducing repeated linear scans and filesystem probes.
Changes:
- Cache
webpack.cli.getArguments(schema)results per webpack module/schema usingWeakMap+WeakRef, and reuse the returned name-keyed argument map for option application. - Speed up option matching by switching from repeated
.find()scans toSet/map lookups, and avoid registering all CLI options with commander unless needed for help. - Optimize default-config discovery by reading candidate directories once (via
readdir) and confirming matches withfs.access.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/webpack-cli/src/webpack-cli.ts | Adds argument metadata caching, commander option-registration optimization, map-based option lookups, and faster default-config discovery; also lazily creates colors. |
| .changeset/fast-pumas-cache.md | Adds a changeset documenting the performance and memory improvements. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Short option: the alias is the first character; the rest (if any) is | ||
| // an attached value, e.g. `-d<value>` means `-d <value>`. | ||
| names.add(token[1]); |
| const entries = await readDirectoryEntries(path.dirname(resolvedBase)); | ||
|
|
||
| if (!entries) { | ||
| continue; | ||
| } |
| (command as Command & { allOptionNames?: string[] }).allOptionNames = commandOptions.map( | ||
| (option) => option.name, |
- Register every letter of a combined short token (e.g. `-abc`), not just the first, so clustered boolean short flags are not dropped and reported as unknown options. - Include the `--no-<name>` negated forms in the stashed option-name list so "did you mean" suggestions still work for mistyped negated flags. - Fall back to per-candidate `access` probing when a directory can't be listed (e.g. execute-only permissions), so default-config discovery keeps working in restricted-permission directories. https://claude.ai/code/session_01PEtzv6Xqv2yXQaQsZaeoSF
Move the candidate-search logic out of `loadConfig` into a dedicated `#findDefaultConfigFile` private method for readability. No behavior change. https://claude.ai/code/session_01PEtzv6Xqv2yXQaQsZaeoSF
Building CLI argument metadata from the webpack/dev-server schema walks a
large JSON schema and was repeated within a single run (once per command and
again in loadConfig). Memoize it per webpack module and schema. Replace the
linear
.find()scans over the full option list with map/set lookups whenmatching CLI options in loadConfig and the serve command.
https://claude.ai/code/session_01PEtzv6Xqv2yXQaQsZaeoSF