Skip to content

perf(react-doctor): cut install graph from 32 to 15 packages#312

Merged
aidenybai merged 1 commit into
mainfrom
cursor/reduce-react-doctor-install-time-2d19
May 22, 2026
Merged

perf(react-doctor): cut install graph from 32 to 15 packages#312
aidenybai merged 1 commit into
mainfrom
cursor/reduce-react-doctor-install-time-2d19

Conversation

@aidenybai
Copy link
Copy Markdown
Member

Summary

npm i react-doctor previously pulled in 32 packages. After this change it pulls 15 — a 17-package (53%) reduction in the transitive install graph, with no functional change.

Before After
Direct deps 8 5
Total packages installed 32 15
npm install (warm cache, local) 2.34s 0.86s
dist/cli.js size 260 KB 445 KB
dist/cli.js gzip ~62 KB ~106 KB

Total node_modules size is unchanged (~45 MB), because the heavy hitters — typescript (~24 MB) and oxlint'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

  1. commander and ora are now inlined into dist/cli.js.
    They're pure-ESM leaf packages that bundle cleanly via tsdown's deps.alwaysBundle. Moved from dependencies to devDependencies. This kills the entire ora transitive chain (cli-cursor, cli-spinners, log-symbols, signal-exit, stdin-discarder, string-width, strip-ansi, is-interactive, is-unicode-supported, chalk, …).

  2. picocolors removed from dependencies entirely.
    It was already inlined into dist/cli.js and dist/index.js via @react-doctor/core (which is a workspace devDependency and gets bundled into the published artifact). The runtime install was a duplicate.

  3. prompts stays external on purpose.
    src/cli/utils/prompts.ts monkey-patches prompts/lib/elements/multiselect through createRequire. That patch needs to run against the same constructor instance that basePrompts(…) uses internally, which means we need a copy on disk to require().

  4. agent-install stays external on purpose.
    Its mcp helper (re-exported from the root entry) pulls in jsonc-parser, which ships its main entry as a UMD bundle whose require("./impl/format") calls don't survive Rolldown's CJS handler. Bundling it produced a runtime Cannot find module './impl/format' crash. Leaving agent-install external sidesteps the issue.

Files

  • packages/react-doctor/vite.config.ts — set deps.alwaysBundle: ["commander", "ora"] and explicitly list the externals (agent-install, oxlint, oxlint-plugin-react-doctor, prompts, typescript).
  • packages/react-doctor/package.json — moved commander and ora to devDependencies; removed picocolors.
  • pnpm-lock.yaml — regenerated.

Verification

  • pnpm typecheck, pnpm lint, pnpm format:check, pnpm test (1204 tests across 100 files) all green.
  • Smoke test: node packages/react-doctor/bin/react-doctor.js . against a small React project still produces the expected diagnostics.
  • Smoke test: react-doctor install --dry-run -y still detects agents and would install — exercises the agent-install external path.
  • Verified the externals in dist/cli.js exactly match the new dependencies: typescript, prompts, oxlint-plugin-react-doctor, agent-install (plus oxlint invoked via child_process).
  • Verified the externals in dist/index.js (the react-doctor/api entry) are just typescript and oxlint-plugin-react-doctor — programmatic API consumers no longer pull commander/ora/picocolors into their own dep graphs either.

Not in scope

  • typescript (~24 MB) is the single biggest install cost. It's used in one place (resolveUseCallBinding for the react-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.
Open in Web Open in Cursor 

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>
@reactreview
Copy link
Copy Markdown

reactreview Bot commented May 22, 2026

No new issues

Reviewed by reactreview for commit 54d4d04. Configure here.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-doctor-website Ready Ready Preview, Comment May 22, 2026 3:05am

@aidenybai aidenybai marked this pull request as ready for review May 22, 2026 03:06
@aidenybai aidenybai merged commit 9047249 into main May 22, 2026
7 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants