Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
32b7bfc3b9 fix(css): prevent style rule deduplication across non-style rule boundaries
The CSS minifier incorrectly removed a `:root` selector when it appeared
before an `@property` at-rule and another `:root` existed after it. The
deduplication logic merged the two `:root` rules without considering that
the intervening `@property` rule creates a semantic boundary that prevents
safe reordering.

Clear the style_rules deduplication map whenever a non-style rule (like
`@property`, `@keyframes`, etc.) is appended, preventing merges across
these boundaries.

Closes #27117

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-19 04:47:53 +00:00
2 changed files with 47 additions and 0 deletions

View File

@@ -452,6 +452,14 @@ pub fn CssRuleList(comptime AtRule: type) type {
}
bun.handleOom(rules.append(context.allocator, rule.*));
moved_rule = true;
// Non-style rules (e.g. @property, @keyframes) act as a barrier for
// style rule deduplication. We cannot safely merge identical style rules
// across such boundaries because the intervening at-rule may affect how
// the declarations are interpreted (e.g. @property defines a custom
// property that a :root rule above may set differently than one below).
style_rules.clearRetainingCapacity();
}
// MISSING SHIT HERE

View File

@@ -0,0 +1,39 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
test("CSS bundler should not drop :root rule before @property", async () => {
using dir = tempDir("css-property-root-dedup", {
"input.css": `:root {
--bar: 1;
}
@property --foo {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
:root {
--baz: 2;
}
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "build", "input.css", "--outdir", "out"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
const output = await Bun.file(`${dir}/out/input.css`).text();
// Both :root blocks must be preserved — they cannot be merged across the @property boundary
expect(output).toContain("--bar: 1");
expect(output).toContain("--baz: 2");
expect(output).toContain("@property --foo");
expect(exitCode).toBe(0);
});