Variants Class — tailwindcss Architecture
Architecture documentation for the Variants class in variants.ts from the tailwindcss codebase.
Entity Profile
Relationship Graph
Source Code
packages/tailwindcss/src/variants.ts lines 39–317
export class Variants {
public compareFns = new Map<number, CompareFn>()
public variants = new Map<
string,
{
kind: Variant['kind']
order: number
applyFn: VariantFn<any>
// The kind of rules that are allowed in this compound variant
compoundsWith: Compounds
// The kind of rules that are generated by this variant
// Determines whether or not a compound variant can use this variant
compounds: Compounds
}
>()
private completions = new Map<string, () => string[]>()
/**
* Registering a group of variants should result in the same sort number for
* all the variants. This is to ensure that the variants are applied in the
* correct order.
*/
private groupOrder: null | number = null
/**
* Keep track of the last sort order instead of using the size of the map to
* avoid unnecessarily skipping order numbers.
*/
private lastOrder = 0
static(
name: string,
applyFn: VariantFn<'static'>,
{ compounds, order }: { compounds?: Compounds; order?: number } = {},
) {
this.set(name, {
kind: 'static',
applyFn,
compoundsWith: Compounds.Never,
compounds: compounds ?? Compounds.StyleRules,
order,
})
}
fromAst(name: string, ast: AstNode[], designSystem: DesignSystem) {
let selectors: string[] = []
let usesAtVariant = false
walk(ast, (node) => {
if (node.kind === 'rule') {
selectors.push(node.selector)
} else if (node.kind === 'at-rule' && node.name === '@variant') {
usesAtVariant = true
} else if (node.kind === 'at-rule' && node.name !== '@slot') {
selectors.push(`${node.name} ${node.params}`)
}
})
this.static(
name,
(r) => {
let body = ast.map(cloneAstNode)
if (usesAtVariant) substituteAtVariant(body, designSystem)
substituteAtSlot(body, r.nodes)
r.nodes = body
},
{ compounds: compoundsForSelectors(selectors) },
)
}
functional(
name: string,
applyFn: VariantFn<'functional'>,
{ compounds, order }: { compounds?: Compounds; order?: number } = {},
) {
this.set(name, {
kind: 'functional',
applyFn,
compoundsWith: Compounds.Never,
compounds: compounds ?? Compounds.StyleRules,
order,
})
}
compound(
name: string,
compoundsWith: Compounds,
applyFn: VariantFn<'compound'>,
{ compounds, order }: { compounds?: Compounds; order?: number } = {},
) {
this.set(name, {
kind: 'compound',
applyFn,
compoundsWith,
compounds: compounds ?? Compounds.StyleRules,
order,
})
}
group(fn: () => void, compareFn?: CompareFn) {
this.groupOrder = this.nextOrder()
if (compareFn) this.compareFns.set(this.groupOrder, compareFn)
fn()
this.groupOrder = null
}
has(name: string) {
return this.variants.has(name)
}
get(name: string) {
return this.variants.get(name)
}
kind(name: string) {
return this.variants.get(name)?.kind!
}
compoundsWith(parent: string, child: string | Variant) {
let parentInfo = this.variants.get(parent)
let childInfo =
typeof child === 'string'
? this.variants.get(child)
: child.kind === 'arbitrary'
? // This isn't strictly necessary but it'll allow us to bail quickly
// when parsing candidates
{ compounds: compoundsForSelectors([child.selector]) }
: this.variants.get(child.root)
// One of the variants don't exist
if (!parentInfo || !childInfo) return false
// The parent variant is not a compound variant
if (parentInfo.kind !== 'compound') return false
// The variant `parent` may _compound with_ `child` if `parent` supports the
// rules that `child` generates. We instead use static registration metadata
// about what `parent` and `child` support instead of trying to apply the
// variant at runtime to see if the rules are compatible.
// The `child` variant cannot compound *ever*
if (childInfo.compounds === Compounds.Never) return false
// The `parent` variant cannot compound *ever*
// This shouldn't ever happen because `kind` is `compound`
if (parentInfo.compoundsWith === Compounds.Never) return false
// Any rule that `child` may generate must be supported by `parent`
if ((parentInfo.compoundsWith & childInfo.compounds) === 0) return false
return true
}
suggest(name: string, suggestions: () => string[]) {
this.completions.set(name, suggestions)
}
getCompletions(name: string) {
return this.completions.get(name)?.() ?? []
}
compare(a: Variant | null, z: Variant | null): number {
if (a === z) return 0
if (a === null) return -1
if (z === null) return 1
if (a.kind === 'arbitrary' && z.kind === 'arbitrary') {
// SAFETY: The selectors don't need to be checked for equality as they
// are guaranteed to be unique since we sort a list of de-duped variants
return a.selector < z.selector ? -1 : 1
} else if (a.kind === 'arbitrary') {
return 1
} else if (z.kind === 'arbitrary') {
return -1
}
let aOrder = this.variants.get(a.root)!.order
let zOrder = this.variants.get(z.root)!.order
let orderedByVariant = aOrder - zOrder
if (orderedByVariant !== 0) return orderedByVariant
if (a.kind === 'compound' && z.kind === 'compound') {
let order = this.compare(a.variant, z.variant)
if (order !== 0) return order
if (a.modifier && z.modifier) {
// SAFETY: The modifiers don't need to be checked for equality as they
// are guaranteed to be unique since we sort a list of de-duped variants
return a.modifier.value < z.modifier.value ? -1 : 1
} else if (a.modifier) {
return 1
} else if (z.modifier) {
return -1
} else {
return 0
}
}
let compareFn = this.compareFns.get(aOrder)
if (compareFn !== undefined) return compareFn(a, z)
if (a.root !== z.root) return a.root < z.root ? -1 : 1
// SAFETY: Variants `a` and `z` are both functional at this point. Static
// variants are de-duped by the `DefaultMap` and checked earlier.
let aValue = (a as Extract<Variant, { kind: 'functional' }>).value
let zValue = (z as Extract<Variant, { kind: 'functional' }>).value
// While no functional variant in core supports a "default" value the parser
// will see something like `data:flex` and still parse and store it as a
// functional variant even though it actually produces no CSS. This means
// that we need to handle the case where the value is `null` here. Even
// though _for valid utilities_ this never happens.
if (aValue === null) return -1
if (zValue === null) return 1
// Variants with arbitrary values should appear after any with named values
if (aValue.kind === 'arbitrary' && zValue.kind !== 'arbitrary') return 1
if (aValue.kind !== 'arbitrary' && zValue.kind === 'arbitrary') return -1
// SAFETY: The values don't need to be checked for equality as they are
// guaranteed to be unique since we sort a list of de-duped variants. The
// only way this could matter would be when two different variants parse to
// the same AST. That is only possible with arbitrary values when spaces are
// involved. e.g. `data-[a_b]:flex` and `data-[a ]:flex` but this is not a
// concern for us because spaces are not allowed in variant names.
return aValue.value < zValue.value ? -1 : 1
}
keys() {
return this.variants.keys()
}
entries() {
return this.variants.entries()
}
private set<T extends Variant['kind']>(
name: string,
{
kind,
applyFn,
compounds,
compoundsWith,
order,
}: {
kind: T
applyFn: VariantFn<T>
compoundsWith: Compounds
compounds: Compounds
order?: number
},
) {
let existing = this.variants.get(name)
if (existing) {
Object.assign(existing, { kind, applyFn, compounds })
} else {
if (order === undefined) {
this.lastOrder = this.nextOrder()
order = this.lastOrder
}
this.variants.set(name, {
kind,
applyFn,
order,
compoundsWith,
compounds,
})
}
}
private nextOrder() {
return this.groupOrder ?? this.lastOrder + 1
}
}
Domain
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free