collapseCandidates() — tailwindcss Function Reference
Architecture documentation for the collapseCandidates() function in canonicalize-candidates.ts from the tailwindcss codebase.
Entity Profile
Dependency Diagram
graph TD 830bd29f_3420_9239_4bdf_20fd57106bdf["collapseCandidates()"] 74a4389f_06f5_9213_9ee9_31f7f355cad8["canonicalizeCandidates()"] 74a4389f_06f5_9213_9ee9_31f7f355cad8 -->|calls| 830bd29f_3420_9239_4bdf_20fd57106bdf 03b8d706_a876_a776_0056_186ced5d6067["segment()"] 830bd29f_3420_9239_4bdf_20fd57106bdf -->|calls| 03b8d706_a876_a776_0056_186ced5d6067 24d95be4_356f_a1f9_9702_2a4f413db0f5["add()"] 830bd29f_3420_9239_4bdf_20fd57106bdf -->|calls| 24d95be4_356f_a1f9_9702_2a4f413db0f5 cb44739b_2fa4_1336_6907_dd5fe05389f3["entries()"] 830bd29f_3420_9239_4bdf_20fd57106bdf -->|calls| cb44739b_2fa4_1336_6907_dd5fe05389f3 560e9980_aa5c_b1ac_ff79_9f9c8623d150["keysInNamespaces()"] 830bd29f_3420_9239_4bdf_20fd57106bdf -->|calls| 560e9980_aa5c_b1ac_ff79_9f9c8623d150 1dd167b2_b42d_8596_9276_d0e969bf2522["isValidSpacingMultiplier()"] 830bd29f_3420_9239_4bdf_20fd57106bdf -->|calls| 1dd167b2_b42d_8596_9276_d0e969bf2522 65969186_877b_bb11_99b7_44102e7ff5f2["intersection()"] 830bd29f_3420_9239_4bdf_20fd57106bdf -->|calls| 65969186_877b_bb11_99b7_44102e7ff5f2 d195fa22_71c2_5bb5_0db2_368d87eec97e["combinations()"] 830bd29f_3420_9239_4bdf_20fd57106bdf -->|calls| d195fa22_71c2_5bb5_0db2_368d87eec97e 0aa64a1c_efd8_a69d_48ed_649b7a86c854["get()"] 830bd29f_3420_9239_4bdf_20fd57106bdf -->|calls| 0aa64a1c_efd8_a69d_48ed_649b7a86c854 style 830bd29f_3420_9239_4bdf_20fd57106bdf 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)
}
}
Domain
Subdomains
Calls
Called By
Source
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