diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts index 5e5027edb..df0c73759 100644 --- a/packages/tailwindcss/src/compile.ts +++ b/packages/tailwindcss/src/compile.ts @@ -6,45 +6,34 @@ import { DefaultMap } from './utils/default-map' import { escape } from './utils/escape' import type { Variants } from './variants' -export function compileCandidates( - rawCandidates: string[], +export function createParsedVariants( designSystem: DesignSystem, - { throwOnInvalidCandidate = false } = {}, -) { - let nodeSorting = new Map< - AstNode, - { properties: number[]; variants: bigint; candidate: string } - >() - let astNodes: AstNode[] = [] - +): DefaultMap { // A lazy map implementation that will return the variant if it exists. If it // doesn't exist yet, the raw string variant will be parsed and added to the // map. - let parsedVariants: DefaultMap = new DefaultMap((variant, map) => { + return new DefaultMap((variant, map) => { return parseVariant(variant, designSystem.variants, map) }) +} - let candidates = new Map() - - // Parse candidates and variants - for (let rawCandidate of rawCandidates) { - let candidate = parseCandidate(rawCandidate, designSystem.utilities, parsedVariants) - if (candidate === null) { - if (throwOnInvalidCandidate) { - throw new Error(`Cannot apply unknown utility class: ${rawCandidate}`) - } - continue // Bail, invalid candidate - } - candidates.set(candidate, rawCandidate) - } - - // Sort the variants - let variants = Array.from(parsedVariants.values()).sort((a, z) => { - return designSystem.variants.compare(a, z) +export function createParsedCandidates( + designSystem: DesignSystem, + parsedVariants: DefaultMap, +) { + return new DefaultMap((candidate) => { + return parseCandidate(candidate, designSystem.utilities, parsedVariants) }) +} + +export function createParsedAstNodes( + designSystem: DesignSystem, + parsedCandidates: DefaultMap, +) { + return new DefaultMap((rawCandidate: string) => { + let candidate = parsedCandidates.get(rawCandidate) + if (candidate === null) return null - // Create the AST - next: for (let [candidate, rawCandidate] of candidates) { let nodes: AstNode[] = [] // Handle arbitrary properties @@ -53,12 +42,7 @@ export function compileCandidates( // Build the node let compiledNodes = compileFn(candidate) - if (compiledNodes === undefined) { - if (throwOnInvalidCandidate) { - throw new Error(`Cannot apply unknown utility class: ${rawCandidate}`) - } - continue next - } + if (compiledNodes === undefined) return null nodes = compiledNodes } @@ -73,12 +57,7 @@ export function compileCandidates( // Build the node let compiledNodes = compileFn(candidate) - if (compiledNodes === undefined) { - if (throwOnInvalidCandidate) { - throw new Error(`Cannot apply unknown utility class: ${rawCandidate}`) - } - continue next - } + if (compiledNodes === undefined) return null nodes = compiledNodes } @@ -95,23 +74,76 @@ export function compileCandidates( nodes, } - let variantOrder = 0n for (let variant of candidate.variants) { let result = applyVariant(node, variant, designSystem.variants) // When the variant results in `null`, it means that the variant cannot be // applied to the rule. Discard the candidate and continue to the next // one. - if (result === null) { - if (throwOnInvalidCandidate) { - throw new Error(`Cannot apply unknown utility class: ${rawCandidate}`) - } - continue next - } + if (result === null) return null + } - // Track the variant order which is a number with each bit representing a - // variant. This allows us to sort the rules based on the order of - // variants used. + return { + node, + propertySort, + } + }) +} + +export function compileCandidates( + rawCandidates: string[], + designSystem: DesignSystem, + { + parsedVariants = createParsedVariants(designSystem), + parsedCandidates = createParsedCandidates(designSystem, parsedVariants), + parsedAstNodes = createParsedAstNodes(designSystem, parsedCandidates), + invalidRawCandidates = new Set(), + throwOnInvalidCandidate = false, + } = {}, +) { + let nodeSorting = new Map< + AstNode, + { properties: number[]; variants: bigint; candidate: string } + >() + let astNodes: AstNode[] = [] + let candidates = new Map() + + // Parse candidates and variants + for (let rawCandidate of rawCandidates) { + let candidate = parsedCandidates.get(rawCandidate) + if (candidate === null) { + if (throwOnInvalidCandidate) { + throw new Error(`Cannot apply unknown utility class: ${rawCandidate}`) + } + invalidRawCandidates.add(rawCandidate) + continue // Bail, invalid candidate + } + candidates.set(candidate, rawCandidate) + } + + // Sort the variants + let variants = Array.from(parsedVariants.values()).sort((a, z) => { + return designSystem.variants.compare(a, z) + }) + + // Create the AST + next: for (let [candidate, rawCandidate] of candidates) { + let astNode = parsedAstNodes.get(rawCandidate) + if (astNode === null) { + if (throwOnInvalidCandidate) { + throw new Error(`Cannot apply unknown utility class: ${rawCandidate}`) + } + invalidRawCandidates.add(rawCandidate) + continue next + } + + let { node, propertySort } = astNode + + // Track the variant order which is a number with each bit representing a + // variant. This allows us to sort the rules based on the order of + // variants used. + let variantOrder = 0n + for (let variant of candidate.variants) { variantOrder |= 1n << BigInt(variants.indexOf(variant)) } diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 86077ecbb..de124dea3 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -1,7 +1,12 @@ import { Features, transform } from 'lightningcss' import { version } from '../package.json' import { WalkAction, comment, decl, rule, toCss, walk, type AstNode, type Rule } from './ast' -import { compileCandidates } from './compile' +import { + compileCandidates, + createParsedAstNodes, + createParsedCandidates, + createParsedVariants, +} from './compile' import * as CSS from './css-parser' import { buildDesignSystem } from './design-system' import { Theme } from './theme' @@ -102,6 +107,9 @@ export function compile( } let designSystem = buildDesignSystem(theme) + let parsedVariants = createParsedVariants(designSystem) + let parsedCandidates = createParsedCandidates(designSystem, parsedVariants) + let parsedAstNodes = createParsedAstNodes(designSystem, parsedCandidates) let tailwindUtilitiesNode: Rule | null = null @@ -112,7 +120,11 @@ export function compile( tailwindUtilitiesNode = node // Set the `@tailwind utilities` nodes, to the actual generated CSS - node.nodes = compileCandidates(rawCandidates, designSystem).astNodes + node.nodes = compileCandidates(rawCandidates, designSystem, { + parsedVariants, + parsedCandidates, + parsedAstNodes, + }).astNodes // Stop walking after finding `@tailwind utilities` to avoid walking all // of the generated CSS. This means `@tailwind utilities` can only appear @@ -134,6 +146,9 @@ export function compile( { // Parse the candidates to an AST that we can replace the `@apply` rule with. let candidateAst = compileCandidates(candidates, designSystem, { + parsedVariants, + parsedCandidates, + parsedAstNodes, throwOnInvalidCandidate: true, }).astNodes @@ -176,15 +191,23 @@ export function compile( // TODO: Don't do this unless rebuilding? let allCandidates = new Set(rawCandidates) + let invalidRawCandidates = new Set() return { rebuild(newRawCandidates: string[]) { for (let candidate of newRawCandidates) { - allCandidates.add(candidate) + if (!invalidRawCandidates.has(candidate)) { + allCandidates.add(candidate) + } } if (tailwindUtilitiesNode) { - let newNodes = compileCandidates(Array.from(allCandidates), designSystem).astNodes + let newNodes = compileCandidates(Array.from(allCandidates), designSystem, { + parsedVariants, + parsedCandidates, + parsedAstNodes, + invalidRawCandidates, + }).astNodes tailwindUtilitiesNode.nodes = newNodes } return toCss(ast)