inspector: cut first paint by 30% and delete the unused half of packages/ui - #35
Open
Mark-Life wants to merge 3 commits into
Open
inspector: cut first paint by 30% and delete the unused half of packages/ui#35Mark-Life wants to merge 3 commits into
Mark-Life wants to merge 3 commits into
Conversation
`manualChunks` ended with a catch-all `return "vendor"` for anything under `node_modules`. A named chunk is reachable from the entry, so that line pinned every third-party dependency to the first paint — and would have silently undone any `lazy()` boundary added above it: the component splits, its dependencies do not. Only trees the first paint pulls in whole are named now; the rest is left to Rolldown. With that fixed, four boundaries carry their weight: the three non-default routes, `SessionDetail` inside the sessions route, the `Calendar` behind the date facet's popover, and the `Toaster`. `sessions` stays eager because an empty hash resolves to it. `@effect/rpc` serves NDJSON and MessagePack from one module and imports msgpackr statically, so 27 KB of MessagePack shipped whichever serialization the app built. `src/stubs/msgpackr.ts` is aliased in its place and throws if MessagePack is ever constructed; the transport is NDJSON. First paint: 1159.5 KiB raw / 332.6 KiB gzip -> 801.4 / 233.4. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`packages/ui` carried 56 shadcn components; 28 had no importer anywhere in the repo, `demo.tsx` included. They cost nothing in JS, but `globals.css` scans `packages/ui/src/**` for class names, so each one contributed utilities to both apps' stylesheets. The inspector's CSS drops 162.8 KiB -> 115.0 (24.6 -> 18.5 gzip); the web app's 162.0 -> 113.8. Two `@source` globs went with them. `../../../apps/**` and `../../../components/**` resolve against `packages/ui/src/styles`, so they pointed at `packages/apps` and `packages/components`, neither of which exists. Removing them leaves a byte-identical stylesheet. Eight dependencies were only reachable through the deleted components: `@base-ui/react`, `cmdk`, `date-fns`, `embla-carousel-react`, `input-otp`, `react-resizable-panels`, `vaul`, `zod`. `shadcn add <name>` brings any component back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Method, before/after numbers, what each boundary defers, and what is left. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Problem:
The inspector fetched its entire JavaScript bundle before painting a pixel — 332.6 KiB gzip, including a charting library only the memory route renders, a date picker only a popover opens, and a MessagePack codec the transport never uses. Nothing was code-split, so
dist/and the first paint were the same number.manualChunksinvite.config.tsended with a catch-allreturn "vendor"for anything undernode_modules. A named chunk is reachable from the entry, so that line pinned every dependency to the first paint, and would have silently undone anylazy()boundary added above it: the component splits, its dependencies do not.Separately,
packages/uicarried 56 shadcn components, 28 of which no file in the repo imports. They cost nothing in JS, butglobals.cssscanspackages/ui/src/**for class names, so each contributed utilities to both apps' stylesheets.Solution:
Name only the trees the first paint pulls in whole (
effect,react-dom,lucide-react) and leave the rest to Rolldown, which keeps a lazy-only module in the async chunk that needs it. Four boundaries then carry weight: the three non-default routes,SessionDetailinside the sessions route, theCalendarbehind the date facet, and theToaster.sessionsstays eager because an empty hash resolves to it. Each boundary has a sized Suspense fallback, so no pane collapses while its chunk arrives.@effect/rpcserves NDJSON and MessagePack from one module and imports msgpackr statically;src/stubs/msgpackr.tsis aliased in its place and throws if MessagePack is ever constructed. The CLI's server keeps the real package.Deleting the 28 unimported components takes the inspector's CSS from 162.8 KiB to 115.0 and the web app's from 162.0 to 113.8, and drops eight dependencies only reachable through them.
shadcn add <name>brings any of them back. Two dead@sourceglobs went too — they pointed atpackages/appsandpackages/components, neither of which exists.Full numbers and method in
docs/bundle-size.md.Security Impact:
None. The msgpackr alias is browser-build-only and narrows an unused deserializer to a throwing constructor.
Testing:
bun run typecheck,bunx --bun ultracite check, andbun test:The two failures are in
packages/core/test/agents.test.tsand reproduce unchanged onmain.No browser exists in this environment, so the lazy boundaries were exercised under happy-dom: the shell, all three lazy routes, the
Toaster(fired a toast to force it to paint) and theCalendarinside its Radix portal all mounted. That harness needs a global DOM registration that breaks the 28 non-DOM tests sharing the process, so it was not kept.peektrace servewas also run against the newdist/; all 20 assets return 200.Before, first paint was 1159.5 KiB raw / 332.6 KiB gzip and equalled the whole directory. After, it is 801.4 / 233.4, with 291 KiB deferred and 66.8 KiB genuinely deleted.