Skip to content

perf(start-plugin-core): reuse the Rsbuild dev client build when a rebuild cannot change it - #8800

Open
mattcosta7 wants to merge 5 commits into
TanStack:mainfrom
mattcosta7:perf/rsbuild-dev-client-build-reuse
Open

mattcosta7 wants to merge 5 commits into
TanStack:mainfrom
mattcosta7:perf/rsbuild-dev-client-build-reuse

Conversation

@mattcosta7

@mattcosta7 mattcosta7 commented Oct 23, 2026 •

Copy link
Copy Markdown

🎯 Changes

In rsbuild dev, every rebuild normalizes the client compilation and rebuilds the Start manifest, even for a code-only edit that cannot change either. This reuses the previous result when all of these hold:

  • the compilation directly follows the one that produced it
  • no file was removed
  • a fingerprint of the chunk graph is unchanged (chunk JS and CSS files, runtimes, module counts, chunk groups, entrypoints)
  • the active static imports of rebuilt modules and their direct importers are unchanged

The fingerprint only includes files normalization reads, so *.hot-update.* files and other assets don't defeat it. Its lines are sorted because HMR rebuilds reorder chunks between compilations without changing them. Import signatures are recorded once per dev server and then updated for rebuilt modules only. When the client build and route tree are unchanged, the dev manifest isn't rebuilt either.

128-route dev rebuild (tests/rsbuild/dev-client-build-reuse.bench.ts):

Mean
Normalize + manifest on every rebuild (before) 9.4 ms
Code-only rebuild, reused 1.8 ms
First dev compilation, recording signatures (once) 27 ms

A rebuild that does change imports or chunks pays the before cost plus the ~1.8 ms check.

Tests: a real Rspack watch test, run with and without HotModuleReplacementPlugin, compares every captured build with a fresh normalization after code-only, shared-module, CSS, hydration-query and dynamic-import edits, and checks which ones were reused. Weakening the import check or ignoring import changes makes it fail; so did the hot-update and chunk-order problems before they were fixed.

rebuilt-modules.ts is also added by #8801; both copies are identical. Rebases cleanly with #8780. If #8588 lands first, its rebase needs to keep getChunkJsFiles and getChunkCssFiles callable, since the fingerprint here uses them.

Written with an AI coding assistant.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with the relevant test commands, or tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Performance Improvements
    • Development rebuilds can reuse the previous client build and manifest when code-only edits leave the relevant build structure unchanged, reducing unnecessary rebuild work.
    • Changes that affect imports, routes, styles, or other build structure still trigger a fresh build and manifest update. Production behavior remains unchanged.

…build cannot change it

Reuse the previous normalized client build and dev manifest when the
compilation directly follows the captured one, no file was removed, the
chunk graph fingerprint is unchanged, and the active static imports of
rebuilt modules and their importers are unchanged.

The fingerprint covers only the files normalization reads, so HMR update
files and other assets do not defeat reuse, and it is independent of chunk
iteration order, which HMR rebuilds shuffle. Import signatures are recorded
once and then updated for rebuilt modules only.
@changeset-bot

changeset-bot Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ac1e563

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@tanstack/start-plugin-core Patch
@tanstack/react-start-rsc Patch
@tanstack/react-start Patch
@tanstack/solid-start Patch
@tanstack/vue-start Patch

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

@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 6b9fe6d and ac1e563.

Files selected for processing: 7
  • .changeset/rsbuild-dev-client-build-reuse.md
  • packages/start-plugin-core/src/rsbuild/normalized-client-build.ts
  • packages/start-plugin-core/src/rsbuild/rebuilt-modules.ts
  • packages/start-plugin-core/src/rsbuild/virtual-modules.ts
  • packages/start-plugin-core/tests/rsbuild/dev-client-build-reuse.bench.ts
  • packages/start-plugin-core/tests/rsbuild/normalized-client-build.test.ts
  • packages/start-plugin-core/tests/rsbuild/rebuilt-modules.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

Walkthrough

