Perf: SPA route-change re-sweep + detached-subtree fast-path (#150 Tier 1S)#152
Merged
Merged
Conversation
Tier 1S of #150 — targets SPA route transitions specifically. Without these, a React/Vue route swap that mounts thousands of nodes either waits out a 250ms throttle window before the new content is hidden, or misses URL-gated selectors that newly apply to the route. lib/route-change.ts (new): - Process-wide route-change emitter. Hooks the Navigation API (`navigatesuccess`, modern Chrome), plus `popstate` and `hashchange` as belt-and-suspenders. Dedupes by URL so multiple events for one route fire the listener once. Older browsers without Navigation API miss pushState-only routes, but the watcher catches up on the next throttle window — this is a latency optimization, not a correctness requirement. lib/subtree-watcher.ts: - Subscribe to route changes in start(). On a detected change, cancel the pending throttle, discard buffered MutationRecords, clear pending, then schedule a one-frame-later sweep that calls onSubtrees([document.body]). The rAF wait lets the framework commit the new route's DOM before we scan. Consecutive route changes coalesce — only the latest scheduled sweep fires. Skipped while the tab is hidden or the watcher has been stopped. - Detached-subtree fast-path in enqueue(). React's reconciliation routinely adds-then-removes nodes within one synchronous block; by the time the MO callback fires, the addition record is still there but the node is already detached. drain() filters by isConnected too — checking at enqueue keeps pending small during noisy route-swap bursts. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
Tier 1S of #150 — targets SPA route transitions specifically. Without these, a React/Vue route swap that mounts thousands of nodes either waits out a 250ms throttle window before the new content is hidden, or misses URL-gated selectors that newly apply to the route.
Summary
lib/route-change.ts(new) — process-wide route-change emitter:navigatesuccess(modern Chrome) pluspopstateandhashchangeas belt-and-suspenders.lib/subtree-watcher.ts— wire route changes + detached fast-path:subscribeRouteChangeinstart(). On a detected change:MutationRecords, clearpending.onSubtrees([document.body]).enqueue(): React reconciliation routinely adds-then-removes nodes within one synchronous block. By the time the MO callback fires, the addition record is still there but the node is already detached.drain()filters byisConnectedtoo — checking at enqueue keepspendingsmall during noisy route-swap bursts.What Tier 1S items are landed
From issue #150:
Test plan
bun run test→ 1177 passed (1164 baseline + 6 route-change.test.ts + 7 new subtree-watcher cases)bun run typecheckbun run check(biome + eslint)bun run knipbun run test:coverage— statements 85.55% / branches 76.81% / functions 88.71% / lines 85.46% (above ratchet)Refs #150.
🤖 Generated with Claude Code