cache all the things

This commit is contained in:
Robin Malfait 2024-03-07 20:51:18 +01:00
parent 6282e2b22e
commit 27e06c76d2
No known key found for this signature in database
2 changed files with 111 additions and 56 deletions

View file

@ -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<string, Variant | null> {
// 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<string, Variant | null> = new DefaultMap((variant, map) => {
return new DefaultMap((variant, map) => {
return parseVariant(variant, designSystem.variants, map)
})
}
let candidates = new Map<Candidate, string>()
// 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<string, Variant | null>,
) {
return new DefaultMap((candidate) => {
return parseCandidate(candidate, designSystem.utilities, parsedVariants)
})
}
export function createParsedAstNodes(
designSystem: DesignSystem,
parsedCandidates: DefaultMap<string, Candidate | null>,
) {
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<string>(),
throwOnInvalidCandidate = false,
} = {},
) {
let nodeSorting = new Map<
AstNode,
{ properties: number[]; variants: bigint; candidate: string }
>()
let astNodes: AstNode[] = []
let candidates = new Map<Candidate, string>()
// 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))
}

View file

@ -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<string>()
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)