From 862e2de9fd506fa1eef1bb4dc749b7ca3544ae63 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 6 Sep 2022 16:11:37 -0400 Subject: [PATCH 01/22] Remove remnants of the user layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It hasn’t been used in a while --- src/lib/expandApplyAtRules.js | 2 +- src/lib/expandTailwindAtRules.js | 15 --------------- src/lib/setupContextUtils.js | 3 --- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/lib/expandApplyAtRules.js b/src/lib/expandApplyAtRules.js index 7c4d156e06e7..23fe3392466f 100644 --- a/src/lib/expandApplyAtRules.js +++ b/src/lib/expandApplyAtRules.js @@ -149,7 +149,7 @@ function buildLocalApplyCache(root, context) { /** @type {ApplyCache} */ let cache = new Map() - let highestOffset = context.layerOrder.user >> 4n + let highestOffset = context.layerOrder.utilities >> 3n root.walkRules((rule, idx) => { // Ignore rules generated by Tailwind diff --git a/src/lib/expandTailwindAtRules.js b/src/lib/expandTailwindAtRules.js index 30b470800626..81abffd4a7d2 100644 --- a/src/lib/expandTailwindAtRules.js +++ b/src/lib/expandTailwindAtRules.js @@ -83,16 +83,6 @@ function buildStylesheet(rules, context) { 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) { @@ -120,11 +110,6 @@ function buildStylesheet(rules, context) { returnValue.utilities.add(rule) continue } - - if (sort & context.layerOrder.user) { - returnValue.user.add(rule) - continue - } } return returnValue diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 1fd72e301c4e..e8bc3eee06db 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -646,7 +646,6 @@ function registerPlugins(plugins, context) { base: 0n, components: 0n, utilities: 0n, - user: 0n, } let classList = new Set() @@ -673,7 +672,6 @@ function registerPlugins(plugins, context) { offsets.defaults, offsets.components, offsets.utilities, - offsets.user, ]) let reservedBits = BigInt(highestOffset.toString(2).length) @@ -686,7 +684,6 @@ function registerPlugins(plugins, context) { base: (1n << reservedBits) << 1n, components: (1n << reservedBits) << 2n, utilities: (1n << reservedBits) << 3n, - user: (1n << reservedBits) << 4n, } reservedBits += 5n From 6c5b60358d85b0bb58c378507142d8bcb0c915e6 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 6 Sep 2022 18:45:00 -0400 Subject: [PATCH 02/22] Rewrite sort offset generation --- src/lib/expandApplyAtRules.js | 13 +- src/lib/expandTailwindAtRules.js | 34 ++---- src/lib/generateRules.js | 15 ++- src/lib/offsets.js | 199 +++++++++++++++++++++++++++++++ src/lib/setupContextUtils.js | 68 ++++------- 5 files changed, 241 insertions(+), 88 deletions(-) create mode 100644 src/lib/offsets.js diff --git a/src/lib/expandApplyAtRules.js b/src/lib/expandApplyAtRules.js index 23fe3392466f..f86c04859e8a 100644 --- a/src/lib/expandApplyAtRules.js +++ b/src/lib/expandApplyAtRules.js @@ -149,9 +149,7 @@ function buildLocalApplyCache(root, context) { /** @type {ApplyCache} */ let cache = new Map() - let highestOffset = context.layerOrder.utilities >> 3n - - root.walkRules((rule, idx) => { + root.walkRules((rule) => { // Ignore rules generated by Tailwind for (let node of pathToRoot(rule)) { if (node.raws.tailwind?.layer !== undefined) { @@ -161,6 +159,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) || [] @@ -169,7 +168,7 @@ function buildLocalApplyCache(root, context) { list.push([ { layer: 'user', - sort: BigInt(idx) + highestOffset, + sort, important: false, }, container, @@ -553,11 +552,7 @@ 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, root.nodes[0]]) } } diff --git a/src/lib/expandTailwindAtRules.js b/src/lib/expandTailwindAtRules.js index 81abffd4a7d2..1bd16beaf972 100644 --- a/src/lib/expandTailwindAtRules.js +++ b/src/lib/expandTailwindAtRules.js @@ -1,10 +1,10 @@ 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' +import { Offsets } from './offsets.js' let env = sharedState.env @@ -74,8 +74,13 @@ 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, ([offset]) => offset) let returnValue = { base: new Set(), @@ -86,30 +91,7 @@ function buildStylesheet(rules, context) { } 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 - } + returnValue[sort.layer].add(rule) } return returnValue diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index fa07238ad402..69792fe877c8 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -146,9 +146,10 @@ function applyVariant(variant, matches, context) { let fn = parseVariant(selector) - let sort = Array.from(context.variantOrder.values()).pop() << 1n + // TODO: Test that this won't clash with reserved bits from the last registered variant + let sort = context.offsets.recordVariant(variant) + context.variantMap.set(variant, [[sort, fn]]) - context.variantOrder.set(variant, sort) } if (context.variantMap.has(variant)) { @@ -298,7 +299,7 @@ function applyVariant(variant, matches, context) { let withOffset = [ { ...meta, - sort: variantSort | meta.sort, + sort: context.offsets.applyVariantSort(meta.sort, variantSort), collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats), isArbitraryVariant: isArbitraryValue(variant), }, @@ -409,9 +410,11 @@ function extractArbitraryProperty(classCandidate, context) { return null } + let sort = context.offsets.create('utilities') + return [ [ - { sort: context.arbitraryPropertiesSort, layer: 'utilities' }, + { sort, layer: 'utilities' }, () => ({ [asClass(classCandidate)]: { [property]: normalized, @@ -704,7 +707,7 @@ 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()] }) @@ -712,7 +715,7 @@ function generateRules(candidates, context) { 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) diff --git a/src/lib/offsets.js b/src/lib/offsets.js new file mode 100644 index 000000000000..337d6229ad63 --- /dev/null +++ b/src/lib/offsets.js @@ -0,0 +1,199 @@ +// @ts-check + +import bigSign from '../util/bigSign' + +/** + * @typedef {'base' | 'defaults' | 'components' | 'utilities' | 'variants' | 'user'} Layer + */ + +/** + * @typedef {object} RuleOffset + * @property {Layer} layer The layer that this rule belongs to + * @property {Layer} parentLayer The layer that this rule originally belonged to. Only different from layer if this is a variant. + * @property {bigint} arbitrary 0n if false, 1n if true + * @property {bigint} variants Dynamic size. 1 bit per registered variant. 0n means no variants + * @property {bigint} parallelIndex Rule index for the parallel variant. 0 if not applicable. + * @property {bigint} index Index of the rule / utility in it's given *parent* layer. Monotonically increasing. + */ + +export class Offsets { + /** + * Offsets for the next rule in a given layer + * + * @type {Record} + */ + offsets = { + defaults: 0n, + base: 0n, + components: 0n, + utilities: 0n, + variants: 0n, + user: 0n, + } + + /** + * Positions for a given layer + * + * @type {Record} + */ + layerPositions = { + defaults: 0n, + base: 1n, + components: 2n, + utilities: 3n, + variants: 4n, + + // There isn't technically a "user" layer, but we need to give it a position + // Because it's used for ordering user-css from @apply + user: 5n, + } + + /** + * The total number of functions currently registered across all variants (including arbitrary variants) + * + * @type {bigint} + */ + reservedVariantBits = 0n + + /** + * Positions for a given variant + * + * @type {Map} + */ + variantOffsets = new Map() + + constructor(context) { + this.context = context + } + + /** + * @param {Layer} layer + * @returns {RuleOffset} + */ + create(layer) { + return { + layer, + parentLayer: layer, + arbitrary: 0n, + variants: 0n, + parallelIndex: 0n, + index: this.offsets[layer]++, + } + } + + /** + * @param {Layer} layer + * @returns {RuleOffset} + */ + arbitraryProperty() { + return { + ...this.create('utilities'), + arbitrary: 1n, + } + } + + /** + * Get the offset for a variant + * + * @param {string} variant + * @param {number} index + */ + forVariant(variant, index = 0) { + let offset = this.variantOffsets.get(variant) + if (offset === undefined) { + throw new Error(`Cannot find offset for unknown variant ${variant}`) + } + + return offset + BigInt(index) + } + + /** + * + * @param {RuleOffset} offset + * @param {bigint} bitmask + */ + applyVariantSort(offset, bitmask) { + return { + ...offset, + variants: offset.variants | bitmask, + } + } + + /** + * Each variant gets 1 bit per function / rule registered. + * This is because multiple variants can be applied to a single rule and we need to know which ones are present and which ones are not. + * Additionally, every unique group of variants is grouped together in the stylesheet. + * + * This grouping is order-independent. For instance, we do not differentiate between `hover:focus` and `focus:hover`. + * + * @param {string[]} variants + */ + recordVariants(variants) { + for (const variant of variants) { + this.recordVariant(variant, this.context.variantMap.get(variant).length) + } + } + + /** + * The same as `recordVariants` but for a single arbitrary variant at runtime. + * @param {string} variant + * @param {number} fnCount + * + * @returns {bigint} The highest offset for this variant + */ + recordVariant(variant, fnCount = 1) { + this.variantOffsets.set(variant, 1n << this.reservedVariantBits) + + // Ensure space is reserved for each "function" in the parallel variant + // by offsetting the next variant by the number of parallel variants + // in the one we just added. + + // Single functions that return parallel variants are NOT handled separately here + // They're offset by 1 (or the number of functions) as usual + // And each rule returned is tracked separately since the functions are evaluated lazily. + // @see `RuleOffset.parallelIndex` + this.reservedVariantBits += BigInt(fnCount) + + return 1n << this.reservedVariantBits + } + + /** + * @param {RuleOffset} a + * @param {RuleOffset} b + * @returns {bigint} + */ + compare(a, b) { + // Sort layers together + if (a.layer !== b.layer) { + return this.layerPositions[a.layer] - this.layerPositions[b.layer] + } + + // Sort variants in the order they were registered + if (a.variants !== b.variants) { + return a.variants - b.variants + } + + // Make sure each rule returned by a parallel variant is sorted in ascending order + if (a.parallelIndex !== b.parallelIndex) { + return a.parallelIndex - b.parallelIndex + } + + // Always sort arbitrary properties after other utilities + if (a.arbitrary !== b.arbitrary) { + return a.arbitrary - b.arbitrary + } + + // Sort utilities, components, etc… in the order they were registered + return a.index - b.index + } + + /** + * @template T + * @param {T[]} list + * @param {(item: T) => RuleOffset} getOffset + * @returns {T[]} + */ + sort(list, getOffset) { + return list.sort((a, b) => bigSign(this.compare(getOffset(a), getOffset(b)))) + } +} diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index e8bc3eee06db..3ed41547407e 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -22,6 +22,7 @@ import negateValue from '../util/negateValue' import isValidArbitraryValue from '../util/isValidArbitraryValue' import { generateRules } from './generateRules' import { hasContentChanged } from './cacheInvalidation.js' +import { Offsets } from './offsets.js' let MATCH_VARIANT = Symbol() @@ -201,6 +202,13 @@ export function parseVariant(variant) { } } +/** + * + * @param {any} tailwindConfig + * @param {any} context + * @param {object} param2 + * @param {Offsets} param2.offsets + */ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offsets, classList }) { function getConfigValue(path, defaultValue) { return path ? dlv(tailwindConfig, path, defaultValue) : tailwindConfig @@ -255,7 +263,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs addBase(base) { for (let [identifier, rule] of withIdentifiers(base)) { let prefixedIdentifier = prefixIdentifier(identifier, {}) - let offset = offsets.base++ + let offset = offsets.create('base') if (!context.candidateRuleMap.has(prefixedIdentifier)) { context.candidateRuleMap.set(prefixedIdentifier, []) @@ -284,7 +292,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs context.candidateRuleMap .get(prefixedIdentifier) - .push([{ sort: offsets.base++, layer: 'defaults' }, rule]) + .push([{ sort: offsets.create('defaults'), layer: 'defaults' }, rule]) } }, addComponents(components, options) { @@ -307,7 +315,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs context.candidateRuleMap .get(prefixedIdentifier) - .push([{ sort: offsets.components++, layer: 'components', options }, rule]) + .push([{ sort: offsets.create('components'), layer: 'components', options }, rule]) } }, addUtilities(utilities, options) { @@ -330,7 +338,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs context.candidateRuleMap .get(prefixedIdentifier) - .push([{ sort: offsets.utilities++, layer: 'utilities', options }, rule]) + .push([{ sort: offsets.create('utilities'), layer: 'utilities', options }, rule]) } }, matchUtilities: function (utilities, options) { @@ -341,7 +349,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs options = { ...defaultOptions, ...options } - let offset = offsets.utilities++ + let offset = offsets.create('utilities') for (let identifier in utilities) { let prefixedIdentifier = prefixIdentifier(identifier, options) @@ -393,7 +401,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs options = { ...defaultOptions, ...options } - let offset = offsets.components++ + let offset = offsets.create('components') for (let identifier in components) { let prefixedIdentifier = prefixIdentifier(identifier, options) @@ -641,12 +649,10 @@ function resolvePlugins(context, root) { function registerPlugins(plugins, context) { let variantList = [] let variantMap = new Map() - let offsets = { - defaults: 0n, - base: 0n, - components: 0n, - utilities: 0n, - } + context.variantMap = variantMap + + let offsets = new Offsets(context) + context.offsets = offsets let classList = new Set() @@ -667,44 +673,12 @@ function registerPlugins(plugins, context) { } } - let highestOffset = ((args) => args.reduce((m, e) => (e > m ? e : m)))([ - offsets.base, - offsets.defaults, - offsets.components, - offsets.utilities, - ]) - let reservedBits = BigInt(highestOffset.toString(2).length) - - // A number one less than the top range of the highest offset area - // so arbitrary properties are always sorted at the end. - context.arbitraryPropertiesSort = ((1n << reservedBits) << 0n) - 1n - - context.layerOrder = { - defaults: (1n << reservedBits) << 0n, - base: (1n << reservedBits) << 1n, - components: (1n << reservedBits) << 2n, - utilities: (1n << reservedBits) << 3n, - } - - reservedBits += 5n - - let offset = 0 - context.variantOrder = new Map( - variantList - .map((variant, i) => { - let variantFunctions = variantMap.get(variant).length - let bits = (1n << BigInt(i + offset)) << reservedBits - offset += variantFunctions - 1 - return [variant, bits] - }) - .sort(([, a], [, z]) => bigSign(a - z)) - ) - - context.minimumScreen = [...context.variantOrder.values()].shift() + // Make sure to record bit masks for every variant + offsets.recordVariants(variantList) // Build variantMap for (let [variantName, variantFunctions] of variantMap.entries()) { - let sort = context.variantOrder.get(variantName) + let sort = offsets.forVariant(variantName) context.variantMap.set( variantName, variantFunctions.map((variantFunction, idx) => [sort << BigInt(idx), variantFunction]) From db71de7d2492917268db0f1d0dbdc604f496bfbc Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 6 Sep 2022 19:33:47 -0400 Subject: [PATCH 03/22] wip --- src/lib/offsets.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/offsets.js b/src/lib/offsets.js index 337d6229ad63..4f24029bdc88 100644 --- a/src/lib/offsets.js +++ b/src/lib/offsets.js @@ -115,6 +115,8 @@ export class Offsets { applyVariantSort(offset, bitmask) { return { ...offset, + layer: 'variants', + parentLayer: offset.layer === 'variants' ? offset.parentLayer : offset.layer, variants: offset.variants | bitmask, } } From ad1e5be368c2f0cba4ac5cb806a1011339de715f Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 6 Sep 2022 20:34:50 -0400 Subject: [PATCH 04/22] wip wip --- src/lib/expandApplyAtRules.js | 3 +-- src/lib/generateRules.js | 2 +- src/lib/offsets.js | 6 +++--- src/lib/setupContextUtils.js | 22 +++++++++++++++------- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/lib/expandApplyAtRules.js b/src/lib/expandApplyAtRules.js index f86c04859e8a..aa42cb200e75 100644 --- a/src/lib/expandApplyAtRules.js +++ b/src/lib/expandApplyAtRules.js @@ -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} ApplyCache */ @@ -557,7 +556,7 @@ function processApply(root, context, localCache) { } // 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, (sibling) => sibling[0].sort).map((s) => s[1]) // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 } parent.after(nodes) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 69792fe877c8..d0bd981d2c34 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -410,7 +410,7 @@ function extractArbitraryProperty(classCandidate, context) { return null } - let sort = context.offsets.create('utilities') + let sort = context.offsets.arbitraryProperty() return [ [ diff --git a/src/lib/offsets.js b/src/lib/offsets.js index 4f24029bdc88..76ec403e889e 100644 --- a/src/lib/offsets.js +++ b/src/lib/offsets.js @@ -41,11 +41,12 @@ export class Offsets { base: 1n, components: 2n, utilities: 3n, - variants: 4n, // There isn't technically a "user" layer, but we need to give it a position // Because it's used for ordering user-css from @apply - user: 5n, + user: 4n, + + variants: 5n, } /** @@ -82,7 +83,6 @@ export class Offsets { } /** - * @param {Layer} layer * @returns {RuleOffset} */ arbitraryProperty() { diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 3ed41547407e..23e6cd1d1085 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -12,7 +12,6 @@ import isPlainObject from '../util/isPlainObject' import escapeClassName from '../util/escapeClassName' import nameClass, { formatClass } from '../util/nameClass' import { coerceValue } from '../util/pluginUtils' -import bigSign from '../util/bigSign' import { variantPlugins, corePlugins } from '../corePlugins' import * as sharedState from './sharedState' import { env } from './sharedState' @@ -790,13 +789,22 @@ function registerPlugins(plugins, context) { // A list of utilities that are used by certain Tailwind CSS utilities but // that don't exist on their own. This will result in them "not existing" and // sorting could be weird since you still require them in order to make the - // host utitlies work properly. (Thanks Biology) + // host utilities work properly. (Thanks Biology) let parasiteUtilities = new Set([prefix(context, 'group'), prefix(context, 'peer')]) context.getClassOrder = function getClassOrder(classes) { - let sortedClassNames = new Map() - for (let [sort, rule] of generateRules(new Set(classes), context)) { - if (sortedClassNames.has(rule.raws.tailwind.candidate)) continue - sortedClassNames.set(rule.raws.tailwind.candidate, sort) + // Non-util classes won't be generated, so we default them to null + let sortedClassNames = new Map(classes.map((className) => [className, null])) + + // Sort all classes in order + // Non-tailwind classes won't be generated and will be left as `null` + let rules = generateRules(new Set(classes), context) + rules = context.offsets.sort(rules, (rule) => rule[0]) + + // Give each generated class an increasing index + let idx = 0n + + for (const [, rule] of rules) { + sortedClassNames.set(rule.raws.tailwind.candidate, idx++) } return classes.map((className) => { @@ -806,7 +814,7 @@ function registerPlugins(plugins, context) { // This will make sure that it is at the very beginning of the // `components` layer which technically means 'before any // components'. - order = context.layerOrder.components + order = null // TODO } return [className, order] From 6c8cf5e711eb8c00ecd017bf889fd906a7ff4854 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 6 Sep 2022 20:37:27 -0400 Subject: [PATCH 05/22] Handle parasite utilities --- src/lib/offsets.js | 85 +++++++++++++++++++++++++++++++++--- src/lib/setupContextUtils.js | 9 ++-- 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/src/lib/offsets.js b/src/lib/offsets.js index 76ec403e889e..c2be92540779 100644 --- a/src/lib/offsets.js +++ b/src/lib/offsets.js @@ -3,7 +3,7 @@ import bigSign from '../util/bigSign' /** - * @typedef {'base' | 'defaults' | 'components' | 'utilities' | 'variants' | 'user'} Layer + * @typedef {'base' | 'defaults' | 'parasites' | 'components' | 'utilities' | 'variants' | 'user'} Layer */ /** @@ -25,6 +25,7 @@ export class Offsets { offsets = { defaults: 0n, base: 0n, + parasites: 0n, components: 0n, utilities: 0n, variants: 0n, @@ -39,14 +40,18 @@ export class Offsets { layerPositions = { defaults: 0n, base: 1n, - components: 2n, - utilities: 3n, + + // This also not a layer but "parasite" utilities are sorted before components + parasites: 2n, + + components: 3n, + utilities: 4n, // There isn't technically a "user" layer, but we need to give it a position // Because it's used for ordering user-css from @apply - user: 4n, + user: 5n, - variants: 5n, + variants: 6n, } /** @@ -82,6 +87,41 @@ export class Offsets { } } + /** + * @param {RuleOffset[]} rules + * @returns {{layers: Record, rules: bigint[]}} + */ + numeric(rules) { + let variantBits = this.reservedVariantBits + let parallelIndexBits = bitCount(max(rules.map((offset) => offset.parallelIndex))) + let arbitraryBits = 1n + let indexBits = bitCount(max(rules.map((offset) => offset.index))) + + let layerPos = variantBits + parallelIndexBits + arbitraryBits + indexBits + let variantPos = parallelIndexBits + arbitraryBits + indexBits + let parallelPos = arbitraryBits + indexBits + let arbitraryPos = indexBits + let indexPos = 0n + + return { + // @ts-ignore + layers: Object.fromEntries( + Object.entries(this.layerPositions).map(([layer, pos]) => [layer, pos << layerPos]) + ), + rules: rules.map((offset) => { + const layer = this.layerPositions[offset.layer] + + return ( + (layer << layerPos) + + (offset.variants << variantPos) + + (offset.parallelIndex << parallelPos) + + (offset.arbitrary << arbitraryPos) + + (offset.index << indexPos) + ) + }), + } + } + /** * @returns {RuleOffset} */ @@ -199,3 +239,38 @@ export class Offsets { return list.sort((a, b) => bigSign(this.compare(getOffset(a), getOffset(b)))) } } + +/** + * + * @param {bigint[]} nums + * @returns {bigint|null} + */ +function max(nums) { + let max = null + + for (const num of nums) { + max = max ?? num + max = max > num ? max : num + } + + return max +} + +/** + * + * @param {bigint|null} value + * @returns {bigint} + */ +function bitCount(value) { + if (value === null) { + return 0n + } + + let bits = 1n + + while ((value >>= 1n)) { + bits += 1n + } + + return bits +} diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 23e6cd1d1085..61e2281c1d4c 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -800,11 +800,10 @@ function registerPlugins(plugins, context) { let rules = generateRules(new Set(classes), context) rules = context.offsets.sort(rules, (rule) => rule[0]) - // Give each generated class an increasing index - let idx = 0n + let offsets = context.offsets.numeric(rules.map(([sort]) => sort)) - for (const [, rule] of rules) { - sortedClassNames.set(rule.raws.tailwind.candidate, idx++) + for (const [index, [, rule]] of rules.entries()) { + sortedClassNames.set(rule.raws.tailwind.candidate, offsets.rules[index]) } return classes.map((className) => { @@ -814,7 +813,7 @@ function registerPlugins(plugins, context) { // This will make sure that it is at the very beginning of the // `components` layer which technically means 'before any // components'. - order = null // TODO + order = offsets.layers.parasites } return [className, order] From 5166fd387df0533d15b9630f7b919ccf1afe75b3 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 6 Sep 2022 20:45:57 -0400 Subject: [PATCH 06/22] wip --- src/lib/offsets.js | 2 +- src/lib/setupContextUtils.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/offsets.js b/src/lib/offsets.js index c2be92540779..c281c69707bd 100644 --- a/src/lib/offsets.js +++ b/src/lib/offsets.js @@ -144,7 +144,7 @@ export class Offsets { throw new Error(`Cannot find offset for unknown variant ${variant}`) } - return offset + BigInt(index) + return offset << BigInt(index) } /** diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 61e2281c1d4c..3a4aacc0cad4 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -677,10 +677,12 @@ function registerPlugins(plugins, context) { // Build variantMap for (let [variantName, variantFunctions] of variantMap.entries()) { - let sort = offsets.forVariant(variantName) context.variantMap.set( variantName, - variantFunctions.map((variantFunction, idx) => [sort << BigInt(idx), variantFunction]) + variantFunctions.map((variantFunction, idx) => [ + offsets.forVariant(variantName, idx), + variantFunction, + ]) ) } From a9ffcacdf2b6c921e764407ea0a553742b10b50d Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 6 Sep 2022 20:58:49 -0400 Subject: [PATCH 07/22] wip --- src/lib/generateRules.js | 2 +- src/lib/offsets.js | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index d0bd981d2c34..bde52ada0701 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -299,7 +299,7 @@ function applyVariant(variant, matches, context) { let withOffset = [ { ...meta, - sort: context.offsets.applyVariantSort(meta.sort, variantSort), + sort: context.offsets.applyVariantOffset(meta.sort, variantSort), collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats), isArbitraryVariant: isArbitraryValue(variant), }, diff --git a/src/lib/offsets.js b/src/lib/offsets.js index c281c69707bd..62dda529a568 100644 --- a/src/lib/offsets.js +++ b/src/lib/offsets.js @@ -137,6 +137,7 @@ export class Offsets { * * @param {string} variant * @param {number} index + * @returns {RuleOffset} */ forVariant(variant, index = 0) { let offset = this.variantOffsets.get(variant) @@ -144,20 +145,23 @@ export class Offsets { throw new Error(`Cannot find offset for unknown variant ${variant}`) } - return offset << BigInt(index) + return { + ...this.create('variants'), + variants: offset << BigInt(index), + } } /** - * - * @param {RuleOffset} offset - * @param {bigint} bitmask + * @param {RuleOffset} rule + * @param {RuleOffset} variant + * @returns {RuleOffset} */ - applyVariantSort(offset, bitmask) { + applyVariantOffset(rule, variant) { return { - ...offset, + ...rule, layer: 'variants', - parentLayer: offset.layer === 'variants' ? offset.parentLayer : offset.layer, - variants: offset.variants | bitmask, + parentLayer: rule.layer === 'variants' ? rule.parentLayer : rule.layer, + variants: rule.variants | variant.variants, } } From 610aad9495d764c757238c0d0484f8702b968ebc Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 6 Sep 2022 20:59:57 -0400 Subject: [PATCH 08/22] Make parallel variants sorting more resillient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s not perfect but it’s close --- src/lib/generateRules.js | 6 +----- src/lib/offsets.js | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index bde52ada0701..7f5145c902dc 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -228,11 +228,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 diff --git a/src/lib/offsets.js b/src/lib/offsets.js index 62dda529a568..c67bfc7e38ce 100644 --- a/src/lib/offsets.js +++ b/src/lib/offsets.js @@ -162,6 +162,23 @@ export class Offsets { layer: 'variants', parentLayer: rule.layer === 'variants' ? rule.parentLayer : rule.layer, variants: rule.variants | variant.variants, + + // TODO: Technically this is wrong. We should be handling parallel index on a per variant basis. + // We'll take the max of all the parallel indexes for now. + // @ts-ignore + parallelIndex: max([rule.parallelIndex, variant.parallelIndex]), + } + } + + /** + * @param {RuleOffset} offset + * @param {number} parallelIndex + * @returns {RuleOffset} + */ + applyParallelOffset(offset, parallelIndex) { + return { + ...offset, + parallelIndex: BigInt(parallelIndex), } } From 2c92061ca3c0d0b81ce702e97d25f616386fb019 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 6 Sep 2022 21:23:00 -0400 Subject: [PATCH 09/22] fix --- src/lib/offsets.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/offsets.js b/src/lib/offsets.js index c67bfc7e38ce..76bd5e83edff 100644 --- a/src/lib/offsets.js +++ b/src/lib/offsets.js @@ -202,7 +202,7 @@ export class Offsets { * @param {string} variant * @param {number} fnCount * - * @returns {bigint} The highest offset for this variant + * @returns {RuleOffset} The highest offset for this variant */ recordVariant(variant, fnCount = 1) { this.variantOffsets.set(variant, 1n << this.reservedVariantBits) @@ -217,7 +217,10 @@ export class Offsets { // @see `RuleOffset.parallelIndex` this.reservedVariantBits += BigInt(fnCount) - return 1n << this.reservedVariantBits + return { + ...this.create('variants'), + variants: 1n << this.reservedVariantBits, + } } /** From 1e16d90d614970c6011bd7d6dda10bdb8004a3d8 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 6 Sep 2022 22:08:01 -0400 Subject: [PATCH 10/22] remove todo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit it adds a new bit so it can’t --- src/lib/generateRules.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 7f5145c902dc..558cd47ebfa3 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -146,7 +146,6 @@ function applyVariant(variant, matches, context) { let fn = parseVariant(selector) - // TODO: Test that this won't clash with reserved bits from the last registered variant let sort = context.offsets.recordVariant(variant) context.variantMap.set(variant, [[sort, fn]]) From 4f5aa4e839f44e39edda71ce04255506baddaab3 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 7 Sep 2022 09:59:19 -0400 Subject: [PATCH 11/22] Simplify getClassOrder usage --- src/lib/offsets.js | 70 ------------------------------------ src/lib/setupContextUtils.js | 8 ++--- 2 files changed, 4 insertions(+), 74 deletions(-) diff --git a/src/lib/offsets.js b/src/lib/offsets.js index 76bd5e83edff..3de0bb02d737 100644 --- a/src/lib/offsets.js +++ b/src/lib/offsets.js @@ -87,41 +87,6 @@ export class Offsets { } } - /** - * @param {RuleOffset[]} rules - * @returns {{layers: Record, rules: bigint[]}} - */ - numeric(rules) { - let variantBits = this.reservedVariantBits - let parallelIndexBits = bitCount(max(rules.map((offset) => offset.parallelIndex))) - let arbitraryBits = 1n - let indexBits = bitCount(max(rules.map((offset) => offset.index))) - - let layerPos = variantBits + parallelIndexBits + arbitraryBits + indexBits - let variantPos = parallelIndexBits + arbitraryBits + indexBits - let parallelPos = arbitraryBits + indexBits - let arbitraryPos = indexBits - let indexPos = 0n - - return { - // @ts-ignore - layers: Object.fromEntries( - Object.entries(this.layerPositions).map(([layer, pos]) => [layer, pos << layerPos]) - ), - rules: rules.map((offset) => { - const layer = this.layerPositions[offset.layer] - - return ( - (layer << layerPos) + - (offset.variants << variantPos) + - (offset.parallelIndex << parallelPos) + - (offset.arbitrary << arbitraryPos) + - (offset.index << indexPos) - ) - }), - } - } - /** * @returns {RuleOffset} */ @@ -263,38 +228,3 @@ export class Offsets { return list.sort((a, b) => bigSign(this.compare(getOffset(a), getOffset(b)))) } } - -/** - * - * @param {bigint[]} nums - * @returns {bigint|null} - */ -function max(nums) { - let max = null - - for (const num of nums) { - max = max ?? num - max = max > num ? max : num - } - - return max -} - -/** - * - * @param {bigint|null} value - * @returns {bigint} - */ -function bitCount(value) { - if (value === null) { - return 0n - } - - let bits = 1n - - while ((value >>= 1n)) { - bits += 1n - } - - return bits -} diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 3a4aacc0cad4..c0dcf340f54a 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -802,10 +802,10 @@ function registerPlugins(plugins, context) { let rules = generateRules(new Set(classes), context) rules = context.offsets.sort(rules, (rule) => rule[0]) - let offsets = context.offsets.numeric(rules.map(([sort]) => sort)) + let idx = BigInt(parasiteUtilities.size) - for (const [index, [, rule]] of rules.entries()) { - sortedClassNames.set(rule.raws.tailwind.candidate, offsets.rules[index]) + for (const [, rule] of rules) { + sortedClassNames.set(rule.raws.tailwind.candidate, idx++) } return classes.map((className) => { @@ -815,7 +815,7 @@ function registerPlugins(plugins, context) { // This will make sure that it is at the very beginning of the // `components` layer which technically means 'before any // components'. - order = offsets.layers.parasites + order = BigInt(Array.from(parasiteUtilities).indexOf(className)) } return [className, order] From c908d292dbaf3de3d2964efa9a63bbe12d1fbee5 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 7 Sep 2022 10:00:23 -0400 Subject: [PATCH 12/22] Simplify oops oops --- src/lib/offsets.js | 16 ++++++++++++++++ src/lib/setupContextUtils.js | 9 +++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/lib/offsets.js b/src/lib/offsets.js index 3de0bb02d737..1028b7f1fe43 100644 --- a/src/lib/offsets.js +++ b/src/lib/offsets.js @@ -228,3 +228,19 @@ export class Offsets { return list.sort((a, b) => bigSign(this.compare(getOffset(a), getOffset(b)))) } } + +/** + * + * @param {bigint[]} nums + * @returns {bigint|null} + */ +function max(nums) { + let max = null + + for (const num of nums) { + max = max ?? num + max = max > num ? max : num + } + + return max +} diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index c0dcf340f54a..c95c27591554 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -792,7 +792,7 @@ function registerPlugins(plugins, context) { // that don't exist on their own. This will result in them "not existing" and // sorting could be weird since you still require them in order to make the // host utilities work properly. (Thanks Biology) - let parasiteUtilities = new Set([prefix(context, 'group'), prefix(context, 'peer')]) + let parasiteUtilities = [prefix(context, 'group'), prefix(context, 'peer')] context.getClassOrder = function getClassOrder(classes) { // Non-util classes won't be generated, so we default them to null let sortedClassNames = new Map(classes.map((className) => [className, null])) @@ -802,7 +802,7 @@ function registerPlugins(plugins, context) { let rules = generateRules(new Set(classes), context) rules = context.offsets.sort(rules, (rule) => rule[0]) - let idx = BigInt(parasiteUtilities.size) + let idx = BigInt(parasiteUtilities.length) for (const [, rule] of rules) { sortedClassNames.set(rule.raws.tailwind.candidate, idx++) @@ -810,12 +810,13 @@ function registerPlugins(plugins, context) { return classes.map((className) => { let order = sortedClassNames.get(className) ?? null + let parasiteIndex = parasiteUtilities.indexOf(className) - if (order === null && parasiteUtilities.has(className)) { + if (order === null && parasiteIndex !== -1) { // This will make sure that it is at the very beginning of the // `components` layer which technically means 'before any // components'. - order = BigInt(Array.from(parasiteUtilities).indexOf(className)) + order = BigInt(parasiteIndex) } return [className, order] From 3b52e9e015b8e8f079467210dc449ee417168b55 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 7 Sep 2022 10:00:36 -0400 Subject: [PATCH 13/22] Add parasite utility for `dark` dark mode class name --- src/lib/setupContextUtils.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index c95c27591554..92faf3c29cd1 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -788,11 +788,17 @@ function registerPlugins(plugins, context) { } } + let darkClassName = [].concat(context.tailwindConfig.darkMode ?? 'media')[1] ?? 'dark' + // A list of utilities that are used by certain Tailwind CSS utilities but // that don't exist on their own. This will result in them "not existing" and // sorting could be weird since you still require them in order to make the // host utilities work properly. (Thanks Biology) - let parasiteUtilities = [prefix(context, 'group'), prefix(context, 'peer')] + let parasiteUtilities = [ + prefix(context, darkClassName), + prefix(context, 'group'), + prefix(context, 'peer'), + ] context.getClassOrder = function getClassOrder(classes) { // Non-util classes won't be generated, so we default them to null let sortedClassNames = new Map(classes.map((className) => [className, null])) From 99e42e4c0f4b3e00b3238d73f9acaf6524d26520 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 7 Sep 2022 10:11:20 -0400 Subject: [PATCH 14/22] Cleanup --- src/lib/expandTailwindAtRules.js | 1 - src/lib/offsets.js | 102 +++++++++++++++---------------- src/lib/setupContextUtils.js | 4 +- 3 files changed, 50 insertions(+), 57 deletions(-) diff --git a/src/lib/expandTailwindAtRules.js b/src/lib/expandTailwindAtRules.js index 1bd16beaf972..1cfbe07bb979 100644 --- a/src/lib/expandTailwindAtRules.js +++ b/src/lib/expandTailwindAtRules.js @@ -4,7 +4,6 @@ import { generateRules } from './generateRules' import log from '../util/log' import cloneNodes from '../util/cloneNodes' import { defaultExtractor } from './defaultExtractor' -import { Offsets } from './offsets.js' let env = sharedState.env diff --git a/src/lib/offsets.js b/src/lib/offsets.js index 1028b7f1fe43..8f1e2bde22be 100644 --- a/src/lib/offsets.js +++ b/src/lib/offsets.js @@ -3,7 +3,7 @@ import bigSign from '../util/bigSign' /** - * @typedef {'base' | 'defaults' | 'parasites' | 'components' | 'utilities' | 'variants' | 'user'} Layer + * @typedef {'base' | 'defaults' | 'components' | 'utilities' | 'variants' | 'user'} Layer */ /** @@ -17,59 +17,52 @@ import bigSign from '../util/bigSign' */ export class Offsets { - /** - * Offsets for the next rule in a given layer - * - * @type {Record} - */ - offsets = { - defaults: 0n, - base: 0n, - parasites: 0n, - components: 0n, - utilities: 0n, - variants: 0n, - user: 0n, - } - - /** - * Positions for a given layer - * - * @type {Record} - */ - layerPositions = { - defaults: 0n, - base: 1n, - - // This also not a layer but "parasite" utilities are sorted before components - parasites: 2n, - - components: 3n, - utilities: 4n, - - // There isn't technically a "user" layer, but we need to give it a position - // Because it's used for ordering user-css from @apply - user: 5n, - - variants: 6n, - } - - /** - * The total number of functions currently registered across all variants (including arbitrary variants) - * - * @type {bigint} - */ - reservedVariantBits = 0n + constructor() { + /** + * Offsets for the next rule in a given layer + * + * @type {Record} + */ + this.offsets = { + defaults: 0n, + base: 0n, + components: 0n, + utilities: 0n, + variants: 0n, + user: 0n, + } - /** - * Positions for a given variant - * - * @type {Map} - */ - variantOffsets = new Map() + /** + * Positions for a given layer + * + * @type {Record} + */ + this.layerPositions = { + defaults: 0n, + base: 1n, + components: 2n, + utilities: 3n, + + // There isn't technically a "user" layer, but we need to give it a position + // Because it's used for ordering user-css from @apply + user: 4n, + + variants: 5n, + } - constructor(context) { - this.context = context + /** + * The total number of functions currently registered across all variants (including arbitrary variants) + * + * @type {bigint} + */ + this.reservedVariantBits = 0n + + /** + * Positions for a given variant + * + * @type {Map} + */ + this.variantOffsets = new Map() } /** @@ -155,10 +148,11 @@ export class Offsets { * This grouping is order-independent. For instance, we do not differentiate between `hover:focus` and `focus:hover`. * * @param {string[]} variants + * @param {(name: string) => number} getLength */ - recordVariants(variants) { + recordVariants(variants, getLength) { for (const variant of variants) { - this.recordVariant(variant, this.context.variantMap.get(variant).length) + this.recordVariant(variant, getLength(variant)) } } diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 92faf3c29cd1..36b35bd0209e 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -650,7 +650,7 @@ function registerPlugins(plugins, context) { let variantMap = new Map() context.variantMap = variantMap - let offsets = new Offsets(context) + let offsets = new Offsets() context.offsets = offsets let classList = new Set() @@ -673,7 +673,7 @@ function registerPlugins(plugins, context) { } // Make sure to record bit masks for every variant - offsets.recordVariants(variantList) + offsets.recordVariants(variantList, (variant) => variantMap.get(variant).length) // Build variantMap for (let [variantName, variantFunctions] of variantMap.entries()) { From 2d2b7bf846d04e5cb18a3916a930979e849fcb34 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 7 Sep 2022 10:17:53 -0400 Subject: [PATCH 15/22] Cleanup --- src/lib/expandApplyAtRules.js | 4 ++-- src/lib/setupContextUtils.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/expandApplyAtRules.js b/src/lib/expandApplyAtRules.js index aa42cb200e75..290415c934c7 100644 --- a/src/lib/expandApplyAtRules.js +++ b/src/lib/expandApplyAtRules.js @@ -551,12 +551,12 @@ function processApply(root, context, localCache) { } // Insert it - siblings.push([meta, root.nodes[0]]) + siblings.push([meta.sort, root.nodes[0]]) } } // Inject the rules, sorted, correctly - let nodes = context.offsets.sort(siblings, (sibling) => sibling[0].sort).map((s) => s[1]) + let nodes = context.offsets.sort(siblings, ([offset]) => offset).map((s) => s[1]) // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 } parent.after(nodes) diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 36b35bd0209e..100c81152d63 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -806,7 +806,7 @@ function registerPlugins(plugins, context) { // Sort all classes in order // Non-tailwind classes won't be generated and will be left as `null` let rules = generateRules(new Set(classes), context) - rules = context.offsets.sort(rules, (rule) => rule[0]) + rules = context.offsets.sort(rules, ([offset]) => offset) let idx = BigInt(parasiteUtilities.length) From 01f76d5f0df722615af4c210d1f156b5380827ea Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 7 Sep 2022 10:22:30 -0400 Subject: [PATCH 16/22] Simplify --- src/lib/expandApplyAtRules.js | 2 +- src/lib/expandTailwindAtRules.js | 2 +- src/lib/offsets.js | 9 ++++----- src/lib/setupContextUtils.js | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib/expandApplyAtRules.js b/src/lib/expandApplyAtRules.js index 290415c934c7..7dad021f0d82 100644 --- a/src/lib/expandApplyAtRules.js +++ b/src/lib/expandApplyAtRules.js @@ -556,7 +556,7 @@ function processApply(root, context, localCache) { } // Inject the rules, sorted, correctly - let nodes = context.offsets.sort(siblings, ([offset]) => offset).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) diff --git a/src/lib/expandTailwindAtRules.js b/src/lib/expandTailwindAtRules.js index 1cfbe07bb979..040f713b0e62 100644 --- a/src/lib/expandTailwindAtRules.js +++ b/src/lib/expandTailwindAtRules.js @@ -79,7 +79,7 @@ function getClassCandidates(content, extractor, candidates, seen) { * @param {*} context */ function buildStylesheet(rules, context) { - let sortedRules = context.offsets.sort(rules, ([offset]) => offset) + let sortedRules = context.offsets.sort(rules) let returnValue = { base: new Set(), diff --git a/src/lib/offsets.js b/src/lib/offsets.js index 8f1e2bde22be..1aea30a698f9 100644 --- a/src/lib/offsets.js +++ b/src/lib/offsets.js @@ -214,12 +214,11 @@ export class Offsets { /** * @template T - * @param {T[]} list - * @param {(item: T) => RuleOffset} getOffset - * @returns {T[]} + * @param {[RuleOffset, T][]} list + * @returns {[RuleOffset, T][]} */ - sort(list, getOffset) { - return list.sort((a, b) => bigSign(this.compare(getOffset(a), getOffset(b)))) + sort(list) { + return list.sort(([a], [b]) => bigSign(this.compare(a, b))) } } diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 100c81152d63..e8519ad95f9f 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -806,7 +806,7 @@ function registerPlugins(plugins, context) { // Sort all classes in order // Non-tailwind classes won't be generated and will be left as `null` let rules = generateRules(new Set(classes), context) - rules = context.offsets.sort(rules, ([offset]) => offset) + rules = context.offsets.sort(rules) let idx = BigInt(parasiteUtilities.length) From 04cc215b49c8edd632993dd8709d7a8e55dac54a Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 9 Sep 2022 12:24:03 +0200 Subject: [PATCH 17/22] format files --- tests/arbitrary-variants.test.js | 2 +- tests/prefers-contrast.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/arbitrary-variants.test.js b/tests/arbitrary-variants.test.js index a145ee5b4731..c17016a5bccb 100644 --- a/tests/arbitrary-variants.test.js +++ b/tests/arbitrary-variants.test.js @@ -107,7 +107,7 @@ test('variants without & or an at-rule are ignored', () => { test('arbitrary variants are sorted after other variants', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], corePlugins: { preflight: false }, } diff --git a/tests/prefers-contrast.test.js b/tests/prefers-contrast.test.js index e20e86146d3e..362096078b39 100644 --- a/tests/prefers-contrast.test.js +++ b/tests/prefers-contrast.test.js @@ -3,7 +3,7 @@ import { run, html, css, defaults } from './util/run' it('should be possible to use contrast-more and contrast-less variants', () => { let config = { content: [ - { raw: html`
` }, + { raw: html`
` }, ], corePlugins: { preflight: false }, } From bed2b35304515e54fcf96a10e9384e1545e03189 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 9 Sep 2022 12:33:12 -0400 Subject: [PATCH 18/22] Fix prettier plugin to use git build of Tailwind CSS Symlink and build instead of adding a recursive dev dependency It breaks node < 16 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index c21b19840273..dd4680ce8fdc 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "postswcify": "esbuild lib/cli-peer-dependencies.js --bundle --platform=node --outfile=peers/index.js", "rebuild-fixtures": "npm run swcify && node -r @swc/register scripts/rebuildFixtures.js", "prepublishOnly": "npm install --force && npm run swcify", + "prestyle": "ln -nfs `pwd` node_modules/tailwindcss && npm run swcify", "style": "eslint .", "pretest": "npm run generate", "test": "jest", From e3a6da30f725f2abfcb36cea5720ae4d4a3b6686 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 9 Sep 2022 12:33:19 -0400 Subject: [PATCH 19/22] Fix prettier error --- tests/arbitrary-properties.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/arbitrary-properties.test.js b/tests/arbitrary-properties.test.js index e3a4f02a5bce..024b1fa13cbd 100644 --- a/tests/arbitrary-properties.test.js +++ b/tests/arbitrary-properties.test.js @@ -324,7 +324,7 @@ it('should be possible to read theme values in arbitrary properties (without quo it('should be possible to read theme values in arbitrary properties (with quotes)', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], corePlugins: { preflight: false }, } From 7c121a536a06f9477f33e7de704a7754af5a928d Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 9 Sep 2022 12:50:33 -0400 Subject: [PATCH 20/22] wip --- .github/workflows/nodejs.yml | 7 +++++++ package.json | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index abb81dad4a8a..c00672f686de 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -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: diff --git a/package.json b/package.json index dd4680ce8fdc..c21b19840273 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "postswcify": "esbuild lib/cli-peer-dependencies.js --bundle --platform=node --outfile=peers/index.js", "rebuild-fixtures": "npm run swcify && node -r @swc/register scripts/rebuildFixtures.js", "prepublishOnly": "npm install --force && npm run swcify", - "prestyle": "ln -nfs `pwd` node_modules/tailwindcss && npm run swcify", "style": "eslint .", "pretest": "npm run generate", "test": "jest", From d965f2068d02387ac265ee073919e73456399d75 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 9 Sep 2022 12:56:00 -0400 Subject: [PATCH 21/22] fix test --- tests/arbitrary-properties.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/arbitrary-properties.test.js b/tests/arbitrary-properties.test.js index 024b1fa13cbd..a7d1b5bbf72d 100644 --- a/tests/arbitrary-properties.test.js +++ b/tests/arbitrary-properties.test.js @@ -338,12 +338,12 @@ it('should be possible to read theme values in arbitrary properties (with quotes return expect(result.css).toMatchFormattedCss(css` ${defaults} - .\[--a\:theme\(\'colors\.blue\.500\'\)\] { - --a: #3b82f6; - } .\[color\:var\(--a\)\] { color: var(--a); } + .\[--a\:theme\(\'colors\.blue\.500\'\)\] { + --a: #3b82f6; + } `) }) }) From 8579ff4f523d2788908927bfdc34c2f7eeb4822e Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 9 Sep 2022 13:05:52 -0400 Subject: [PATCH 22/22] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd358b6073f3..44ba2ccee79d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Re-use existing entries in the rule cache ([#9208](https://github.com/tailwindlabs/tailwindcss/pull/9208)) - Don't output duplicate utilities ([#9208](https://github.com/tailwindlabs/tailwindcss/pull/9208)) - Fix `fontFamily` config TypeScript types ([#9214](https://github.com/tailwindlabs/tailwindcss/pull/9214)) +- Fix ordering of parallel variants ([#9282](https://github.com/tailwindlabs/tailwindcss/pull/9282)) ## [3.1.8] - 2022-08-05