diff --git a/CHANGELOG.md b/CHANGELOG.md index 114412e42d3c..45e54de0830b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103)) - Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120)) - Discard invalid declarations when parsing CSS ([#16093](https://github.com/tailwindlabs/tailwindcss/pull/16093)) +- Do not emit empty CSS rules and at-rules ([#16121](https://github.com/tailwindlabs/tailwindcss/pull/16121)) ## [4.0.1] - 2025-01-29 diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 2e6b9ad03549..0e3aba4c4e88 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -368,12 +368,7 @@ describe.each([ `, ) - await fs.expectFileToContain('project-a/dist/out.css', [ - css` - :root, :host { - } - `, - ]) + await fs.expectFileToContain('project-a/dist/out.css', [css``]) }, ) diff --git a/packages/tailwindcss/src/ast.test.ts b/packages/tailwindcss/src/ast.test.ts index c3d9b2f159bf..174971ea541d 100644 --- a/packages/tailwindcss/src/ast.test.ts +++ b/packages/tailwindcss/src/ast.test.ts @@ -2,6 +2,8 @@ import { expect, it } from 'vitest' import { context, decl, optimizeAst, styleRule, toCss, walk, WalkAction } from './ast' import * as CSS from './css-parser' +const css = String.raw + it('should pretty print an AST', () => { expect(toCss(optimizeAst(CSS.parse('.foo{color:red;&:hover{color:blue;}}')))) .toMatchInlineSnapshot(` @@ -95,3 +97,91 @@ it('should stop walking when returning `WalkAction.Stop`', () => { } `) }) + +it('should not emit empty rules once optimized', () => { + let ast = CSS.parse(css` + /* Empty rule */ + .foo { + } + + /* Empty rule, with nesting */ + .foo { + .bar { + } + .baz { + } + } + + /* Empty rule, with special case '&' rules */ + .foo { + & { + &:hover { + } + &:focus { + } + } + } + + /* Empty at-rule */ + @media (min-width: 768px) { + } + + /* Empty at-rule with nesting*/ + @media (min-width: 768px) { + .foo { + } + + @media (min-width: 1024px) { + .bar { + } + } + } + + /* Exceptions: */ + @charset "UTF-8"; + @layer foo, bar, baz; + @custom-media --modern (color), (hover); + @namespace 'http://www.w3.org/1999/xhtml'; + `) + + expect(toCss(ast)).toMatchInlineSnapshot(` + ".foo { + } + .foo { + .bar { + } + .baz { + } + } + .foo { + & { + &:hover { + } + &:focus { + } + } + } + @media (min-width: 768px); + @media (min-width: 768px) { + .foo { + } + @media (min-width: 1024px) { + .bar { + } + } + } + @charset "UTF-8"; + @layer foo, bar, baz; + @custom-media --modern (color), (hover); + @namespace 'http://www.w3.org/1999/xhtml'; + " + `) + + expect(toCss(optimizeAst(ast))).toMatchInlineSnapshot(` + "@charset "UTF-8"; + @layer foo, bar, baz; + @custom-media --modern (color), (hover); + @namespace 'http://www.w3.org/1999/xhtml'; + " + `) +}) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 6052724f33b9..7f67fdda5f57 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -261,7 +261,9 @@ export function optimizeAst(ast: AstNode[]) { for (let child of node.nodes) { let nodes: AstNode[] = [] transform(child, nodes, depth + 1) - parent.push(...nodes) + if (nodes.length > 0) { + parent.push(...nodes) + } } } @@ -271,7 +273,9 @@ export function optimizeAst(ast: AstNode[]) { for (let child of node.nodes) { transform(child, copy.nodes, depth + 1) } - parent.push(copy) + if (copy.nodes.length > 0) { + parent.push(copy) + } } } @@ -297,7 +301,15 @@ export function optimizeAst(ast: AstNode[]) { for (let child of node.nodes) { transform(child, copy.nodes, depth + 1) } - parent.push(copy) + if ( + copy.nodes.length > 0 || + copy.name === '@layer' || + copy.name === '@charset' || + copy.name === '@custom-media' || + copy.name === '@namespace' + ) { + parent.push(copy) + } } // AtRoot diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts index 4502118f458f..9eb65ccb954a 100644 --- a/packages/tailwindcss/src/compat/plugin-api.test.ts +++ b/packages/tailwindcss/src/compat/plugin-api.test.ts @@ -3167,8 +3167,6 @@ describe('addUtilities()', () => { color: red; } } - } - :root, :host { }" `, ) diff --git a/packages/tailwindcss/src/compat/screens-config.test.ts b/packages/tailwindcss/src/compat/screens-config.test.ts index 09ab97fc9f8a..e8f96ee55fd3 100644 --- a/packages/tailwindcss/src/compat/screens-config.test.ts +++ b/packages/tailwindcss/src/compat/screens-config.test.ts @@ -655,9 +655,5 @@ test('JS config `screens` can overwrite default CSS `--breakpoint-*`', async () // currently. expect( compiler.build(['min-sm:flex', 'min-md:flex', 'min-lg:flex', 'min-xl:flex', 'min-2xl:flex']), - ).toMatchInlineSnapshot(` - ":root, :host { - } - " - `) + ).toMatchInlineSnapshot(`""`) })