Cold start performance improvements (#20427)

<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

Hi guys, thanks for you great work!

I work on performance improvements on
https://github.com/schoero/eslint-plugin-better-tailwindcss project.
Part of issues can be fixed on the tailwind side only.

This is a first fix, I have a bigger one in mind, it would require a
small additional public API method - out of scope of this PR.

Please let me know what you think.

## Finding

`getVariantOrder()` re-sorts all parsed variants with the (expensive)
variant comparator on every call, and it is called by every
compileCandidates() invocation — per build pass, per @apply
substitution, and hundreds of times during candidate canonicalization
via the variant signature caches. Since parsedVariants is append-only,
the computed order only changes when a new variant is parsed, so we can
cache the result and invalidate on parsedVariants.size. This makes cold
canonicalization (IntelliSense, lint plugins) faster and removes
repeated sorting from @apply substitution and incremental rebuilds.

## Test plan

Tested at https://github.com/smnbbrv/better-tailwindcss-bench . The
relevant part is the `epbt-now / tw-patched`

**plugin cost** (ms, cold run net of parse baseline)

| codebase | epbt-now / tw-now | epbt-patched / tw-now | epbt-now /
tw-patched | epbt-patched / tw-patched |
| ---------- | ----------------: | --------------------: |
--------------------: | ------------------------: |
| mixed | 6292 | 3965 (-37%) | 5395 (-14%) | 3595 (-43%) |
| repetitive | 5279 | 2425 (-54%) | 4819 (-9%) | 2124 (-60%) |
| unique | 6096 | 5458 (-10%) | 5507 (-10%) | 5295 (-13%) |
This commit is contained in:
Simon Bobrov 2026-08-31 12:05:03 +02:00 • committed by GitHub
parent 90f8ff41c8
commit 9f451eead8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -75,6 +75,8 @@ export function buildDesignSystem(
let variants = createVariants(theme)
let parsedVariants = new DefaultMap((variant) => parseVariant(variant, designSystem))
let cachedVariantOrder: Map<Variant, number> | null = null
let cachedVariantOrderSize = -1
let parsedCandidates = new DefaultMap((candidate) =>
Array.from(parseCandidate(candidate, designSystem)),
)
@ -188,6 +190,11 @@ export function buildDesignSystem(
},
getVariantOrder() {
// parsedVariants only grows, so the cache should be reset only when the size changes
if (cachedVariantOrder !== null && cachedVariantOrderSize === parsedVariants.size) {
return cachedVariantOrder
}
let variants = Array.from(parsedVariants.values())
variants.sort((a, z) => this.variants.compare(a, z))
@ -209,6 +216,9 @@ export function buildDesignSystem(
prevVariant = variant
}
cachedVariantOrder = order
cachedVariantOrderSize = parsedVariants.size
return order
},