Repository navigation
Conversation
`minify_style_arm` copies `sty.selectors` into the rules that a declaration adds after its style rule: `@supports` rules for color fallbacks, `:dir()` rules for logical properties, and the dark `@media` rule. Two earlier steps can leave that list empty. - The partition moves every selector out when none is compatible with the targets. The `@supports` rules of the original rule were still printed, with no selector. - A selector merge moves the selectors into the previous rule. The `@supports` rules were printed with no selector, and the logical rules were dropped. Snapshot the selectors before the merge when the declarations staged such rules. Build no added rules for a rule that the partition left without a selector.
…path The logical test now also uses `inset-inline`, which a second property handler stages. A new case has a rule whose compatible selector merges into the previous rule while its incompatible selector gets a rule of its own. Both selectors must keep the same declarations.
StatusReproduced on 1.4.3-canary.1+b52d51348 and on a debug build of main at fd8422c. cat > in.css <<'CSS'
.x:where(a), .y:is(a, b) { box-shadow: 2s lch(83% 47 20) }
CSS
bun build in.css --outdir out && bun build out/in.css --outdir out2
# error: Invalid selector. Empty selector is not allowedThe merge path needs no second build to see. Tests: PR: #43240 |
|
Updated 12:02 AM PT - Sep 18th, 2026
✅ @robobun, your commit d8c070960f365ebd996977c74cdc3f50156ccbaa passed in 🧪 To try this PR locally: bunx bun-pr 43240That installs a local version of the PR into your bun-43240 --bun |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Essentials Run ID: 📒 Files selected for processing (3)
Included review availability: Your plan provides up to 10 included reviews per hour; 4 remain after this review. WalkthroughThe CSS minifier now detects staged handler-context rules, preserves selectors during rule merging, and avoids generating extras for selectorless styles. Tests cover selector fallbacks, logical rules, selector splitting, and repeated builds. ChangesCSS staged-rule generation
Suggested reviewers: Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to The CSS minification fix has no identified remaining merge-blocking risk. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
Problem
Bun.buildover.x:where(a), .y:is(a, b) { box-shadow: 2s lch(83% 47 20) }prints@supports (color: lab(0% 0 0)) { { box-shadow: ... } }, a style rule with no selector. Bun then fails on its own output:Invalid selector. Empty selector is not allowed.minify_style_arm(src/css/rules/mod.rs:774). It copiessty.selectorsinto the rules that a declaration adds. Two earlier steps can empty that list. The partition moves every selector out when the targets support none. A selector merge moves them into the previous rule..a{color:red} .b{color:red;border-start-start-radius:2px}builds to.a,.b{color:red}on the default targets.Fix
sty.selectorsbefore the merge when the declarations staged rules. Restore them after a merge. The existingincompatible_declssnapshot does the same for declarations.test/js/bun/css/css.test.ts, 6 new tests that all fail without the fix. Alsotest/js/bun/css/,test/bundler/css/,test/bundler/esbuild/css.test.ts.Background
lch()in an unparsed value stages@supportsfallbacks. A logical property stages:dir()rules when a target lacks it.PropertyHandlerContextholds them forminify_style_arm.merge_style_rulesappends a rule's selectors to the previous rule when their declarations are equal.Notes
:is()and:where()need Chrome 88, andborder-start-start-radiusneeds Chrome 89, so plain CSS reaches both paths with no configuration.@supportsrule..x:where(a), .y:where(b)is enough, in either order. Logical and dark-mode rules for the empty rule were already dropped, because those rules pass throughCssRuleList::minifyand the@supportsrules do not.@supportsfallback:.a { box-shadow: 2s #ffbdbf } .b { box-shadow: 2s lch(83% 47 20) }printed.a,.b{...}plus selector-less@supportsrules, so the.bfallbacks were lost. It now prints@supports (...) { .b { ... } }.inset-inline,inset-inline-startandinset-inline-endvanished. I ran all seven on the fixed build. Each prints.a,.b{color:red}plus the same:dir()rules that.bprints when it stands alone..a { color: red } .b, .y:focus-visible { ... }on Safari 14 is the test for it..a{color:red} .b{color:red;border-start-start-radius:2px} .c{color:red}printed.a,.b,.c{color:red}. It now prints.a,.b{color:red}, the.brules, then.c{color:red}. Source order is kept and nothing is lost. An unmerged.bhas always ended the run in the same way. The@supportscase does not change, because the rules without a selector already ended the run.:full-screento:fullscreen, or from a bare::file-upload-buttonto::file-selector-button. The bare spelling is not valid CSS, so the change is a repair..c0:fullscreen { & .c1, & .c2 { box-shadow: 2s lch(50% 40 30 / .5) } }shows it.StyleRule::to_css(src/css/rules/style.rs:155) setsdest.vendor_prefixto empty after a rule that has its own vendor prefix, and does not restore the value of the enclosing pass. Later nested siblings then print the parent's downleveled pseudo with no pass prefix, which gives the bare legacy name.@supportsfallback hasvendor_prefix: NONE, so it triggers that reset. The rules without a selector stood in front of the first split-out rule and reset the prefix before it. They are gone now, so that rule prints correctly. The second split-out rule still prints the bare name on every build, because the valid@supportsrules of the first one reset the prefix in the same way..c0:fullscreen { & .a:any-link { color: red } & .b { color: blue } }prints.c0:full-screen .b. With the two nested rules swapped it prints.c0:fullscreen .b. Bun 1.4.2 and this branch print the same.@supportsrules where rules without a selector, with the samevendor_prefix, already stood. Its:dir()rules carry a prefix only when the targets lack:is(), and then nested siblings do not merge. This is reasoned and checked with three inputs, not proven.@supportsblock is generated again on each pass. Bun 1.4.2 does the same.merge_style_rulesstage a rule for a rule that staged none itself. If one exists, this change prints no rule where the old code printed a selector-less one.bun bd test test/js/bun/css/shows timeouts incss-fuzz.test.ts,nested-selector-expansion.test.tsandnested-vendor-prefix-duplication.test.ts. They are the same on an unfixed debug build in the same container.minify_style_arm. None of them changes where the added rules get their selectors.[human-review] gate passed · iteration 0 · 3 files touched
fails on main (without fix)
passes on PR (with fix)
diff hotspot
gate history · 1 passed · 0 rejected · iteration 0
evidence per changed file