Skip to content

Commit 29eab26

Browse files
Improve performance in large files (#1507)
There are two performance improvements here that both come down to caching: - We call `indexToPosition` *a lot*. Every time this is called it takes the string passed in and re-computes the indices every time. There's no need for this so we have a small, module-level LRU cache to store the data. This should eventually be replaced with parsing and storing data up front when files are edited instead of just doing this whenever we receive a request. That would negate the need for a module-level cache. - We call `compile()` and `candidatesToCss` *a lot* as well. This is expected but 90% of the time the classes passed in have already been seen. We take the generated CSS and convert to a PostCSS node because a lot of the implementation uses that internally. There's no need to go through the whole parse -> serialize -> re-parse routine like we are. I've opted to store the compiled PostCSS roots on the `designSystem.storage` object (which will be created when it doesn't exist on old versions) and we'll return clones of those objects every time (since they may get mutated). With this there's only one large-ish block in a left-heavy profile: `findClassListsInHtmlRange`. That is mostly related to the class attribute lexer. Fixing it will require us to scan document and store info about them when they change. We shouldn't need to run the class attribute lexer if the document hasn't changed. Fixing this will require some more work though. Fixes #1503
1 parent e586933 commit 29eab26

File tree

4 files changed

+49
-9
lines changed

4 files changed

+49
-9
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/find.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Range, Position } from 'vscode-languageserver'
1+
import { type Range, type Position, LRUCache } from 'vscode-languageserver'
22
import type { TextDocument } from 'vscode-languageserver-textdocument'
33
import type { DocumentClassName, DocumentClassList, State, DocumentHelperFunction } from './state'
44
import lineColumn from 'line-column'
@@ -591,8 +591,20 @@ export function findHelperFunctionsInRange(
591591
return fns
592592
}
593593

594+
let lineTableCache = new LRUCache<string, ReturnType<typeof lineColumn>>(20)
595+
596+
function createLineTable(str: string) {
597+
let existing = lineTableCache.get(str)
598+
if (existing) return existing
599+
600+
let table = lineColumn(str + '\n')
601+
lineTableCache.set(str, table)
602+
return table
603+
}
604+
594605
export function indexToPosition(str: string, index: number): Position {
595-
const { line, col } = lineColumn(str + '\n').fromIndex(index) ?? { line: 1, col: 1 }
606+
let table = createLineTable(str)
607+
let { line, col } = table.fromIndex(index) ?? { line: 1, col: 1 }
596608
return { line: line - 1, character: col - 1 }
597609
}
598610

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 {

packages/vscode-tailwindcss/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Prerelease
44

55
- Add a source to all emitted diagnostics ([#1491](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1491))
6+
- Improve performance in large files ([#1507](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1507))
67

78
## 0.14.29
89

0 commit comments

Comments
 (0)