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.
|
Hi, thanks for your contribution! |
|
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
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.
|
Good feedback. LRUCache can now be created through a factory. Added some tests for it as well. |
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 644 +5
==========================================
+ 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 Sentry. 🚀 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