Repository navigation
perf(signals,web): get the hello world back under 10 KB - #4104
Conversation
Optional engines install their scheduler hooks as module-level variables instead of GlobalQueue statics, so the minifier folds their paths away in bundles that never load them. Lane- and verdict-only paths in recompute, read, setSignal and the settle step are gated on those hooks. On the web side, client-only render drops hydration text adoption, eventHandler drops guards no caller needs, and reconcileArrays reuses isLive. compiled floor 10,352 -> 9,752 B brotli; render + one signal 10,171 -> 9,549 B; signals core floor 7,657 -> 7,238 B. Caps ratcheted down. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F58uQgS9taZV2yd3SW4srW
🦋 Changeset detectedLatest commit: 9ad5ede The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
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 |
Size (brotli, eager entry chunk)
|
|
Failing: Why it's not this PR: the same job fails on No fix exists yet. Here is the proposed patch, outside this PR's scope: // examples/chat-client/test/chat.test.tsx
-const attribution = { hotTime: false, fallbackFlashes: false } as const;
+const attribution = { hotTime: false, fallbackFlashes: false, wastedRecompute: false } as const;
Generated by Claude Code |
Coverage Report for CI Build 38812119112Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Warning No base build found for commit Coverage: 77.511%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsRequires a base build to compare against. How to fix this → Coverage Stats
💛 - Coveralls |
Merging this PR will regress 1 benchmark
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | build + consume |
694.5 µs | 740.8 µs | -6.25% |
| ⚡ | dynamic-tag update 100/1000 rows × 16: Dynamic |
37.7 ms | 34.9 ms | +7.98% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing claude/project-thread-kyj9yk (9ad5ede) with next (3e94215)
…like the other wall-clock findings Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F58uQgS9taZV2yd3SW4srW
Requested by Ryan · project thread
Summary
Before: the JSX hello world (
app: compiled floor) shipped 10,352 B brotli and had been over 10 KB since we added it on Oct 5. The hand-written floor (render + one signal) crossed 10 KB today with #4085 (9,994 → 10,156 B) and sat 1 B over its cap onnext.After: the JSX hello world ships 9,752 B (−600 B) and the hand-written floor ships 9,549 B (−622 B). The signals core floor goes from 7,657 B to 7,238 B. Behavior doesn't change, and there's no new public API.
The bundler's minifier can remove a module-level
let x = nullthat nothing assigns, along with every check on it. It can't do that forGlobalQueue._xstatics, which is how the optional engines plugged in until now.How:
GlobalQueuehooks the floor reads become module variables inscheduler.ts. Each engine (lanes, verdicts, boundaries, store, affects, external source) sets its own through aninstall…Hooks()function when it loads. Code that only lanes or verdicts can reach is gated on that engine being installed: the lane seat and staging inrecompute, lane and pending-flight reads inread, the lane write insetSignal,propagateStatus's lane step,stagedReaders,stagedScreen/frameRead, and the settle step's zombie re-run loop. Without those engines, the gated state can't occur: onlylanes.ts(andstore/optimistic.tsthrough it) sets lane state, and onlyverdict.tssets the verdict flag. The deaduntrackfast path is removed. It compared_externalUntrack === nullagainst a static that was alwaysundefined, so it never ran.renderno longer keeps hydration text adoption innormalize, which is now gated on the hydration runtime that onlyhydrate()installs.eventHandlerdrops guards that no caller needs.reconcileArraysreusesisLivefor its ownership checks.selectInsertmoved so the minifier can remove it.Three pages that load every engine move slightly the other way, because brotli compresses the smaller web code a little worse there:
page: live server components: +92 B brotli, −1 B minified. It's now 43 B over its cap, but within its recorded minified (+4 B), so the gate warns rather than fails.live + routerandcompiled live: already over their caps onnext, and they move +43 B and +13 B.How did you test this change?
node scripts/size/size.mjsbefore and after; all scenario numbers above come from it, and the ratchet table is inscenarios.js.pnpm test: 5,312 passed, the same asnext.pnpm test: 868/868.cross-package-fieldsneedspackages/universal/distbuilt, both before and after this change.server-functions-adapter-request.spec.tsx, which also fails onnext.🤖 Generated with Claude Code
https://claude.ai/code/session_01F58uQgS9taZV2yd3SW4srW
Generated by Claude Code