perf: render root help without loading every command - #1591
productdevbook wants to merge 1 commit into
Conversation
`nuxt --help` (and bare `nuxt`) evaluated all 21 command modules: citty's `_findSubCommand` resolved each one looking for an alias even when no command was given, and `renderUsage` resolved each one again to read its meta. Command metas now live in `commands/meta.ts`, which the commands import, so the root usage is rendered from that table instead. The citty patch returns early from `_findSubCommand` when there is no name to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
commit: |
CLI benchmark
Full report
|
| Setting | Value |
|---|---|
| Baseline | ref:f140924fb88992febb831316ac01bce275609336 (v4.0.0-alpha.1) |
| Head | local packages/nuxt-cli at 1c7c2c0 (v4.0.0-alpha.1) |
| Node | v24.21.0 |
| OS | Linux 6.17.0 (kernel 6.17.0-1022-azure) |
| CPU | AMD EPYC 7763 64-Core Processor x 4 |
| Memory | 15.6 GB |
| Load average at start | 1.48, 0.46, 0.16 |
| Run started | 2026-10-03T16:59:23.741Z |
Cold CLI startup
Median of 15 interleaved runs per command, one warmup discarded.
| Command | baseline v4.0.0-alpha.1 median | head v4.0.0-alpha.1 median | Delta | baseline v4.0.0-alpha.1 min / p95 | head v4.0.0-alpha.1 min / p95 |
|---|---|---|---|---|---|
nuxt --version |
66 ms | 65 ms | -1.9% | 64 ms / 68 ms | 63 ms / 67 ms |
nuxt --version (first output byte) |
62 ms | 61 ms | -1.9% | 60 ms / 63 ms | 59 ms / 63 ms |
nuxt --help |
139 ms | 65 ms | -52.7% | 134 ms / 142 ms | 63 ms / 69 ms |
nuxt --help (first output byte) |
133 ms | 62 ms | -53.5% | 129 ms / 137 ms | 59 ms / 65 ms |
nuxt dev --help |
103 ms | 103 ms | -0.5% | 101 ms / 106 ms | 99 ms / 108 ms |
nuxt dev --help (first output byte) |
98 ms | 97 ms | -1.1% | 96 ms / 101 ms | 95 ms / 103 ms |
nuxt <unknown-command> (no-op) |
148 ms | 149 ms | +0.3% | 144 ms / 151 ms | 144 ms / 153 ms |
nuxt <unknown-command> (no-op) (first output byte) |
142 ms | 143 ms | +0.4% | 138 ms / 145 ms | 139 ms / 147 ms |
Module load cost
Counted with a module.registerHooks load hook, compile cache disabled. Counts every JS module actually evaluated on that code path (native addons excluded). Built-ins loaded after bootstrap are counted separately, including the internal modules they load.
| Command | baseline v4.0.0-alpha.1 modules | head v4.0.0-alpha.1 modules | Delta | baseline v4.0.0-alpha.1 source bytes | head v4.0.0-alpha.1 source bytes | Delta | baseline v4.0.0-alpha.1 built-ins | head v4.0.0-alpha.1 built-ins | Delta |
|---|---|---|---|---|---|---|---|---|---|
nuxt --version |
35 | 35 | 0.0% | 297.8 kB | 300.2 kB | +0.8% | 27 | 27 | 0.0% |
nuxt --help |
134 | 35 | -73.9% | 839.9 kB | 300.2 kB | -64.3% | 87 | 27 | -69.0% |
nuxt dev --help |
63 | 63 | 0.0% | 453.0 kB | 455.3 kB | +0.5% | 87 | 87 | 0.0% |
Install footprint and published tarball
Each version installed on its own into an empty project with nothing but @nuxt/cli as a dependency, so the tree is exactly the CLI and its transitive dependencies. npm cache is warm and the registry is only consulted for metadata, so install wall time is indicative, not a network benchmark.
| Metric | baseline v4.0.0-alpha.1 | head v4.0.0-alpha.1 | Delta |
|---|---|---|---|
Direct dependencies of @nuxt/cli |
23 | 23 | 0.0% |
| Packages in the installed tree (unique name@version) | 39 | 39 | 0.0% |
| Unique package names | 39 | 39 | 0.0% |
| Package directories on disk (cross-check) | 32 | 32 | 0.0% |
Installed node_modules on disk |
2.43 MB | 2.43 MB | +0.1% |
| Installed files | 434 | 434 | 0.0% |
| Install wall time (warm npm cache, median of 3) | 1.30 s | 1.31 s | +0.8% |
| Published tarball (packed) | 239.6 kB | 240.5 kB | +0.4% |
| Published tarball (unpacked) | 775.2 kB | 776.6 kB | +0.2% |
| Files in tarball | 99 | 99 | 0.0% |
Interleaved runs on a shared runner: trust the deltas, not the absolute timings. The dev, restart and build suites run locally via pnpm bench:cli.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1591 +/- ##
=======================================
Coverage ? 83.37%
=======================================
Files ? 178
Lines ? 11310
Branches ? 3247
=======================================
Hits ? 9430
Misses ? 1584
Partials ? 296 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
⛔ Files ignored due to path filters (1)
📒 Files selected for processing (24)
Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review. 📝 WalkthroughWalkthroughCLI commands now use a shared metadata catalog, including hidden status for Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to The reviewed help-text and argument-forwarding paths retain their expected behavior; no identified issue blocks merging. Security Architecture ReviewSecurity architecture risk: ⚪ Minimal · up to The change avoids loading command implementations for root help while preserving named-command execution and existing controls. No introduced or worsened security issue was identified. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 23 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches🧪 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 |
nuxt --helpand barenuxtevaluated all 21 command modules (dev,build,upgrade,typecheck, …) only to print one description line for each. The modules were loaded in two places:_findSubCommandresolves every subcommand to look for an alias, even when no command name was given (undefined in subCommandsis false, so it falls through to the scan).renderUsageresolves every subcommand again to readmeta.description/meta.hidden.Changes
commands/meta.ts: the commands'metaobjects move here, and each command imports its own entry (meta: commandMeta.build), so there is still a single source for each description. The table is typedsatisfies Record<keyof typeof commands, CommandMeta>, so adding a command without a meta is a type error.run.ts:showUsagerenders the root command with each subcommand reduced to{ meta }from that table. Subcommand help (nuxt dev --help,nuxt module --help) is unchanged._findSubCommandreturns early whennameisundefined. The lockfile change is only the new patch hash.The output of
nuxt --help,nuxtandnuxt foois byte-identical before and after (NO_COLOR=1, diffed).Benchmark
pnpm bench:cli --baseline ref:main --fixture playground --suite startup --suite modules --startup-reps 21, run on an M3 Max with Node 24.21 and interleaved runs. The baseline ismainat f140924.nuxt --help--version,dev --helpand unknown commands load the same modules as before.Checks
eslint packages/nuxt-cli/srcandtsc --noEmitpassvitest run packages/nuxt-cli/test/unitplus e2ecommands,unknown-command,hidden-commandsandunknown-flags: 136 files, 1893 passed, 10 todo. This includes the inline help snapshots inhelp.spec.tstest:dist,generate-command-docs --checkandpnpm i --frozen-lockfilepass🤖 Generated with Claude Code