refactor(theme)!: swap tailwind-merge for cn behind the merger seam - #7006
Draft
benjamincanac wants to merge 2 commits into
Draft
benjamincanac wants to merge 2 commits into
benjamincanac wants to merge 2 commits into
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
commit: |
Merging this PR will not alter performance
Comparing Footnotes
|
benjamincanac
force-pushed
the
feat/cn-merger
branch
from
September 22, 2026 15:57
3fab667 to
071bec9
Compare
8 tasks
benjamincanac
force-pushed
the
feat/cn-merger
branch
from
September 22, 2026 16:54
071bec9 to
608866d
Compare
…he merger config
This branch was successfully deployed
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.
🔗 Linked issue
Part of #6918, Epic H of #6610. Needs shadcn-ui/cn#143, fixed in
cn@0.3.1.❓ Type of change
📚 Description
On hold (2026-09-22). Nothing here reaches users: the merger runs 2 to 9 times on a cold render and never on a warm one. Against that, it costs 734 B gzip and the class-group extension. The seam keeps this a one-import change whenever we pick it up, and
cn/configcan bring class groups back without a breaking change. Worth revisiting when any of these moves:cn's gzipped size after bundling is at or undertailwind-merge's (9.8 KB vs 8.5 KB today)cnowns its tables instead of generating them fromtailwind-merge's configcnreplacestailwind-mergebehindgetMerger(), the seam the engine has had since #6964. Owning the engine is what makes this one import rather than aliasing a package inside the consumer's build graph.Why now. Our report (shadcn-ui/cn#143) was fixed in
cn@0.3.1: the vendoredtailwind-mergewent to 3.7.0, the tables were regenerated and alogical.mjsconformance suite was added. Before thatpx-2did not overridepe-11, which every theme here depends on since they are written with logical properties throughout.Output is the same. 200,000 class chains joined from real theme fragments plus typical
:uioverrides: 0.34% differ, all of thembg-gradient-to-*against a laterbg-<color>, wherecnkeeps the gradient andtailwind-mergedrops it.cnis right there, and #7007 already moved those themes tobg-linear-*, so the suite has zero snapshot churn.app.config.ui.tv.mergeConfigis replaced byprefixandcacheSize, the two options that have an equivalent, tested under a mocked app config since the suite never sets them. The engine builds its merger fromcn/engineandcn/tables, which the default path already pulls in, so the prefix we set fromtheme.prefixcosts nothing extra.Extending class groups has no equivalent and is dropped. That is what keeps
cn/config, the 8 KB compiler, out of the picture: no conditional import, no generated stub, no restart caveat.TWMergeConfiggoes with it. It was undocumented and the only built-in use was the prefix, so I would rather wait for someone to ask for it than carry the machinery on spec.Measured:
cnlands in one chunk, not duplicated. Bundled on their own with only the merge path used,cnis smaller minified (24.0 KB vs 27.4) and bigger gzipped (9.8 KB vs 8.5):tailwind-mergesheds about half its dist when tree shaken,cn's tables are data and shed a quarter, and they compress 2.4x against 3.2x.cnis 2.5x faster on cold merges, but the slot memo sits above the merger so those are rare in a render.Two more paths measured since, both closed:
cn's headline shape is slower for us than what we already do. Its 30x row iscn(base, variant, cond && extra)with stable arguments, won by an argument-identity cache. On Button's real compiled parts: pre-joined string 33 ns withtailwind-mergeand 19 ns withcn, stable variadic args 390 ns and 148 ns. Pre-joining withcxand handing over one string beats feeding that cache by 8x, so restructuring the engine for it would cost, not save.cnwins.tailwind-mergeis 1.45 ms to import plus 2.58 ms on first merge;cnis 3.20 ms to import plus 0.40 ms. About 0.4 ms once per process, against a 116 ms cold Button mount.In a real render the merger is 0.66% of a cold Button toolbar (2 calls) and 0.09% of a cold 200x5 Table (9 calls). Warm renders call it zero times, since the slot memo sits above it.
So the ledger is 0.4 ms once, 14 ns per merge on a handful of merges, and 734 bytes. Keeping this open in case
cngets smaller after bundling, which is the whole of that 734 B. The deprecated-class andui.tvrenaming pieces landed separately in #7007.📝 Checklist