v3: Remove irrelevant utility rules when matching important classes (#19030)

Fixes #18678

When given a simple plugin like this:
```js
export default {
  content: [{ raw: '!a' },],
  plugins: [
    ({ addBase }) => addBase({
      '@media (min-width: 1728px)': {
        '.a': { 'padding-top': '1rem !important' },
        '.b': { 'padding-right': '1rem !important' },
      }
    }),
  ],
}
```

If this plugin saw the utility `!a` it would correctly modify both rules
to eliminate any irrelevant classes. Unfortunately, in the case of the
2nd rule this meant it's left with an empty selector which is invalid.

We now detect this case and remove the rule if that happens.
This commit is contained in:
Jordan Pittman 2025-09-29 18:11:09 -04:00 • committed by GitHub
parent ba55a445cd
commit aecc8521dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 158 additions and 1 deletions

View file

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix `require.cache` error when loaded through a TypeScript file in Node 22.18+ ([#18665](https://github.com/tailwindlabs/tailwindcss/pull/18665))
- Support `import.meta.resolve(…)` in configs for new enough Node.js versions ([#18938](https://github.com/tailwindlabs/tailwindcss/pull/18938))
- Allow using newer versions of `postcss-load-config` for better ESM and TypeScript PostCSS config support with the CLI ([#18938](https://github.com/tailwindlabs/tailwindcss/pull/18938))
- Remove irrelevant utility rules when matching important classes ([#19030](https://github.com/tailwindlabs/tailwindcss/pull/19030))
## [3.4.17] - 2024-12-17

View file

@ -143,7 +143,14 @@ function applyImportant(matches, classCandidate) {
className === classCandidate ? `!${className}` : className
)
r.selector = ast.toString()
let newSelector = ast.toString()
if (newSelector.trim() === '') {
r.remove()
return
}
r.selector = newSelector
r.walkDecls((d) => (d.important = true))
})

View file

@ -1914,3 +1914,152 @@ test('custom properties are not converted to kebab-case when added to base layer
expect(result.css).toContain(`--colors-primaryThing-500: 0, 0, 255;`)
})
})
test('important candidates discard rules with irrelevant selectors', async () => {
let config = {
content: [{ raw: '!a1 !b1 !c1' }],
corePlugins: { preflight: false },
plugins: [
({ addBase }) => {
addBase({
'@media (min-width: 1728px)': {
'.a1': { 'padding-top': '1rem !important' },
'.a2': { 'padding-right': '1rem !important' },
},
})
addBase({
'@media (min-width: 1728px)': { '.b1': { 'padding-top': '1rem !important' } },
'@media (min-width: 1729px)': { '.b2': { 'padding-right': '1rem !important' } },
})
addBase({
'.c1': { '@media (min-width: 1728px)': { 'padding-top': '1rem !important' } },
'.c2': { '@media (min-width: 1728px)': { 'padding-right': '1rem !important' } },
})
},
],
}
let result = await run('@tailwind base', config)
expect(result.css).toMatchInlineSnapshot(`
"*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style:
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style:
}
@media (min-width: 1728px) {
.\\!a1 {
padding-top: 1rem !important
}
.\\!b1 {
padding-top: 1rem !important
}
.\\!c1 {
padding-top: 1rem !important
}
}"
`)
})