Home / Function/ collapseCandidates() — tailwindcss Function Reference

collapseCandidates() — tailwindcss Function Reference

Architecture documentation for the collapseCandidates() function in canonicalize-candidates.ts from the tailwindcss codebase.

Entity Profile

Dependency Diagram

graph TD
  71b68838_221b_7f75_2376_3e23d6c37929["collapseCandidates()"]
  0c93fc33_6ff1_7638_3219_621fdee3ade6["canonicalizeCandidates()"]
  0c93fc33_6ff1_7638_3219_621fdee3ade6 -->|calls| 71b68838_221b_7f75_2376_3e23d6c37929
  2a20ea29_c850_cd61_5600_aeebbe3dda66["segment()"]
  71b68838_221b_7f75_2376_3e23d6c37929 -->|calls| 2a20ea29_c850_cd61_5600_aeebbe3dda66
  3ba19013_498f_3c9b_5c44_0eb24efc4394["add()"]
  71b68838_221b_7f75_2376_3e23d6c37929 -->|calls| 3ba19013_498f_3c9b_5c44_0eb24efc4394
  92969a3b_d253_e151_139a_8e2f44014af0["entries()"]
  71b68838_221b_7f75_2376_3e23d6c37929 -->|calls| 92969a3b_d253_e151_139a_8e2f44014af0
  3d6e204b_1a08_95c4_b252_89c58ff8ac22["keysInNamespaces()"]
  71b68838_221b_7f75_2376_3e23d6c37929 -->|calls| 3d6e204b_1a08_95c4_b252_89c58ff8ac22
  c12acffb_80f9_0b95_b1a6_e663fbaca197["isValidSpacingMultiplier()"]
  71b68838_221b_7f75_2376_3e23d6c37929 -->|calls| c12acffb_80f9_0b95_b1a6_e663fbaca197
  ab170d02_96a7_f694_5fc0_cead984d9735["intersection()"]
  71b68838_221b_7f75_2376_3e23d6c37929 -->|calls| ab170d02_96a7_f694_5fc0_cead984d9735
  1afe933f_54d9_6585_5a06_51a83e5a53c8["combinations()"]
  71b68838_221b_7f75_2376_3e23d6c37929 -->|calls| 1afe933f_54d9_6585_5a06_51a83e5a53c8
  4cd99e59_ac1e_2a1f_0946_33cc1afd2532["get()"]
  71b68838_221b_7f75_2376_3e23d6c37929 -->|calls| 4cd99e59_ac1e_2a1f_0946_33cc1afd2532
  style 71b68838_221b_7f75_2376_3e23d6c37929 fill:#6366f1,stroke:#818cf8,color:#fff

Relationship Graph

Source Code

packages/tailwindcss/src/canonicalize-candidates.ts lines 200–417