Development client-build capture now reuses a normalized build when rebuild checks show that imports, module edges, and chunk structure are unchanged. Development manifest updates also skip work when the client build and route manifest are unchanged.

Changes: Rsbuild development client-build reuse

Layer / File(s) Summary
Rebuild eligibility and snapshot reuse
packages/start-plugin-core/src/rsbuild/rebuilt-modules.ts, packages/start-plugin-core/src/rsbuild/normalized-client-build.ts
New helpers identify rebuilt modules and detect possible import re-resolution. Development capture tracks module-edge signatures and a chunk-graph fingerprint, then reuses the prior normalized build only when its snapshot remains eligible.
Development manifest update
packages/start-plugin-core/src/rsbuild/virtual-modules.ts
Development manifest updates return early when both the client build and route manifest match their stored references. Otherwise, the code records the route manifest and refreshes the development manifest.
Reuse tests and benchmarks
packages/start-plugin-core/tests/rsbuild/normalized-client-build.test.ts, packages/start-plugin-core/tests/rsbuild/rebuilt-modules.test.ts, packages/start-plugin-core/tests/rsbuild/dev-client-build-reuse.bench.ts, .changeset/rsbuild-dev-client-build-reuse.md
Tests cover rebuild eligibility and reuse across several types of edits. The benchmark measures normalization and manifest generation before and after a code-only rebuild. A patch changeset records the update.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Refactor

Sequence Diagram(s)

sequenceDiagram
  participant RspackCompiler
  participant registerClientBuildCapture
  participant mayHaveReResolvedImports
  participant getRebuiltModulesAndImporters
  participant NormalizedClientBuild
  participant DevelopmentManifest
  RspackCompiler->>registerClientBuildCapture: Advance or invalidate the prior snapshot
  registerClientBuildCapture->>mayHaveReResolvedImports: Check changed and removed files
  registerClientBuildCapture->>getRebuiltModulesAndImporters: Collect rebuilt modules and importers
  registerClientBuildCapture->>NormalizedClientBuild: Reuse eligible snapshot or normalize compilation
  NormalizedClientBuild->>DevelopmentManifest: Provide captured client build
  DevelopmentManifest->>DevelopmentManifest: Skip update if build and route manifest references match
Loading

Possibly related PRs

  • TanStack/router#8801: Uses the same rebuilt-module and import re-resolution helpers for incremental import-protection scans.

Suggested labels: package: start-plugin-core

Suggested reviewers: ladybluenotes, schiller-manuel

Merge Risk: Minimal · up to ac1e5

This change speeds up Rsbuild dev rebuilds by reusing the normalized client build when a rebuild cannot change it. The checks that guard reuse are conservative and tested against real watch builds. No actionable merge-blocking risk was found.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 6 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main performance change: reusing the Rsbuild development client build when a rebuild cannot change it.
Description check Passed The description includes the required Changes, Checklist, and Release Impact sections. It explains the implementation, motivation, benchmarks, tests, changeset, and AI assistance. The checklist item c…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description includes the required Changes, Checklist, and Release Impact sections. It explains the implementation, motivation, benchmarks, tests, changeset, and AI assistance. The checklist item confirming full understanding of the code remains unchecked, but the description is otherwise substantially complete.

Full details: Docstring Coverage

Explanation

Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 6 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…signatures

A rebuilt module could point a dynamic import at a module already in the graph while the chunk kept its name and module count, so the previous manifest was reused with the old route or hydration metadata.
Module identifiers include full loader chains, so keeping the joined identifiers retained about 7.9 KB per first-party module for the life of the dev server. Keys and signatures are now SHA-1 digests.
…r resolution changes

A changed package.json import map, exports map or tsconfig paths can re-resolve imports without rebuilding any module, so the edge check saw nothing change. Reuse now also requires that every changed file is a rebuilt module and none is a package.json or tsconfig file.
@mattcosta7
mattcosta7 marked this pull request as ready for review October 26, 2026 15:54
@coderabbitai coderabbitai Bot mentioned this pull request Oct 29, 2026
4 tasks done

This branch has not been deployed

No deployments
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.

1 participant