Dedupe duplicate properties (#5830)

* add `collapseDuplicateDeclarations`

This will allow us to remove duplicate declarations. This occurs when
you are using `@apply` for example.

The reason I implemented it as a separate step, is because this doesn't
only happen for `@apply`, but it also happens if you do something like:

```js
addComponents({ '.btn-blue, .btm-red': { padding: '10px' } })
```

So instead of tracking down every place this is happening, it now
happens at the very end.

* use new plugin in processTailwindFeatures

* add/update tests by removing duplicate declarations

* update changelog
This commit is contained in:
Robin Malfait 2021-10-21 11:54:23 +02:00 committed by GitHub
parent 0c2f1a6472
commit ea6f14a499
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 58 additions and 8 deletions

View File

@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Configure chokidar's `awaitWriteFinish` setting to avoid occasional stale builds on Windows ([#5774](https://github.com/tailwindlabs/tailwindcss/pull/5774))
- Fix CLI `--content` option ([#5775](https://github.com/tailwindlabs/tailwindcss/pull/5775))
- Fix before/after utilities overriding custom content values at larger breakpoints ([#5820](https://github.com/tailwindlabs/tailwindcss/pull/5820))
- Cleanup duplicate properties ([#5830](https://github.com/tailwindlabs/tailwindcss/pull/5830))
## [3.0.0-alpha.1] - 2021-10-01

View File

@ -0,0 +1,28 @@
export default function collapseDuplicateDeclarations() {
return (root) => {
root.walkRules((node) => {
let seen = new Map()
let droppable = new Set([])
node.walkDecls((decl) => {
// This could happen if we have nested selectors. In that case the
// parent will loop over all its declarations but also the declarations
// of nested rules. With this we ensure that we are shallowly checking
// declarations.
if (decl.parent !== node) {
return
}
if (seen.has(decl.prop)) {
droppable.add(seen.get(decl.prop))
}
seen.set(decl.prop, decl)
})
for (let decl of droppable) {
decl.remove()
}
})
}
}

View File

@ -5,6 +5,7 @@ import evaluateTailwindFunctions from './lib/evaluateTailwindFunctions'
import substituteScreenAtRules from './lib/substituteScreenAtRules'
import resolveDefaultsAtRules from './lib/resolveDefaultsAtRules'
import collapseAdjacentRules from './lib/collapseAdjacentRules'
import collapseDuplicateDeclarations from './lib/collapseDuplicateDeclarations'
import detectNesting from './lib/detectNesting'
import { createContext } from './lib/setupContextUtils'
import { issueFlagNotices } from './featureFlags'
@ -42,5 +43,6 @@ export default function processTailwindFeatures(setupContext) {
substituteScreenAtRules(context)(root, result)
resolveDefaultsAtRules(context)(root, result)
collapseAdjacentRules(context)(root, result)
collapseDuplicateDeclarations(context)(root, result)
}
}

View File

@ -10,8 +10,6 @@
.class-order {
padding: 2rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
padding-top: 1.75rem;
padding-bottom: 1.75rem;
padding-top: 1rem;
padding-right: 0.25rem;
@ -127,7 +125,6 @@
/* TODO: This works but the generated CSS is unnecessarily verbose. */
.complex-utilities {
--tw-ordinal: ordinal;
font-variant-numeric: var(--tw-font-variant-numeric);
--tw-numeric-spacing: tabular-nums;
font-variant-numeric: var(--tw-font-variant-numeric);
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05);
@ -155,7 +152,6 @@
font-weight: 700;
}
.use-dependant-only-b {
font-weight: 700;
font-weight: 400;
}
.btn {

View File

@ -351,7 +351,6 @@ test('@applying classes from outside a @layer respects the source order', async
await run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.baz {
text-decoration: underline;
text-decoration: none;
}
@ -402,3 +401,30 @@ test('@applying classes from outside a @layer respects the source order', async
`)
})
})
it('should remove duplicate properties when using apply with similar properties', () => {
let config = {
content: [{ raw: 'foo' }],
}
let input = css`
@tailwind utilities;
.foo {
@apply absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.foo {
position: absolute;
top: 50%;
left: 50%;
--tw-translate-x: -50%;
--tw-translate-y: -50%;
transform: var(--tw-transform);
}
`)
})
})

View File

@ -868,7 +868,6 @@ test('when important is a selector it scopes all selectors in a rule, even thoug
#app .custom-rotate-90,
#app .custom-rotate-1\/4 {
transform: rotate(90deg);
transform: rotate(90deg);
}
`)
})
@ -952,7 +951,6 @@ test('all selectors in a rule are prefixed', () => {
.tw-btn-blue,
.tw-btn-red {
padding: 10px;
padding: 10px;
}
`)
})

View File

@ -199,7 +199,6 @@ div {
}
.test-apply-font-variant {
--tw-ordinal: ordinal;
font-variant-numeric: var(--tw-font-variant-numeric);
--tw-numeric-spacing: tabular-nums;
font-variant-numeric: var(--tw-font-variant-numeric);
}