Skip to content

Commit

Permalink
partition nodes as soon as possible
Browse files Browse the repository at this point in the history
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*
  • Loading branch information
RobinMalfait committed Jan 6, 2022
1 parent 9c72add commit 6462f37
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 51 deletions.
42 changes: 0 additions & 42 deletions src/lib/expandApplyAtRules.js
Expand Up @@ -72,47 +72,6 @@ function extractApplyCandidates(params) {
return [candidates, false]
}

function partitionApplyParents(root) {
let applyParents = new Set()

root.walkAtRules('apply', (rule) => {
applyParents.add(rule.parent)
})

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 newParent = rule.clone({ nodes: [] })
newParent.append(group)
rule.after(newParent)
}

rule.remove()
}
}

function processApply(root, context) {
let applyCandidates = new Set()

Expand Down Expand Up @@ -343,7 +302,6 @@ function processApply(root, context) {

export default function expandApplyAtRules(context) {
return (root) => {
partitionApplyParents(root)
processApply(root, context)
}
}
82 changes: 74 additions & 8 deletions src/lib/setupContextUtils.js
Expand Up @@ -20,6 +20,58 @@ 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.`)
Expand Down Expand Up @@ -232,7 +284,9 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
context.candidateRuleMap.set(identifier, [])
}

context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
context.candidateRuleMap
.get(identifier)
.push(...partitionRules(rule).map((rule) => [{ sort: offset, layer: 'user' }, rule]))
}
},
addBase(base) {
Expand All @@ -246,7 +300,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs

context.candidateRuleMap
.get(prefixedIdentifier)
.push([{ sort: offset, layer: 'base' }, rule])
.push(...partitionRules(rule).map((rule) => [{ sort: offset, layer: 'base' }, rule]))
}
},
/**
Expand All @@ -260,15 +314,19 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs

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

if (!context.candidateRuleMap.has(prefixedIdentifier)) {
context.candidateRuleMap.set(prefixedIdentifier, [])
}

context.candidateRuleMap
.get(prefixedIdentifier)
.push([{ sort: offset, layer: 'defaults' }, rule])
.push(
...partitionRules(rule).map((rule) => [
{ sort: offsets.base++, layer: 'defaults' },
rule,
])
)
}
},
addComponents(components, options) {
Expand All @@ -281,7 +339,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs

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

classList.add(prefixedIdentifier)

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

context.candidateRuleMap
.get(prefixedIdentifier)
.push([{ sort: offset, layer: 'components', options }, rule])
.push(
...partitionRules(rule).map((rule) => [
{ sort: offsets.components++, layer: 'components', options },
rule,
])
)
}
},
addUtilities(utilities, options) {
Expand All @@ -304,7 +366,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs

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

classList.add(prefixedIdentifier)

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

context.candidateRuleMap
.get(prefixedIdentifier)
.push([{ sort: offset, layer: 'utilities', options }, rule])
.push(
...partitionRules(rule).map((rule) => [
{ sort: offsets.utilities++, layer: 'utilities', options },
rule,
])
)
}
},
matchUtilities: function (utilities, options) {
Expand Down
3 changes: 2 additions & 1 deletion src/processTailwindFeatures.js
Expand Up @@ -14,6 +14,8 @@ export default function processTailwindFeatures(setupContext) {
return function (root, result) {
let { tailwindDirectives, applyDirectives } = normalizeTailwindDirectives(root)

detectNesting()(root, result)

let context = setupContext({
tailwindDirectives,
applyDirectives,
Expand All @@ -37,7 +39,6 @@ export default function processTailwindFeatures(setupContext) {

issueFlagNotices(context.tailwindConfig)

detectNesting(context)(root, result)
expandTailwindAtRules(context)(root, result)
expandApplyAtRules(context)(root, result)
evaluateTailwindFunctions(context)(root, result)
Expand Down

0 comments on commit 6462f37

Please sign in to comment.