[all components] Reduce shared bundle size#4336
Conversation
Bundle size report
Check out the code infra dashboard for more information about this PR. |
✅ Deploy Preview for base-ui ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
7166d33 to
7c466fa
Compare
commit: |
This comment was marked as outdated.
This comment was marked as outdated.
d90c61e to
23a183b
Compare
This comment was marked as outdated.
This comment was marked as outdated.
Code reviewFound 3 issues (scored 75/100 confidence -- below normal posting threshold, posting by request):
base-ui/packages/react/src/floating-ui-react/utils/tabbable.test.ts Lines 56 to 58 in d2e76b4 base-ui/packages/react/src/floating-ui-react/utils/tabbable.test.ts Lines 70 to 72 in d2e76b4
base-ui/packages/react/src/toast/store.ts Lines 34 to 66 in d2e76b4
base-ui/packages/react/src/merge-props/mergeProps.ts Lines 63 to 65 in d2e76b4 base-ui/packages/react/src/merge-props/mergeProps.ts Lines 103 to 105 in d2e76b4 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
6fb6baf to
8427466
Compare
8427466 to
9cdf213
Compare
3c9d028 to
7fb202b
Compare
Claude Opus 4.6 reviewPreviously flagged issues — status check:
New concerns found in this review:1.
|
| Finding | Severity | Blocking? |
|---|---|---|
mergeProps getter-first mutation |
Low | No (not hit internally) |
useAnimationsFinished missing abort guard |
Low | No (wastes CPU, no visible bug) |
tabbable no tabIndex sorting |
Low | No (not used) |
| Chai assertions fixed | - | Resolved |
| Toast multi-provider fixed | - | Resolved |
| mergeProps safety comments restored | - | Resolved |
Overall assessment: The previously flagged issues are addressed. No blocking regressions found. The two new concerns (#1 and #2) are low-risk but worth noting — #2 in particular is a simple one-line fix (if (!signal?.aborted && currentAnimations.length > 0 && ...)).
Codex Review (GPT-5.4)This is a fresh correctness follow-up on a 1. Remaining Bugs / IssuesNo blocking or non-blocking correctness issues found in the latest pushed head. 2. Correctness Sweep
3. Validation
For perf, I reran the latest matrix sequentially against exported production builds for the popup routes, alternating 4. Refreshed Perf SweepPopup-route perf on exported production builds,
Non-popup sanity pass, using a lightweight alternating mount benchmark for
5. Earlier Supporting MicrobenchesI did not rerun the internal
6. Previous Review Issues Status
Confidence4/5. This pass covered the full branch again against the latest fetched base, refreshed the requested perf slices on the latest pushed head, and retained focused JSDOM/Chromium validation on the highest-risk interaction paths. Residual risk is still mostly in browser-specific focus edge cases around the custom |
ef8652b to
6d4574a
Compare
6d4574a to
5e199bd
Compare
| prevFocusElement: HTMLElement | null; | ||
| }; | ||
|
|
||
| const toastMapSelector = createSelectorMemoized( |
There was a problem hiding this comment.
Inlining createSelectorMemoized feels wrong?
There was a problem hiding this comment.
I thought so too but it wins 1.5 KB for toast-only imports:
So this one change is worth about 1.4-1.5 KB gz, which is pretty meaningful for a single local refactor.
Why it saves that much:
putting createSelectorMemoized back in store.ts pulls the heavier reselect path back into the toast bundle the current code only needs a very small per-store WeakMap cache, so the bespoke version is much cheaper
There was a problem hiding this comment.
How common are toast-only imports? Is that a real use-case?
If it's a real use-case, this could instead be an opportunity to create a createMemoizedSelectorLite and put that code where it belongs (with the Store code, so if there is an implementation change, it's modified at the same time).
There was a problem hiding this comment.
I generally think it's good for each component to only have as much code as it really needs to. It makes treeshaking as effective as possible throughout the codebase. And yeah I think it's a real use case given you can use any number of Base UI components you want.
I'll look into the Lite idea
There was a problem hiding this comment.
I'll look into the Lite idea
I think this is a good follow-up idea but not worth the abstraction for this PR because only Toast uses it. It'd be valid to do so if we need it elsewhere later on.
There was a problem hiding this comment.
see that the store-based comps are popular enough to usually be used somewhere in an app anyway
Actually I was mistaken about the import graph here. It's only Toast (and Calendar later) that uses the reselect dep. So it pulls in 1.5 kB which increases the root bundle as well. It seems like reselect is needlessly heavy for our use cases.
With the "Lite" implementation:
| Bundle | Gzip size |
|---|---|
| @base-ui/react | ▼-3.3KB |
Without
| Bundle | Gzip size |
|---|---|
| @base-ui/react | ▼-1.7KB |
There was a problem hiding this comment.
I think we will have more and more components that use the createSelectorMemoized.
So if we can make it lighter, that's awesome 👌
There was a problem hiding this comment.
Just experimented with this, and Codex wrote one faster than reselect in some cases but not in others (5 selectors, which Calendar has). So I guess because Calendar needs it, we'll have to leave it in the bundle.
There was a problem hiding this comment.
Bundle size decreases are still better than 1.7 KB if you don't use Toast (or later Calendar):
There was a problem hiding this comment.
It sounds like we can make wins on the reselect front, but it feels like those improvements could be left as a follow-up PR considering they require more examination & investigation.
romgrk
left a comment
There was a problem hiding this comment.
Feedback on the generated code: there are substantial issues with some of the changes here. Bundle-size is optimized at the cost of readability, performance and maintainability.
|
@romgrk addressed the review comments (Also with the useRenderElement changes I couldn't detect a difference in runtime perf running benchmarks locally with Codex. But I reverted them anyway as bundle size difference is negligible). |
778a4e0 to
9929523
Compare
bd25835 to
fd95a43
Compare
Summary
Reduces shared bundle cost across the popup, focus-management, and selector paths used by many Base UI components.
This keeps the measurable wins from the internal
tabbablehelper and the plain-vs-memoized selector split, trims shared utility code used across popup-heavy components, and backs out the unverified@base-ui/utils/store/...import-path churn so store consumers continue to use the standard barrel entrypoint.Changes
tabbabledependency with a smaller internal helper and add focused regression coverage for shadow DOM,details, hidden controls, radio groups, and inert content.Store,ReactStore, anduseStoreimports on@base-ui/utils/storeafter the subpath split did not show a verified bundle benefit.Notes
@base-ui/reactdown by about3.4 KBgzip versusmaster.master.