perf(webpack-cli): allocate Levenshtein buffer lazily#4765
Conversation
The 256 KB peq Uint32Array was allocated at module load, so every CLI
invocation paid that cost even though distance() only runs on error
paths ("did you mean" suggestions). Defer the allocation to the first
distance() call.
https://claude.ai/code/session_01TKJqMqs6zkot18iBUae52n
Profiling `webpack build` showed CLI-owned code is a tiny fraction of startup; the cost is module loading. Two reductions there: - Default-config discovery now imports `interpret` only when no common-extension config (.js/.mjs/.cjs/.ts/.cts/.mts) is found, so the common `webpack.config.js` build never loads it. - The Levenshtein "did you mean" helper is inlined into webpack-cli as a private method, removing the separate module/import. The 256 KB buffer stays lazily allocated (error paths only). https://claude.ai/code/session_01TKJqMqs6zkot18iBUae52n
Migrate the previously removed test/api/levenshtein.test.js to exercise the inlined implementation. The helper is now a `private static distance` method (TypeScript `private` is erased at runtime), so the unit tests call it directly without re-introducing a separate module or import. https://claude.ai/code/session_01TKJqMqs6zkot18iBUae52n
…ssed A plain `webpack build` (no option flags) previously still ran webpack's ~28ms `getArguments` schema walk and built the full ~864-entry option list, then registered nothing. Now: - makeCommand skips building/registering options entirely when argv has no option flags (nothing to register, and no unknown-option suggestions are possible without flags). - loadConfig computes the argument metadata lazily, skipping it when only internal keys (webpack/argv/isWatchingLikeCommand) are present. Net: a plain build makes 0 getArguments calls (was 1), ~14-18ms faster startup and ~2.5MB less heap. Builds with flags or entry operands are unchanged (getArguments still runs once, as before). https://claude.ai/code/session_01TKJqMqs6zkot18iBUae52n
🦋 Changeset detectedLatest commit: 7c20d69 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✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4765 +/- ##
==========================================
+ Coverage 92.82% 92.89% +0.06%
==========================================
Files 15 14 -1
Lines 5076 5124 +48
Branches 752 756 +4
==========================================
+ Hits 4712 4760 +48
Misses 362 362
Partials 2 2
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 reduces webpack-cli startup overhead by deferring or skipping work that’s only needed on error paths or when certain flags/config patterns are present.
Changes:
- Lazily allocate the 256 KB Levenshtein lookup buffer and inline the distance implementation to avoid a module-load allocation/import on typical runs.
- Skip building the full command option list (and related “did you mean” candidate list) when no option flags are present in argv.
- Defer importing
interpretuntil common config extensions are not found during default config resolution, and makegetArguments()computation lazy when only internal option keys are present.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/api/levenshtein.test.js | Updates the unit test to call the inlined Levenshtein implementation via WebpackCLI.distance. |
| packages/webpack-cli/src/webpack-cli.ts | Inlines and lazily allocates Levenshtein buffer; skips option registration work when no flags; defers interpret import; lazily computes getArguments() and filters internal keys. |
| packages/webpack-cli/src/levenshtein.ts | Removes the standalone Levenshtein module after inlining into webpack-cli.ts. |
| .changeset/skip-getarguments-no-flags.md | Adds release note for skipping schema-to-arguments work on no-flag invocations. |
| .changeset/lazy-levenshtein-buffer.md | Adds release note for lazy Levenshtein buffer allocation. |
| .changeset/defer-interpret-inline-levenshtein.md | Adds release note for deferring interpret import and inlining Levenshtein helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| --- | ||
| "webpack-cli": patch | ||
| --- | ||
|
|
||
| Skip the webpack schema-to-arguments walk on a plain `webpack build` (and other no-flag invocations). When no option flags are present, the CLI no longer builds the full option list or calls `getArguments`, reducing startup time and peak memory for the most common invocation. |
bin/cli.js required both `import-local` and the full CLI implementation up front. Reorder so: - a run delegated to a local install returns without loading the outer installation's CLI lib (+commander), and - WEBPACK_CLI_SKIP_IMPORT_LOCAL short-circuits before requiring `import-local` at all (10 fewer modules loaded on that path). The typical local invocation is unchanged. Behavior is preserved (import-local still runs by default). https://claude.ai/code/session_01TKJqMqs6zkot18iBUae52n
Replace the TypeScript `private static distance` with a `#distance` private method that delegates to a module-scoped `distance` function. The function is exported so the unit tests can exercise the algorithm directly. https://claude.ai/code/session_01TKJqMqs6zkot18iBUae52n
The 256 KB peq Uint32Array was allocated at module load, so every CLI
invocation paid that cost even though distance() only runs on error
paths ("did you mean" suggestions). Defer the allocation to the first
distance() call.
https://claude.ai/code/session_01TKJqMqs6zkot18iBUae52n