Home / Class/ Variants Class — tailwindcss Architecture

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
  }
}

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free