Skip to content

fix(core): speed up nx --version by avoiding heavy imports#35326

Merged
FrozenPandaz merged 6 commits intomasterfrom
speed-version
Apr 20, 2026
Merged

fix(core): speed up nx --version by avoiding heavy imports#35326
FrozenPandaz merged 6 commits intomasterfrom
speed-version

Conversation

@FrozenPandaz
Copy link
Copy Markdown
Contributor

@FrozenPandaz FrozenPandaz commented Apr 17, 2026

Current Behavior

nx --version (and other trivial CLI invocations) eagerly load the daemon client, dotenv loader, analytics + perf-logging stack, and init-local at the top of bin/nx.ts. That's ~225ms of unrelated module load time before main() starts running.

On a typical workspace, the cost looks like:

Layer Time
daemonClient import ~88ms
perf-logging (loads analytics → native binding) ~63ms
init-local ~36ms
dotenv ~19ms
analytics-prompt ~19ms

End-to-end: nx --version was ~440ms via pnpm exec.

Expected Behavior

--version (and other no-workspace fast paths) only load what they actually need.

This PR:

  • Lazy-loads the heavy modules inside the code paths that actually use them (cloud commands, local install handoff, etc.).
  • Adds a --version fast path at the top of main() that exits before any heavy module is touched.
  • Reworks src/utils/perf-logging.ts to lazy-require analytics / daemon logger inside the PerformanceObserver callback. Importing the module no longer pulls in the native binding.
  • Updates benchmarks/bench:* scripts to invoke the workspace nx directly via node ../packages/nx/dist/bin/nx.js instead of pnpm exec nx, which removes ~220ms of pnpm wrapper overhead from the measurement and works on CI agents (which don't sync per-project node_modules/.bin). goals.json is adjusted to the new floor.

Benchmark Results

Measured locally against the benchmarks/ workspace (1,110 projects). Deltas are shown as (vs Goal | vs Baseline).

Benchmark Goal Baseline Current
version 50ms 440ms 41ms (-19% | -91%)
show-projects 100ms 1.07s 434ms (+334% | -60%)
cat-warm 300ms 1.88s 1.04s (+245% | -45%)
copy-warm 500ms 1.48s 1.31s (+163% | -11%)
build-warm 750ms 1.71s 1.16s (+54% | -32%)

version clears the goal; the other benchmarks pick up the wrapper-overhead delta too, but several are still above goal and remain targets for follow-up work.

Related Issue(s)

N/A — internal performance work on the speed-version branch.

@FrozenPandaz FrozenPandaz requested a review from a team as a code owner April 17, 2026 12:55
@FrozenPandaz FrozenPandaz requested a review from MaxKless April 17, 2026 12:55
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 17, 2026

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit 75a7b19
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/69e6a4ee9cd2350008cfd763
😎 Deploy Preview https://deploy-preview-35326--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 17, 2026

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 75a7b19
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/69e6a4ee2a29d40008c33399
😎 Deploy Preview https://deploy-preview-35326--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented Apr 17, 2026

View your CI Pipeline Execution ↗ for commit 75a7b19

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 55m 3s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 3s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 19s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 25s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 14s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-20 23:11:42 UTC

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

@FrozenPandaz FrozenPandaz force-pushed the speed-version branch 2 times, most recently from 3d3fcbf to a032faa Compare April 17, 2026 19:35
nx-cloud[bot]

This comment was marked as outdated.

@FrozenPandaz FrozenPandaz force-pushed the speed-version branch 3 times, most recently from 6c42e9b to ce0d2ff Compare April 17, 2026 19:54
nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

…t path

The bin/nx.ts entry point eagerly imported the daemon client, dotenv loader,
analytics + perf-logging stack, and init-local for every invocation, even
trivial ones like --version. That added ~225ms of unnecessary module load
time before the first instruction of main() ran.

This commit:
- Lazy-loads those heavy modules inside the code paths that actually need them
  (cloud commands, local install handoff, etc.).
- Adds a --version fast path at the top of main() that exits before any heavy
  module is touched.
- Makes src/utils/perf-logging.ts lazy-require analytics / daemon logger
  inside the PerformanceObserver callback, so simply importing the module no
  longer pulls in the native binding (~63ms saved).
- Updates the bench:version benchmark (and friends) to call
  ./node_modules/.bin/nx directly instead of pnpm exec nx, removing
  ~220ms of pnpm wrapper overhead from the measurement.

Result for nx --version:
- nx-only contribution: ~220ms -> ~10ms
- end-to-end (./node_modules/.bin/nx --version): ~260ms -> ~49ms
FrozenPandaz and others added 3 commits April 20, 2026 15:02
The previous fast-path refactor guarded cleanup with
require.cache[require.resolve(...)] so modules would only be cleaned up
if they had actually been loaded. But require.resolve throws
synchronously when the module can't be resolved, which broke the exit
flow in published nx installs (observed as "Cannot find module
'../src/utils/db-connection'" in create-nx-workspace e2e tests and
masked the real error).

- Move the db-connection exit handler into db-connection.ts so it only
  registers when the module is actually imported.
- Track analyticsStarted via a plain flag in bin/nx.ts instead of
  probing require.cache with require.resolve.
Gives proper TypeScript types on the lazily-loaded modules without
'as typeof import(...)' casts. No measurable perf difference on the
--version fast path since it short-circuits before any of these fire.

Directory imports (analytics/) need an explicit /index.js because
dynamic import() uses ESM resolution rules.
Copy link
Copy Markdown
Contributor

@nx-cloud nx-cloud Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nx Cloud has identified a flaky task in your failed CI:

🔂 Since the failure was identified as flaky, we triggered a CI rerun by adding an empty commit to this branch.

Nx Cloud View detailed reasoning in Nx Cloud ↗

🔔 Heads up, your workspace has pending recommendations ↗ to auto-apply fixes for similar failures.


🎓 Learn more about Self-Healing CI on nx.dev

It's only used internally by the self-registered exit handler now, so
there's no reason to expose it.
…tdown

The db-connection module now self-registers a process 'exit' handler
that cleans up connections, so the daemon shutdown path doesn't need
to call it explicitly.
@FrozenPandaz FrozenPandaz merged commit c9b01b1 into master Apr 20, 2026
24 checks passed
@FrozenPandaz FrozenPandaz deleted the speed-version branch April 20, 2026 23:45
@github-actions
Copy link
Copy Markdown
Contributor

This pull request has already been merged/closed. If you experience issues related to these changes, please open a new issue referencing this pull request.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Apr 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants