Skip to content

Commit 6462f37

Browse files
committed
partition nodes as soon as possible
Time to write another story on `@apply`... When we write code like this: ```css .a { @apply b; } .b { @apply uppercase; color: red; } ``` The we create 2 Nodes in our context to keep track of. One has identifier `a`, the other has identifier `b`. However, when we have an `@apply` and it contains multiple declarations/atrules, then we have to split up the (aka partition) the node into multiple nodes so that we can guarantee the correct expected sort order. This means that the above example technically looks like this: ```css .a { @apply b; } .b { @apply uppercase; } .b { color: red; } ``` If this was your input, then we would still have 1 node for identifier 'a', but we woudl have 2 nodes for identifier 'b'. As mentioned earlier, this is important to guarantee the correct order, here is an example: ```css .b { @apply md:font-bold xl:font-normal; /* Here we can sort by our internal rules. This means that the `md` comes before `xl`. */ } ``` ... however ```css .b { @apply xl:font-normal; /* This now exists _before_ the example below */ } .b { @apply md:font-bold; /* Because we respect the order of the user's css */ } ``` So to guarantee the order when doing this: ```css .b { @apply xl:font-normal; @apply lg:font-normal; } ``` We also split this up into 2 nodes like this: ```css .b { @apply xl:font-normal; } .b { @apply lg:font-normal; } ``` The tricky part is that now only 1 empty `.b` node exists in our context because we split the orginal up into multiple nodes and they are new nodes which means that they have a different identity. This partitioning used to happen in the expandApplyAtRules code, but this is a bit too late because the context has already been filled at this time. Instead, we move the code to the front, as if you wrote those separated blocks yourself. Now the code to inject those nodes into the context happens in a single, consistent spot. Another good part about this is that we have better consistency between each layer because it turns out that these two examples generate different results... ```css .a { @apply b; } .b { @apply uppercase; color: red; } ``` ... is different compared to: ```css @tailwind components; @layer components { .a { @apply b; } .b { @apply uppercase; color: red; } } ``` Even if both `a` and `b` are being used in one of your content paths... Yeah.. *sigh*
1 parent 9c72add commit 6462f37

File tree

3 files changed

+76
-51
lines changed

3 files changed

+76
-51
lines changed

src/lib/expandApplyAtRules.js

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -72,47 +72,6 @@ function extractApplyCandidates(params) {
7272
return [candidates, false]
7373
}
7474

75-
function partitionApplyParents(root) {
76-
let applyParents = new Set()
77-
78-
root.walkAtRules('apply', (rule) => {
79-
applyParents.add(rule.parent)
80-
})
81-
82-
for (let rule of applyParents) {
83-
let nodeGroups = []
84-
let lastGroup = []
85-
86-
for (let node of rule.nodes) {
87-
if (node.type === 'atrule' && node.name === 'apply') {
88-
if (lastGroup.length > 0) {
89-
nodeGroups.push(lastGroup)
90-
lastGroup = []
91-
}
92-
nodeGroups.push([node])
93-
} else {
94-
lastGroup.push(node)
95-
}
96-
}
97-
98-
if (lastGroup.length > 0) {
99-
nodeGroups.push(lastGroup)
100-
}
101-
102-
if (nodeGroups.length === 1) {
103-
continue
104-
}
105-
106-
for (let group of [...nodeGroups].reverse()) {
107-
let newParent = rule.clone({ nodes: [] })
108-
newParent.append(group)
109-
rule.after(newParent)
110-
}
111-
112-
rule.remove()
113-
}
114-
}
115-
11675
function processApply(root, context) {
11776
let applyCandidates = new Set()
11877

@@ -343,7 +302,6 @@ function processApply(root, context) {
343302

344303
export default function expandApplyAtRules(context) {
345304
return (root) => {
346-
partitionApplyParents(root)
347305
processApply(root, context)
348306
}
349307
}

src/lib/setupContextUtils.js

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,58 @@ import log from '../util/log'
2020
import negateValue from '../util/negateValue'
2121
import isValidArbitraryValue from '../util/isValidArbitraryValue'
2222

23+
function partitionRules(root) {
24+
if (!root.walkAtRules) return [root]
25+
26+
let applyParents = new Set()
27+
let rules = []
28+
29+
root.walkAtRules('apply', (rule) => {
30+
applyParents.add(rule.parent)
31+
})
32+
33+
if (applyParents.size === 0) {
34+
rules.push(root)
35+
}
36+
37+
for (let rule of applyParents) {
38+
let nodeGroups = []
39+
let lastGroup = []
40+
41+
for (let node of rule.nodes) {
42+
if (node.type === 'atrule' && node.name === 'apply') {
43+
if (lastGroup.length > 0) {
44+
nodeGroups.push(lastGroup)
45+
lastGroup = []
46+
}
47+
nodeGroups.push([node])
48+
} else {
49+
lastGroup.push(node)
50+
}
51+
}
52+
53+
if (lastGroup.length > 0) {
54+
nodeGroups.push(lastGroup)
55+
}
56+
57+
if (nodeGroups.length === 1) {
58+
rules.push(rule)
59+
continue
60+
}
61+
62+
for (let group of [...nodeGroups].reverse()) {
63+
let clone = rule.clone({ nodes: [] })
64+
clone.append(group)
65+
rules.unshift(clone)
66+
rule.after(clone)
67+
}
68+
69+
rule.remove()
70+
}
71+
72+
return rules
73+
}
74+
2375
function parseVariantFormatString(input) {
2476
if (input.includes('{')) {
2577
if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`)
@@ -232,7 +284,9 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
232284
context.candidateRuleMap.set(identifier, [])
233285
}
234286

235-
context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
287+
context.candidateRuleMap
288+
.get(identifier)
289+
.push(...partitionRules(rule).map((rule) => [{ sort: offset, layer: 'user' }, rule]))
236290
}
237291
},
238292
addBase(base) {
@@ -246,7 +300,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
246300

247301
context.candidateRuleMap
248302
.get(prefixedIdentifier)
249-
.push([{ sort: offset, layer: 'base' }, rule])
303+
.push(...partitionRules(rule).map((rule) => [{ sort: offset, layer: 'base' }, rule]))
250304
}
251305
},
252306
/**
@@ -260,15 +314,19 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
260314

261315
for (let [identifier, rule] of withIdentifiers(groups)) {
262316
let prefixedIdentifier = prefixIdentifier(identifier, {})
263-
let offset = offsets.base++
264317

265318
if (!context.candidateRuleMap.has(prefixedIdentifier)) {
266319
context.candidateRuleMap.set(prefixedIdentifier, [])
267320
}
268321

269322
context.candidateRuleMap
270323
.get(prefixedIdentifier)
271-
.push([{ sort: offset, layer: 'defaults' }, rule])
324+
.push(
325+
...partitionRules(rule).map((rule) => [
326+
{ sort: offsets.base++, layer: 'defaults' },
327+
rule,
328+
])
329+
)
272330
}
273331
},
274332
addComponents(components, options) {
@@ -281,7 +339,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
281339

282340
for (let [identifier, rule] of withIdentifiers(components)) {
283341
let prefixedIdentifier = prefixIdentifier(identifier, options)
284-
let offset = offsets.components++
285342

286343
classList.add(prefixedIdentifier)
287344

@@ -291,7 +348,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
291348

292349
context.candidateRuleMap
293350
.get(prefixedIdentifier)
294-
.push([{ sort: offset, layer: 'components', options }, rule])
351+
.push(
352+
...partitionRules(rule).map((rule) => [
353+
{ sort: offsets.components++, layer: 'components', options },
354+
rule,
355+
])
356+
)
295357
}
296358
},
297359
addUtilities(utilities, options) {
@@ -304,7 +366,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
304366

305367
for (let [identifier, rule] of withIdentifiers(utilities)) {
306368
let prefixedIdentifier = prefixIdentifier(identifier, options)
307-
let offset = offsets.utilities++
308369

309370
classList.add(prefixedIdentifier)
310371

@@ -314,7 +375,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
314375

315376
context.candidateRuleMap
316377
.get(prefixedIdentifier)
317-
.push([{ sort: offset, layer: 'utilities', options }, rule])
378+
.push(
379+
...partitionRules(rule).map((rule) => [
380+
{ sort: offsets.utilities++, layer: 'utilities', options },
381+
rule,
382+
])
383+
)
318384
}
319385
},
320386
matchUtilities: function (utilities, options) {

src/processTailwindFeatures.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export default function processTailwindFeatures(setupContext) {
1414
return function (root, result) {
1515
let { tailwindDirectives, applyDirectives } = normalizeTailwindDirectives(root)
1616

17+
detectNesting()(root, result)
18+
1719
let context = setupContext({
1820
tailwindDirectives,
1921
applyDirectives,
@@ -37,7 +39,6 @@ export default function processTailwindFeatures(setupContext) {
3739

3840
issueFlagNotices(context.tailwindConfig)
3941

40-
detectNesting(context)(root, result)
4142
expandTailwindAtRules(context)(root, result)
4243
expandApplyAtRules(context)(root, result)
4344
evaluateTailwindFunctions(context)(root, result)

0 commit comments

Comments
 (0)