Fix consecutive builds with at apply producing different CSS (#6999)

* Partition at rules before building context

* remove unnecessary logic

* Update changelog

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
Jordan Pittman 2022-01-10 12:36:14 -05:00 • committed by GitHub
parent 3a13cfce7b
commit a891867a2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 125 additions and 75 deletions

View file

@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
- Nothing yet!
### Fixed
- Fix consecutive builds with at apply producing different CSS ([#6999](https://github.com/tailwindlabs/tailwindcss/pull/6999))
## [3.0.12] - 2022-01-07

View file

@ -0,0 +1,52 @@
function partitionRules(root) {
if (!root.walkAtRules) return
let applyParents = new Set()
root.walkAtRules('apply', (rule) => {
applyParents.add(rule.parent)
})
if (applyParents.size === 0) {
return
}
for (let rule of applyParents) {
let nodeGroups = []
let lastGroup = []
for (let node of rule.nodes) {
if (node.type === 'atrule' && node.name === 'apply') {
if (lastGroup.length > 0) {
nodeGroups.push(lastGroup)
lastGroup = []
}
nodeGroups.push([node])
} else {
lastGroup.push(node)
}
}
if (lastGroup.length > 0) {
nodeGroups.push(lastGroup)
}
if (nodeGroups.length === 1) {
continue
}
for (let group of [...nodeGroups].reverse()) {
let clone = rule.clone({ nodes: [] })
clone.append(group)
rule.after(clone)
}
rule.remove()
}
}
export default function expandApplyAtRules() {
return (root) => {
partitionRules(root)
}
}

View file

@ -20,58 +20,6 @@ import log from '../util/log'
import negateValue from '../util/negateValue'
import isValidArbitraryValue from '../util/isValidArbitraryValue'
function partitionRules(root) {
if (!root.walkAtRules) return [root]
let applyParents = new Set()
let rules = []
root.walkAtRules('apply', (rule) => {
applyParents.add(rule.parent)
})
if (applyParents.size === 0) {
rules.push(root)
}
for (let rule of applyParents) {
let nodeGroups = []
let lastGroup = []
for (let node of rule.nodes) {
if (node.type === 'atrule' && node.name === 'apply') {
if (lastGroup.length > 0) {
nodeGroups.push(lastGroup)
lastGroup = []
}
nodeGroups.push([node])
} else {
lastGroup.push(node)
}
}
if (lastGroup.length > 0) {
nodeGroups.push(lastGroup)
}
if (nodeGroups.length === 1) {
rules.push(rule)
continue
}
for (let group of [...nodeGroups].reverse()) {
let clone = rule.clone({ nodes: [] })
clone.append(group)
rules.unshift(clone)
rule.after(clone)
}
rule.remove()
}
return rules
}
function parseVariantFormatString(input) {
if (input.includes('{')) {
if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`)
@ -284,9 +232,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
context.candidateRuleMap.set(identifier, [])
}
context.candidateRuleMap
.get(identifier)
.push(...partitionRules(rule).map((rule) => [{ sort: offset, layer: 'user' }, rule]))
context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
}
},
addBase(base) {
@ -300,7 +246,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
context.candidateRuleMap
.get(prefixedIdentifier)
.push(...partitionRules(rule).map((rule) => [{ sort: offset, layer: 'base' }, rule]))
.push([{ sort: offset, layer: 'base' }, rule])
}
},
/**
@ -321,12 +267,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
context.candidateRuleMap
.get(prefixedIdentifier)
.push(
...partitionRules(rule).map((rule) => [
{ sort: offsets.base++, layer: 'defaults' },
rule,
])
)
.push([{ sort: offsets.base++, layer: 'defaults' }, rule])
}
},
addComponents(components, options) {
@ -348,12 +289,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
context.candidateRuleMap
.get(prefixedIdentifier)
.push(
...partitionRules(rule).map((rule) => [
{ sort: offsets.components++, layer: 'components', options },
rule,
])
)
.push([{ sort: offsets.components++, layer: 'components', options }, rule])
}
},
addUtilities(utilities, options) {
@ -375,12 +311,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
context.candidateRuleMap
.get(prefixedIdentifier)
.push(
...partitionRules(rule).map((rule) => [
{ sort: offsets.utilities++, layer: 'utilities', options },
rule,
])
)
.push([{ sort: offsets.utilities++, layer: 'utilities', options }, rule])
}
},
matchUtilities: function (utilities, options) {

View file

@ -6,6 +6,7 @@ import substituteScreenAtRules from './lib/substituteScreenAtRules'
import resolveDefaultsAtRules from './lib/resolveDefaultsAtRules'
import collapseAdjacentRules from './lib/collapseAdjacentRules'
import collapseDuplicateDeclarations from './lib/collapseDuplicateDeclarations'
import partitionApplyAtRules from './lib/partitionApplyAtRules'
import detectNesting from './lib/detectNesting'
import { createContext } from './lib/setupContextUtils'
import { issueFlagNotices } from './featureFlags'
@ -15,6 +16,7 @@ export default function processTailwindFeatures(setupContext) {
let { tailwindDirectives, applyDirectives } = normalizeTailwindDirectives(root)
detectNesting()(root, result)
partitionApplyAtRules()(root, result)
let context = setupContext({
tailwindDirectives,

View file

@ -1139,3 +1139,66 @@ it('apply does not emit defaults in isolated environments without optimizeUniver
`)
})
})
it('should work outside of layer', async () => {
let config = {
content: [{ raw: html`<div class="input-text"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
.input-text {
@apply bg-white;
background-color: red;
}
`
let result
result = await run(input, config)
expect(result.css).toMatchFormattedCss(css`
.input-text {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
background-color: red;
}
`)
result = await run(input, config)
expect(result.css).toMatchFormattedCss(css`
.input-text {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
background-color: red;
}
`)
})
it('should work in layer', async () => {
let config = {
content: [{ raw: html`<div class="input-text"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind components;
@layer components {
.input-text {
@apply bg-white;
background-color: red;
}
}
`
await run(input, config)
const result = await run(input, config)
expect(result.css).toMatchFormattedCss(css`
.input-text {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
background-color: red;
}
`)
})