perf(react-doctor): cut install graph from 32 to 15 packages#312
Merged
Conversation
Cuts `npm i react-doctor` from 32 packages to 15 (~17 transitive installs eliminated, including ora's cli-cursor / cli-spinners / log-symbols / string-width / strip-ansi chain). - `commander` and `ora` are now inlined into dist/cli.js via tsdown's `deps.alwaysBundle` and moved to devDependencies. Both are pure-ESM/leaf packages that bundle cleanly. - `picocolors` is removed from dependencies entirely — it was already inlined via @react-doctor/core (a workspace devDep that gets bundled), so the duplicate runtime install was wasted. - `prompts` stays external because src/cli/utils/prompts.ts monkey-patches `prompts/lib/elements/multiselect` via `createRequire`, which needs a copy on disk. - `agent-install` stays external because its mcp helper pulls in jsonc-parser's UMD bundle, which Rolldown can't follow through the AMD/UMD wrapper. dist/cli.js grows from ~260 KB to ~445 KB (gzip ~62 KB → ~106 KB), but that's one tarball download instead of ~17 round-trips, so cold installs see a clean win. Warm installs measured locally drop from 2.3s to 0.86s. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
|
✅ No new issues Reviewed by reactreview for commit 54d4d04. Configure here. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
aidenybai
added a commit
that referenced
this pull request
May 22, 2026
Conflicts (packages/react-doctor/package.json, pnpm-lock.yaml): - Take main's dep reorganization from #312 (`commander`, `ora` moved from `dependencies` → `devDependencies`; `picocolors` dropped — not imported by this package). - Keep our `oxlint ^1.66.0` bump (origin/main still on ^1.63.0). - Keep our drop of the `eslint-plugin-react-you-might-not-need-an-effect` peer + dev dep — the OXC port in this PR (`4d466f05`) replaces the upstream plugin with native ports under `react-doctor/*`, so main's reintroduction of it as a devDependency in #312 is no longer needed. Verified post-merge: pnpm build / typecheck / lint clean; 1406 tests pass. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
npm i react-doctorpreviously pulled in 32 packages. After this change it pulls 15 — a 17-package (53%) reduction in the transitive install graph, with no functional change.npm install(warm cache, local)dist/cli.jssizedist/cli.jsgzipTotal
node_modulessize is unchanged (~45 MB), because the heavy hitters —typescript(~24 MB) andoxlint's native binding (~14 MB) — must stay external. The win is purely in the number of tarballs the package manager has to fetch, extract, and link.What changed
commanderandoraare now inlined intodist/cli.js.They're pure-ESM leaf packages that bundle cleanly via tsdown's
deps.alwaysBundle. Moved fromdependenciestodevDependencies. This kills the entireoratransitive chain (cli-cursor,cli-spinners,log-symbols,signal-exit,stdin-discarder,string-width,strip-ansi,is-interactive,is-unicode-supported,chalk, …).picocolorsremoved fromdependenciesentirely.It was already inlined into
dist/cli.jsanddist/index.jsvia@react-doctor/core(which is a workspacedevDependencyand gets bundled into the published artifact). The runtime install was a duplicate.promptsstays external on purpose.src/cli/utils/prompts.tsmonkey-patchesprompts/lib/elements/multiselectthroughcreateRequire. That patch needs to run against the same constructor instance thatbasePrompts(…)uses internally, which means we need a copy on disk torequire().agent-installstays external on purpose.Its
mcphelper (re-exported from the root entry) pulls injsonc-parser, which ships its main entry as a UMD bundle whoserequire("./impl/format")calls don't survive Rolldown's CJS handler. Bundling it produced a runtimeCannot find module './impl/format'crash. Leavingagent-installexternal sidesteps the issue.Files
packages/react-doctor/vite.config.ts— setdeps.alwaysBundle: ["commander", "ora"]and explicitly list the externals (agent-install,oxlint,oxlint-plugin-react-doctor,prompts,typescript).packages/react-doctor/package.json— movedcommanderandoratodevDependencies; removedpicocolors.pnpm-lock.yaml— regenerated.Verification
pnpm typecheck,pnpm lint,pnpm format:check,pnpm test(1204 tests across 100 files) all green.node packages/react-doctor/bin/react-doctor.js .against a small React project still produces the expected diagnostics.react-doctor install --dry-run -ystill detects agents and would install — exercises theagent-installexternal path.dist/cli.jsexactly match the newdependencies:typescript,prompts,oxlint-plugin-react-doctor,agent-install(plusoxlintinvoked viachild_process).dist/index.js(thereact-doctor/apientry) are justtypescriptandoxlint-plugin-react-doctor— programmatic API consumers no longer pullcommander/ora/picocolorsinto their own dep graphs either.Not in scope
typescript(~24 MB) is the single biggest install cost. It's used in one place (resolveUseCallBindingfor thereact-hooks(rules-of-hooks)use()suppression heuristic). Demoting it to an optional peer dep with a fallback is a bigger, behavior-changing refactor and is left for a follow-up.oxlint's native binding is also large but unavoidable — it's the linter we shell out to.