diff --git a/internal/bundler/linker.go b/internal/bundler/linker.go index 5de3efe4872..bac6ed39dd2 100644 --- a/internal/bundler/linker.go +++ b/internal/bundler/linker.go @@ -4088,6 +4088,7 @@ func (repr *chunkReprCSS) generate(c *linkerContext, chunk *chunkInfo) func(gene ast.Rules = rules compileResult.printedCSS = css_printer.Print(ast, css_printer.Options{ + MangleSyntax: c.options.MangleSyntax, RemoveWhitespace: c.options.RemoveWhitespace, ASCIIOnly: c.options.ASCIIOnly, }) diff --git a/internal/css_printer/css_printer.go b/internal/css_printer/css_printer.go index 1e5588b4286..39cbf0129a2 100644 --- a/internal/css_printer/css_printer.go +++ b/internal/css_printer/css_printer.go @@ -19,6 +19,7 @@ type printer struct { } type Options struct { + MangleSyntax bool RemoveWhitespace bool ASCIIOnly bool } @@ -89,6 +90,10 @@ func (p *printer) printRule(rule css_ast.R, indent int, omitTrailingSemicolon bo } indent++ for _, block := range r.Blocks { + if p.MangleSyntax && len(block.Rules) == 0 { + continue + } + if !p.RemoveWhitespace { p.printIndent(indent) } @@ -117,6 +122,9 @@ func (p *printer) printRule(rule css_ast.R, indent int, omitTrailingSemicolon bo p.print("}") case *css_ast.RKnownAt: + if p.MangleSyntax && len(r.Rules) == 0 { + return + } p.print("@") whitespace := mayNeedWhitespaceAfter if len(r.Prelude) == 0 { @@ -153,6 +161,10 @@ func (p *printer) printRule(rule css_ast.R, indent int, omitTrailingSemicolon bo } case *css_ast.RSelector: + if p.MangleSyntax && len(r.Rules) == 0 { + return + } + p.printComplexSelectors(r.Selectors, indent) if !p.RemoveWhitespace { p.print(" ") @@ -160,6 +172,10 @@ func (p *printer) printRule(rule css_ast.R, indent int, omitTrailingSemicolon bo p.printRuleBlock(r.Rules, indent) case *css_ast.RQualified: + if p.MangleSyntax && len(r.Rules) == 0 { + return + } + hasWhitespaceAfter := p.printTokens(r.Prelude) if !hasWhitespaceAfter && !p.RemoveWhitespace { p.print(" ") diff --git a/internal/css_printer/css_printer_test.go b/internal/css_printer/css_printer_test.go index c917a807611..40e12147d71 100644 --- a/internal/css_printer/css_printer_test.go +++ b/internal/css_printer/css_printer_test.go @@ -48,6 +48,13 @@ func expectPrintedMinify(t *testing.T, contents string, expected string) { }) } +func expectPrinteMangleSyntax(t *testing.T, contents string, expected string) { + t.Helper() + expectPrintedCommon(t, contents+" [minified]", contents, expected, Options{ + MangleSyntax: true, + }) +} + func expectPrintedASCII(t *testing.T, contents string, expected string) { t.Helper() expectPrintedCommon(t, contents+" [ascii]", contents, expected, Options{ @@ -399,3 +406,19 @@ func TestASCII(t *testing.T) { // This character should always be escaped expectPrinted(t, ".\\FEFF:after { content: '\uFEFF' }", ".\\feff:after {\n content: \"\\feff\";\n}\n") } + +func TestEmptyRule(t *testing.T) { + expectPrinted(t, "div {}", "div {\n}\n") + expectPrinted(t, ".a {}", ".a {\n}\n") + expectPrinted(t, ".a {}.b { color: red;}", ".a {\n}\n.b {\n color: red;\n}\n") + expectPrinted(t, "@media (prefers-reduced-motion: no-preference) {\n}", "@media (prefers-reduced-motion: no-preference) {\n}\n") + expectPrinted(t, "@keyframes test {\n from {}\n to {\n color: red;\n}\n}", "@keyframes test {\n from {\n }\n to {\n color: red;\n }\n}\n") + + expectPrintedMinify(t, "p:hover {}", "p:hover{}") + expectPrintedMinify(t, ".a {}\n.b {\n color: red;\n}", ".a{}.b{color:red}") + expectPrintedMinify(t, "@keyframes mini {\n from {}\n to {\n color: red;\n}\n}", "@keyframes mini{from{}to{color:red}}") + + expectPrinteMangleSyntax(t, "a:hover {}", "") + expectPrinteMangleSyntax(t, ".a {}\n.b {\n color: red;\n}", ".b {\n color: red;\n}\n") + expectPrinteMangleSyntax(t, "@keyframes test {\n from {}\n to {\n color: red;\n}\n}", "@keyframes test {\n to {\n color: red;\n }\n}\n") +}