Skip to content

Commit

Permalink
Fix parallel variant ordering clash (#9282)
Browse files Browse the repository at this point in the history
* Remove remnants of the user layer

It hasn’t been used in a while

* Rewrite sort offset generation

* wip

* wip

wip

* Handle parasite utilities

* wip

* wip

* Make parallel variants sorting more resillient

It’s not perfect but it’s close

* fix

* remove todo

it adds a new bit so it can’t

* Simplify getClassOrder usage

* Simplify

oops

oops

* Add parasite utility for `dark`

dark mode class name

* Cleanup

* Cleanup

* Simplify

* format files

* Fix prettier plugin to use git build of Tailwind CSS

Symlink and build instead of adding a recursive dev dependency

It breaks node < 16

* Fix prettier error

* wip

* fix test

* Update changelog

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
  • Loading branch information
thecrypticace and RobinMalfait committed Sep 9, 2022
1 parent 88e98f5 commit d6bec49
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 129 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/nodejs.yml
Expand Up @@ -41,6 +41,13 @@ jobs:
env:
CI: true

- name: Link and build Tailwind CSS
run: |
npm run swcify
ln -nfs `pwd` node_modules/tailwindcss
env:
CI: true

- name: Test
run: npm test
env:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix `fontFamily` config TypeScript types ([#9214](https://github.com/tailwindlabs/tailwindcss/pull/9214))
- Handle variants on complex selector utilities ([#9262](https://github.com/tailwindlabs/tailwindcss/pull/9262))
- Don't mutate shared config objects ([#9294](https://github.com/tailwindlabs/tailwindcss/pull/9294))
- Fix ordering of parallel variants ([#9282](https://github.com/tailwindlabs/tailwindcss/pull/9282))

## [3.1.8] - 2022-08-05

Expand Down
16 changes: 5 additions & 11 deletions src/lib/expandApplyAtRules.js
Expand Up @@ -2,7 +2,6 @@ import postcss from 'postcss'
import parser from 'postcss-selector-parser'

import { resolveMatches } from './generateRules'
import bigSign from '../util/bigSign'
import escapeClassName from '../util/escapeClassName'

/** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
Expand Down Expand Up @@ -149,9 +148,7 @@ function buildLocalApplyCache(root, context) {
/** @type {ApplyCache} */
let cache = new Map()

let highestOffset = context.layerOrder.user >> 4n

root.walkRules((rule, idx) => {
root.walkRules((rule) => {
// Ignore rules generated by Tailwind
for (let node of pathToRoot(rule)) {
if (node.raws.tailwind?.layer !== undefined) {
Expand All @@ -161,6 +158,7 @@ function buildLocalApplyCache(root, context) {

// Clone what's required to represent this singular rule in the tree
let container = nestedClone(rule)
let sort = context.offsets.create('user')

for (let className of extractClasses(rule)) {
let list = cache.get(className) || []
Expand All @@ -169,7 +167,7 @@ function buildLocalApplyCache(root, context) {
list.push([
{
layer: 'user',
sort: BigInt(idx) + highestOffset,
sort,
important: false,
},
container,
Expand Down Expand Up @@ -553,16 +551,12 @@ function processApply(root, context, localCache) {
}

// Insert it
siblings.push([
// Ensure that when we are sorting, that we take the layer order into account
{ ...meta, sort: meta.sort | context.layerOrder[meta.layer] },
root.nodes[0],
])
siblings.push([meta.sort, root.nodes[0]])
}
}

// Inject the rules, sorted, correctly
let nodes = siblings.sort(([a], [z]) => bigSign(a.sort - z.sort)).map((s) => s[1])
let nodes = context.offsets.sort(siblings).map((s) => s[1])

// `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
parent.after(nodes)
Expand Down
48 changes: 7 additions & 41 deletions src/lib/expandTailwindAtRules.js
@@ -1,7 +1,6 @@
import LRU from 'quick-lru'
import * as sharedState from './sharedState'
import { generateRules } from './generateRules'
import bigSign from '../util/bigSign'
import log from '../util/log'
import cloneNodes from '../util/cloneNodes'
import { defaultExtractor } from './defaultExtractor'
Expand Down Expand Up @@ -74,57 +73,24 @@ function getClassCandidates(content, extractor, candidates, seen) {
}
}

/**
*
* @param {[import('./offsets.js').RuleOffset, import('postcss').Node][]} rules
* @param {*} context
*/
function buildStylesheet(rules, context) {
let sortedRules = rules.sort(([a], [z]) => bigSign(a - z))
let sortedRules = context.offsets.sort(rules)

let returnValue = {
base: new Set(),
defaults: new Set(),
components: new Set(),
utilities: new Set(),
variants: new Set(),

// All the CSS that is not Tailwind related can be put in this bucket. This
// will make it easier to later use this information when we want to
// `@apply` for example. The main reason we do this here is because we
// still need to make sure the order is correct. Last but not least, we
// will make sure to always re-inject this section into the css, even if
// certain rules were not used. This means that it will look like a no-op
// from the user's perspective, but we gathered all the useful information
// we need.
user: new Set(),
}

for (let [sort, rule] of sortedRules) {
if (sort >= context.minimumScreen) {
returnValue.variants.add(rule)
continue
}

if (sort & context.layerOrder.base) {
returnValue.base.add(rule)
continue
}

if (sort & context.layerOrder.defaults) {
returnValue.defaults.add(rule)
continue
}

if (sort & context.layerOrder.components) {
returnValue.components.add(rule)
continue
}

if (sort & context.layerOrder.utilities) {
returnValue.utilities.add(rule)
continue
}

if (sort & context.layerOrder.user) {
returnValue.user.add(rule)
continue
}
returnValue[sort.layer].add(rule)
}

return returnValue
Expand Down
20 changes: 9 additions & 11 deletions src/lib/generateRules.js
Expand Up @@ -146,9 +146,9 @@ function applyVariant(variant, matches, context) {

let fn = parseVariant(selector)

let sort = Array.from(context.variantOrder.values()).pop() << 1n
let sort = context.offsets.recordVariant(variant)

context.variantMap.set(variant, [[sort, fn]])
context.variantOrder.set(variant, sort)
}

if (context.variantMap.has(variant)) {
Expand Down Expand Up @@ -227,11 +227,7 @@ function applyVariant(variant, matches, context) {
// where you keep handling jobs until everything is done and each job can queue more
// jobs if needed.
variantFunctionTuples.push([
// TODO: This could have potential bugs if we shift the sort order from variant A far
// enough into the sort space of variant B. The chances are low, but if this happens
// then this might be the place too look at. One potential solution to this problem is
// reserving additional X places for these 'unknown' variants in between.
variantSort | BigInt(idx << ruleWithVariant.length),
context.offsets.applyParallelOffset(variantSort, idx),
variantFunction,

// If the clone has been modified we have to pass that back
Expand Down Expand Up @@ -298,7 +294,7 @@ function applyVariant(variant, matches, context) {
let withOffset = [
{
...meta,
sort: variantSort | meta.sort,
sort: context.offsets.applyVariantOffset(meta.sort, variantSort),
collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
isArbitraryVariant: isArbitraryValue(variant),
},
Expand Down Expand Up @@ -409,9 +405,11 @@ function extractArbitraryProperty(classCandidate, context) {
return null
}

let sort = context.offsets.arbitraryProperty()

return [
[
{ sort: context.arbitraryPropertiesSort, layer: 'utilities' },
{ sort, layer: 'utilities' },
() => ({
[asClass(classCandidate)]: {
[property]: normalized,
Expand Down Expand Up @@ -704,15 +702,15 @@ function generateRules(candidates, context) {
context.candidateRuleCache.set(candidate, rules)

for (const match of matches) {
let [{ sort, layer, options }, rule] = match
let [{ sort, options }, rule] = match

if (options.respectImportant && strategy) {
let container = postcss.root({ nodes: [rule.clone()] })
container.walkRules(strategy)
rule = container.nodes[0]
}

let newEntry = [sort | context.layerOrder[layer], rule]
let newEntry = [sort, rule]
rules.add(newEntry)
context.ruleCache.add(newEntry)
allRules.push(newEntry)
Expand Down

0 comments on commit d6bec49

Please sign in to comment.