Uh oh!
There was an error while loading. Please reload this page.
perf(css-processor): cache compiled inline CSS strings - #39
Conversation
Memoize `CSSProcessor.compileInlineCSS` results with a bounded LRU (256 entries). Inline `style="..."` strings repeat heavily in real-world HTML (design-system markup, syntax-highlighted code spans, table cells), and recompiling the same string for each occurrence is pure waste — the output `CSSProcessedProps` is immutable post-construction.
5ZYSZ3K
commented
May 10, 2026
Hi, thanks for your contribution! |
pataar
commented
May 10, 2026
All good. Thanks and no hurries |
| if (cache.size >= INLINE_CSS_CACHE_LIMIT) { | ||
| // Evict oldest (Map preserves insertion order). | ||
| const oldest = cache.keys().next().value; | ||
| if (oldest !== undefined) { |
There was a problem hiding this comment.
What does this check protect us from? undefined is fine as a map key
There was a problem hiding this comment.
I could force it, but the check is cheap i guess
There was a problem hiding this comment.
Okay, let's keep it that way, I just checked the behaviour in a JS environment and it sticked out
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Address PR review feedback: - Move LRU logic out of `CSSProcessor` into a generic `createLRUCache` closure factory in `lruCache.ts`. - Gate the cache behind `CSSProcessorConfig.enableExperimentalCssLRUCache` (default `false`) so it stays opt-in given the memory trade-off. - Add `CSSProcessorConfig.maxCssLruCacheSize` (default 256) for tuning. - Drop the redundant `oldest !== undefined` guard left over from the previous implementation; `Map.delete(undefined)` is harmless anyway. - Cover the new behavior with tests (default off, flag on, eviction, touch-keeps-alive).
`set` was evicting the oldest entry whenever `map.size >= maxSize`, even if the key being written already existed in the map. Replacing an entry updates in place — it doesn't free a slot — so the eviction was dropping unrelated data. Skip the eviction when `map.has(key)` is true and add a dedicated `lruCache.test.ts` covering the contract, including a regression case for the replace-doesn't-evict invariant.
pataar
commented
May 11, 2026
Good feedback. LRUCache can now be created through a factory. Added some tests for it as well. |
Uh oh!
There was an error while loading. Please reload this page.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@## main #39 +/- ##
==========================================
+ Coverage 98.65% 98.67% +0.01%
==========================================
Files 140 141 +1 Lines 2085 2111 +26 Branches 639 641 +2 ==========================================
+ Hits 2057 2083 +26
Misses 27 27 Partials 1 1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|

Memoize
CSSProcessor.compileInlineCSSresults in a bounded LRU (256 entries). The samestyle="..."string returns the sameCSSProcessedProps— skipping parse + validate on every repeat.Safe: the compiled
CSSProcessedPropsis only mutated duringCSSInlineParseRun.exec(); nothing downstream writes to it (merge()always returns a fresh instance).Benchmarks
benchmark.jsagainstTRenderEngine.buildTTree()end-to-end (full hoist + whitespace-collapse). 3 runs per workload, mean reported, RME <1%.rnImageDoctranslate-onlyrnImageDocfull pipelinecssHeavy(200)full pipelineFixtures:
rnImageDoc— the existing 65 KB Docusaurus snippet inpackages/performance-testing/html-sources.js. Diverse inline styles.cssHeavy(200)— synthetic 200-element doc where<div>/<span>/<p>carry repeated design-system-shaped inline styles (mixedpx/em/%, colors, spacing). Models the repetition the cache targets.The diverse-content case is neutral; the win shows up wherever inline styles repeat — common in production.
Test plan
yarn test:css-processor— 1414 passyarn test:transient-render-engine— 191 + 66 snapshots passyarn test:render— 262 passyarn test:transient-render-engine:perfpasses thresholds