function collapseCandidates(options: InternalCanonicalizeOptions, candidates: string[]): string[] {
  if (candidates.length <= 1) return candidates
  let designSystem = options.designSystem

  // To keep things simple, we group candidates such that we only collapse
  // candidates with the same variants and important modifier together.
  let groups = new DefaultMap((_before: string) => {
    return new DefaultMap((_after: string) => {
      return new Set<string>()
    })
  })

  let prefix = options.designSystem.theme.prefix ? `${options.designSystem.theme.prefix}:` : ''

  for (let candidate of candidates) {
    let variants = segment(candidate, ':')
    let utility = variants.pop()!

    let important = utility.endsWith('!')
    if (important) {
      utility = utility.slice(0, -1)
    }

    let before = variants.length > 0 ? `${variants.join(':')}:` : ''
    let after = important ? '!' : ''

    // Group by variants and important flag
    groups.get(before).get(after).add(`${prefix}${utility}`)
  }

  let result = new Set<string>()
  for (let [before, group] of groups.entries()) {
    for (let [after, candidates] of group.entries()) {
      for (let candidate of collapseGroup(Array.from(candidates))) {
        // Drop the prefix if we had one, because the prefix is already there as
        // part of the variants.
        if (prefix && candidate.startsWith(prefix)) {
          candidate = candidate.slice(prefix.length)
        }

        result.add(`${before}${candidate}${after}`)
      }
    }
  }

  return Array.from(result)

  function collapseGroup(candidates: string[]) {
    let signatureOptions = options.signatureOptions
    let computeUtilitiesPropertiesLookup =
      designSystem.storage[UTILITY_PROPERTIES_KEY].get(signatureOptions)
    let staticUtilities = designSystem.storage[STATIC_UTILITIES_KEY].get(signatureOptions)

    // For each candidate, compute the used properties and values. E.g.: `mt-1` → `margin-top` → `0.25rem`
    //
    // NOTE: Currently assuming we are dealing with static utilities only. This
    // will change the moment we have `@utility` for most built-ins.
    let candidatePropertiesValues = candidates.map((candidate) =>
      computeUtilitiesPropertiesLookup.get(candidate),
    )

    // Hard-coded optimization: if any candidate sets `line-height` and another
    // candidate sets `font-size`, we pre-compute the `text-*` utilities with
    // this line-height to try and collapse to those combined values.
    if (candidatePropertiesValues.some((x) => x.has('line-height'))) {
      let fontSizeNames = designSystem.theme.keysInNamespaces(['--text'])
      if (fontSizeNames.length > 0) {
        let interestingLineHeights = new Set<string | number>()
        let seenLineHeights = new Set<string>()
        for (let pairs of candidatePropertiesValues) {
          for (let lineHeight of pairs.get('line-height')) {
            if (seenLineHeights.has(lineHeight)) continue
            seenLineHeights.add(lineHeight)

            let bareValue = designSystem.storage[SPACING_KEY]?.get(lineHeight) ?? null
            if (bareValue !== null) {
              if (isValidSpacingMultiplier(bareValue)) {
                interestingLineHeights.add(bareValue)

                for (let name of fontSizeNames) {
                  computeUtilitiesPropertiesLookup.get(`text-${name}/${bareValue}`)
                }
              } else {
                interestingLineHeights.add(lineHeight)

                for (let name of fontSizeNames) {
                  computeUtilitiesPropertiesLookup.get(`text-${name}/[${lineHeight}]`)
                }
              }
            }
          }
        }

        let seenFontSizes = new Set<string>()
        for (let pairs of candidatePropertiesValues) {
          for (let fontSize of pairs.get('font-size')) {
            if (seenFontSizes.has(fontSize)) continue
            seenFontSizes.add(fontSize)

            for (let lineHeight of interestingLineHeights) {
              if (isValidSpacingMultiplier(lineHeight)) {
                computeUtilitiesPropertiesLookup.get(`text-[${fontSize}]/${lineHeight}`)
              } else {
                computeUtilitiesPropertiesLookup.get(`text-[${fontSize}]/[${lineHeight}]`)
              }
            }
          }
        }
      }
    }

    // For each property, lookup other utilities that also set this property and
    // this exact value. If multiple properties are used, use the intersection of
    // each property.
    //
    // E.g.: `margin-top` → `mt-1`, `my-1`, `m-1`
    let otherUtilities = candidatePropertiesValues.map((propertyValues) => {
      let result: Set<string> | null = null
      for (let property of propertyValues.keys()) {
        let otherUtilities = new Set<string>()
        for (let group of staticUtilities.get(property).values()) {
          for (let candidate of group) {
            otherUtilities.add(candidate)
          }
        }

        if (result === null) result = otherUtilities
        else result = intersection(result, otherUtilities)

        // The moment no other utilities match, we can stop searching because
        // all intersections with an empty set will remain empty.
        if (result!.size === 0) return result!
      }
      return result!
    })

    // Link each candidate that could be linked via another utility
    // (intersection). This way we can reduce the amount of required combinations.
    //
    // E.g.: `mt-1` and `mb-1` can be linked via `my-1`.
    //
    // Candidates that cannot be linked won't be able to be collapsed.
    // E.g.: `mt-1` and `text-red-500` cannot be collapsed because there is no 3rd
    // utility with overlapping property/value combinations.
    let linked = new DefaultMap<number, Set<number>>((key) => new Set<number>([key]))
    for (let i = 0; i < otherUtilities.length; i++) {
      let current = otherUtilities[i]
      for (let j = i + 1; j < otherUtilities.length; j++) {
        let other = otherUtilities[j]

        for (let property of current) {
          if (other.has(property)) {
            linked.get(i).add(j)
            linked.get(j).add(i)

            // The moment we find a link, we can stop comparing and move on to the
            // next candidate. This will safe us some time
            break
          }
        }
      }
    }

    // Not a single candidate can be linked to another one, nothing left to do
    if (linked.size === 0) return candidates

    // Each candidate index will now have a set of other candidate indexes as
    // its value. Let's make the lists unique combinations so that we can
    // iterate over them.
    let uniqueCombinations = new DefaultMap((key: string) => key.split(',').map(Number))
    for (let group of linked.values()) {
      let sorted = Array.from(group).sort((a, b) => a - b)
      uniqueCombinations.get(sorted.join(','))
    }

    // Let's try to actually collapse them now.
    let result = new Set<string>(candidates)
    let drop = new Set<string>()

    for (let idxs of uniqueCombinations.values()) {
      for (let combo of combinations(idxs)) {
        if (combo.some((idx) => drop.has(candidates[idx]))) continue // Skip already dropped items

        let potentialReplacements = combo.flatMap((idx) => otherUtilities[idx]).reduce(intersection)

        let collapsedSignature = designSystem.storage[UTILITY_SIGNATURE_KEY].get(
          signatureOptions,
        ).get(
          combo
            .map((idx) => candidates[idx])
            .sort((a, z) => a.localeCompare(z)) // Sort to increase cache hits
            .join(' '),
        )

        for (let replacement of potentialReplacements) {
          let signature =
            designSystem.storage[UTILITY_SIGNATURE_KEY].get(signatureOptions).get(replacement)
          if (signature !== collapsedSignature) continue // Not a safe replacement

          // We can replace all items in the combo with the replacement
          for (let item of combo) {
            drop.add(candidates[item])
          }

          // Use the replacement
          result.add(replacement)
          break
        }
      }
    }

    for (let item of drop) {
      result.delete(item)
    }

    return Array.from(result)
  }
}

Subdomains

Frequently Asked Questions

What does collapseCandidates() do?
collapseCandidates() is a function in the tailwindcss codebase.
What does collapseCandidates() call?
collapseCandidates() calls 8 function(s): add, combinations, entries, get, intersection, isValidSpacingMultiplier, keysInNamespaces, segment.
What calls collapseCandidates()?
collapseCandidates() is called by 1 function(s): canonicalizeCandidates.

Analyze Your Own Codebase

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

Try Supermodel Free