diff --git a/CHANGELOG.md b/CHANGELOG.md index ada77a7d0662..88c7aa7d6368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/lib/partitionApplyAtRules.js b/src/lib/partitionApplyAtRules.js new file mode 100644 index 000000000000..34813c695799 --- /dev/null +++ b/src/lib/partitionApplyAtRules.js @@ -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) + } +} diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index c979c8df7fa0..cb7c6f71505e 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -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) { diff --git a/src/processTailwindFeatures.js b/src/processTailwindFeatures.js index d9bce8cc20a1..ac535bc8d593 100644 --- a/src/processTailwindFeatures.js +++ b/src/processTailwindFeatures.js @@ -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, diff --git a/tests/apply.test.js b/tests/apply.test.js index 00a492adc894..30b047ac85f1 100644 --- a/tests/apply.test.js +++ b/tests/apply.test.js @@ -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`
` }], + 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`
` }], + 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; + } + `) +})