Cache the transform output per file - #55
Conversation
The blanked passes of a `.gts`/`.gjs` file are a function of its content, the tsconfig and the environment switches, so they are now cached on disk next to the Glint result (`.../html-validate-ember/transform/`). On a hit the transformer replays the passes: no content-tag parse, no Glint extraction, no blanking. `transformGlimmer` now computes the serialisable template entries in `computeTemplates` and yields from them either way. The plugin-source hash that invalidates both caches covered only `lib/`; it now covers the root sources (`blank`, `transform`, …) as well, so an edit to the blanker invalidates cached output. Cowritten by Claude
🏎️ Benchmark Comparison
Full output |
There was a problem hiding this comment.
🔵 Needs a closer look
The new caching layer introduces subtle invalidation and serialization behavior (including a backend-detection keying gap versus the sibling Glint cache) that warrants human verification, and the new cache functions lack the unit coverage the existing cache has.
Pull request overview
This PR adds a second on-disk cache layer to the html-validate-ember plugin: it stores the blanked <template> passes of a .gts/.gjs file (with their hook inputs) under node_modules/.cache/html-validate-ember/transform/, so repeat runs replay the stored passes and skip content-tag parsing, Glint extraction, Glimmer parsing, and blanking entirely. It fits alongside the existing Glint extraction cache and builds on top of the parse-memoisation work in #54.
Changes:
- Split
transformGlimmerinto a purecomputeTemplates(produces serialisableCachedTemplate[]) plus a generator that replays those entries whether they came from cache or were freshly computed. - Added
transformCacheKey/readTransformCache/writeTransformCacheand theCachedPass/CachedTemplateshapes inlib/cache.ts, keyed on file content + tsconfig SHA + env switches, bypassed byHVE_NO_CACHE=1. - Widened the plugin-source SHA to cover the package root sources (
blank.ts/transform.ts) pluslib/, fixing a latent invalidation gap where blanker edits didn't invalidate cached results.
File summaries
| File | Description |
|---|---|
| transform.ts | Splits the .gts/.gjs transform into computeTemplates + a cache-aware replay generator; wires in the transform cache. |
| lib/cache.ts | Adds the transform-output cache (key, read, write, serializable types) and broadens the plugin-source SHA walk to the package root plus lib/. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 3
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The key now carries the backend `backendFor` would select (forced kind, or the TypeScript 7 package name and version when the tsconfig declares `contentMappers`), resolved without loading TypeScript. A stored glimmer parse error is re-emitted on replay, so the diagnostic no longer depends on cache state. Round-trip and key-mismatch tests. Cowritten by Claude
The key carries the backend kind from `backendKindFor` (as the transform cache does since #55) instead of the raw `HVE_TS_BACKEND`. Round-trip, miss-on-any-input-change and one-entry-per-path tests. Cowritten by Claude
* CLI: replay the report of unchanged files `validate-gts` caches each file's deduplicated html-validate report, keyed by the file's content, the resolved configuration, html-validate's version, the tsconfig and the environment switches (under `.../html-validate-ember/report/`). An unchanged file is not validated again; its report is replayed into the same counters and output. Console app (838 files), warm run: 3.3 s -> 0.3 s. Output identical to an uncached run; `HVE_NO_CACHE=1` bypasses it like the other caches. Cowritten by Claude * Report cache: key on the resolved backend, tests The key carries the backend kind from `backendKindFor` (as the transform cache does since #55) instead of the raw `HVE_TS_BACKEND`. Round-trip, miss-on-any-input-change and one-entry-per-path tests. Cowritten by Claude
The blanked passes of a
.gts/.gjsfile are a pure function of the file's content, the tsconfig (through Glint) and the environment switches (HVE_GLINT,HVE_TS_BACKEND,HVE_MAX_CONDITIONAL_BRANCHES), so they are now cached on disk next to the Glint result, undernode_modules/.cache/html-validate-ember/transform/. On a hit the transformer replays the stored passes and their hook inputs (dynamic-content offsets, attribute injections, per-element disables): no content-tag parse, no Glint extraction, no Glimmer parse, no blanking.HVE_NO_CACHE=1bypasses it like the Glint cache.transformGlimmeris split:computeTemplatesproduces serialisable template entries; the generator yields from those entries whether they came from the cache or were just computed. Output is identical (both lanes: 291 + 1 expected fail).Also fixes a latent invalidation gap: the plugin-source hash covered only
lib/, butblank.ts/transform.tslive at the package root, so an edit to the blanker did not invalidate cached Glint results and would not have invalidated cached transform output. The hash now covers root sources pluslib/.Same cross-file caveat as the Glint cache: the key is this file's content, so a change in an imported component's template does not invalidate a consumer's cached output until the consumer changes (or the plugin version does). That trade-off already existed for the resolver's results inside the Glint cache; this PR extends it to the blanked output. If that becomes a problem the key should include the resolved import set.
Measured (
pnpm bench:compare, base without the large template): warm run −13 %, one cached file −18 %. On the console app (838 files) the warm run goes from 5.8 s (with #54) to the number in the follow-up comment.Stacks cleanly on top of #54 (one import line touches the same spot).
Cowritten by Claude