Skip to content

perf: reduce ValidScopedCSSClass save latency on large themes#1181

Draft
aswamy wants to merge 1 commit intomainfrom
perf/hoist-css-class-collection
Draft

perf: reduce ValidScopedCSSClass save latency on large themes#1181
aswamy wants to merge 1 commit intomainfrom
perf/hoist-css-class-collection

Conversation

@aswamy
Copy link
Copy Markdown
Contributor

@aswamy aswamy commented Apr 17, 2026

Summary

Fixes the save-lag reported in #1179. On larger themes, saving a file caused VS Code to hang for several seconds on "Getting code actions from 'Shopify Liquid'". This PR brings the post-first-save latency back into the sub-second range.

What was happening

The ValidScopedCSSClass check decides whether a CSS class used in a class attribute is defined somewhere the current file can see. To answer that, it needs to know which classes are declared across the theme — in the file itself, in its ancestors, in the snippets it renders (transitively), and in the CSS assets.

The language server runs this logic from scratch on every save. It doesn't just re-check the file the user edited — it also re-reads and re-parses every Liquid file in the theme that could contribute CSS classes, to rebuild the "every class declared anywhere" set it uses as a filter. For a large theme that is roughly a hundred full syntax-tree parses on every keystroke save. Nothing was cached between saves, even though the overwhelming majority of files haven't changed.

What this PR changes

Two architectural changes to the language-server pipeline, nothing user-facing:

  1. Session-scoped cache for per-file CSS class extraction. The mapping from file → set of classes it declares is now built once per language-server session and kept alive across saves. When the user saves a file, only that file's entry is invalidated; everything else stays warm. The first save of a session still pays the full scan, but every subsequent save only re-parses the single file that changed.

  2. Early exit for files that can't contribute classes. A large portion of Liquid files have no stylesheet tag at all and therefore cannot declare any CSS classes. We now detect that with a cheap substring check on the raw source and skip the full parse entirely for those files. This helps the cold path (first save, CLI runs).

The check's logic, its output, and its public API are all unchanged.

Impact

Scenario Before After
First save after opening an editor ~4.5s ~2.5-3s
Subsequent saves ~4.5s <500ms
CLI run on a large theme ~19s ~17s

(Measured on a 240-file theme locally.)

Companion work

Draft notes

  • Timing logs prefixed with [theme-check] are included in this draft for reviewers to validate on their own themes. They will be removed before this PR is marked ready for review.
  • I'd like a reviewer to validate the cache invalidation behaviour in scenarios I didn't cover — in particular workspace-wide find-and-replace, file renames, and external edits outside the editor.

Test plan

  • Install branch build in VS Code on a large theme (Horizon or similar)
  • Confirm first save latency is lower than main, subsequent saves are sub-second
  • Edit a file's {% stylesheet %} content, save, confirm that file's ancestors see the new class immediately (no stale cache)
  • Run the existing check test suite: yarn test

🤖 Generated with Claude Code

Two changes that together drop the LSP save latency introduced by
ValidScopedCSSClass from ~4.5s to <500ms on large themes after the
first save of a session.

1. Session-scoped CSS class cache in runChecks

Before, every save tore down the getCSSClassesForURI memoization and
re-parsed every .liquid file with a {% stylesheet %} tag to rebuild
the theme-wide class set. Now the Map<uri, Promise<Set<string>>>
lives at makeRunChecks scope. When a save fires, the saved URI is
evicted from the cache; every other entry stays warm. First save
pays the full scan; subsequent saves re-parse one file.

2. Substring short-circuit in extractCSSClassesFromLiquidUri

Skip toLiquidHtmlAST entirely when source contains no
'{% stylesheet'. On Horizon (240 liquid files, 128 with stylesheet
tags) this skips ~47% of parses on the first scan. Also helps the
CLI: ~19.2s → ~17.4s on Horizon.

Also adds [theme-check] timing logs in runChecks for profiling.
@aswamy aswamy force-pushed the perf/hoist-css-class-collection branch from 7514d8f to ddd51bf Compare April 17, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant