Skip to content

Commit b75417f

Browse files
committed
Cache compiled PostCSS roots per-class
This will help perf in two cases: 1. Repeated compilation of the same classes across requests 2. Compilation *of the same* class mulitple times in a single call We store this data on the design system itself so that it gets invalidated and garbage collected when the design system is recreated on CSS file changes
1 parent b74ff12 commit b75417f

File tree

2 files changed

+34
-7
lines changed

2 files changed

+34
-7
lines changed

packages/tailwindcss-language-server/src/util/v4/design-system.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { plugins } from './plugins'
1313

1414
const HAS_V4_IMPORT = /@import\s*(?:'tailwindcss'|"tailwindcss")/
1515
const HAS_V4_THEME = /@theme\s*\{/
16+
const COMPILE_CACHE = Symbol('LSP_COMPILE_CACHE')
1617

1718
export async function isMaybeV4(css: string): Promise<boolean> {
1819
// Look for:
@@ -215,6 +216,11 @@ export async function loadDesignSystem(
215216
}),
216217
})
217218

219+
// This object doesn't exist in older versions but we can patch it in so it
220+
// seems like it always existed.
221+
design.storage ??= {}
222+
design.storage[COMPILE_CACHE] = {}
223+
218224
// Step 4: Augment the design system with some additional APIs that the LSP needs
219225
Object.assign(design, {
220226
dependencies: () => dependencies,
@@ -227,25 +233,42 @@ export async function loadDesignSystem(
227233
// - Replace `candidatesToCss` with a `candidatesToAst` API
228234
// First step would be to convert to a PostCSS AST by transforming the nodes directly
229235
// Then it would be to drop the PostCSS AST representation entirely in all v4 code paths
230-
compile(classes: string[]): (postcss.Root | null)[] {
231-
let css = design.candidatesToCss(classes)
236+
compile(classes: string[]): postcss.Root[] {
237+
// 1. Compile any uncached classes
238+
let cache = design.storage[COMPILE_CACHE] as Record<string, postcss.Root>
239+
let uncached = classes.filter((name) => cache[name] === undefined)
240+
241+
let css = design.candidatesToCss(uncached)
232242
let errors: any[] = []
233243

234-
let roots = css.map((str) => {
235-
if (str === null) return postcss.root()
244+
for (let [idx, cls] of uncached.entries()) {
245+
let str = css[idx]
246+
247+
if (str === null) {
248+
cache[cls] = postcss.root()
249+
continue
250+
}
236251

237252
try {
238-
return postcss.parse(str.trimEnd())
253+
cache[cls] = postcss.parse(str.trimEnd())
239254
} catch (err) {
240255
errors.push(err)
241-
return postcss.root()
256+
cache[cls] = postcss.root()
257+
continue
242258
}
243-
})
259+
}
244260

245261
if (errors.length > 0) {
246262
console.error(JSON.stringify(errors))
247263
}
248264

265+
// 2. Pull all the classes from the cache
266+
let roots: postcss.Root[] = []
267+
268+
for (let cls of classes) {
269+
roots.push(cache[cls].clone())
270+
}
271+
249272
return roots
250273
},
251274

packages/tailwindcss-language-service/src/util/v4/design-system.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ export interface DesignSystem {
5050

5151
// Added in v4.1.15
5252
canonicalizeCandidates?(classes: string[], options?: CanonicalizeOptions): string[]
53+
54+
// Added in v4.1.16
55+
// We can patch it into any design system if it doesn't exist though
56+
storage?: Record<symbol, any>
5357
}
5458

5559
export interface DesignSystem {

0 commit comments

Comments
 (0)