Repository navigation
Conversation
`cacheRequests` caches the SVG text, but the content is still parsed into a DOM node and converted into a React element on every mount, so the cost scales with the number of mounts rather than with the number of distinct SVGs. Apps that mount the same handful of SVGs over and over — icons in a virtualized list or table — pay it repeatedly. The new `cacheElements` prop, off by default, keeps the converted element and reuses it. Entries are keyed by the content and by every prop that shapes the output (`title`, `description`, the `preProcessor` result and, for `uniquifyIDs`, the hash and baseURL), so nothing changes about what is rendered. The cache holds the 100 most recently used elements and `clearElementCache()` empties it. Refs gilbarbara#167 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
There was a problem hiding this comment.
🟡 Changes recommended
Cache-key collisions can cause incorrect SVG markup to be reused.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds an opt-in LRU cache for converted SVG React elements.
Changes:
- Adds
cacheElementsandclearElementCache(). - Centralizes conversion and cache-key generation.
- Adds integration, LRU, and documentation coverage.
File summaries
| File | Summary |
|---|---|
test/modules/elementCache.spec.ts |
Tests cache operations and LRU eviction. |
test/cacheElements.spec.tsx |
Tests integration behavior and keying rules. |
src/types.ts |
Defines the cacheElements prop. |
src/modules/utils.ts |
Centralizes conversion and cache-key generation. Critical: unescaped ` |
src/modules/useInlineSVG.ts |
Integrates cached conversion into the hook. |
src/modules/elementCache.ts |
Implements the LRU element cache. |
src/index.tsx |
Exports cache clearing and filters the internal prop. |
src/config.ts |
Defines the cache size limit. |
README.md |
Documents the new cache option. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
fastfrwrd
commented
Sep 14, 2026
|
|
||
| Entries are keyed by the content and by every prop that shapes the output, so `title`, `description`, `preProcessor` and `uniquifyIDs` keep working as they do without it. Instances using `uniquifyIDs` only share an entry when they also share a `uniqueHash`, since the hash ends up in the markup. | ||
|
|
||
| The cache holds the 100 most recently used elements. You can empty it yourself: |
Author
There was a problem hiding this comment.
Maybe this number should be configurable, lmk if you want that and we could add configureElementCache or something?
The two are now separate parts of the cache key, so add the case that the joined version got wrong: a hash containing the separator and a baseURL that moves it, which render different ids and url() references but shared one entry before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The key builder took an anonymous array and left the shaping rules at the call site, which put SVG-specific knowledge — the marker for an undefined title, the uniquifyIDs gating — inside a module that otherwise only deals in strings. It now lives next to convertToElement with its inputs declared as parameters, and owns those rules. elementCache is back to being a plain keyed store, so its spec drops the two tests that only described the old array signature; the keying rules are covered where they are observable, in cacheElements.spec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #255. Revisits the second point of #167.
What this does
cacheRequestscaches the SVG text, but the conversion of that text is not cached:useInlineSVGrunsgetNode()(aDOMParserparse) and a DOM → React conversion on every mount, including on a cache hit — the lazy reducer initializer re-parses cached content. The cost therefore scales with the number of mounts rather than with the number of distinct SVGs, which hurts surfaces that mount the same few SVGs constantly by design: virtualized tables, log lines, trees.This adds a
cacheElementsprop, off by default, that keeps the converted element in a module-level cache and reuses it.How the key works
Entries are keyed by the content and by everything else that shapes the output, so nothing changes about what gets rendered:
preProcessorresult, so two different pre-processors of the samesrcstay separatetitleundefined(keep an existing<title>) is kept distinct fromnull(remove it) and from any stringdescriptionuniquifyIDsbaseURLeach go in as their own part, so no value of one can look like the other; instances with random hashes never share an entry and instances sharing auniqueHashdoThe cache is an LRU capped at 100 entries (
ELEMENT_CACHE_MAX_SIZE), andclearElementCache()is exported for apps that want to empty it. With the prop unset there are no reads, no writes and no growth.convertToElement()and its key builder now hold the parse + convert step thatuseInlineSVGused in two places (the cached-content initializer andgetElement), so both paths share the caching and thepreProcessoris applied exactly once per conversion.Tests
test/cacheElements.spec.tsxandtest/modules/elementCache.spec.ts: 28 tests covering the default-off behaviour (including that the cache stays empty), one conversion for N mounts, per-instance props still applied to the reused element, warmcacheStorecontent, failures not being cached, and one test per keying rule — each of those fails with a wrongtitle/description/fill/id in the DOM, not just a wrong call count.Mutation-tested with StrykerJS on the new code: 95.5% on
elementCache.tsand 87.5% onconvertToElement. The four survivors are equivalent mutants (the marker value used for an undefinedtitle, the empty-string fallback for an absentbaseURL, a cache read that can never hit when caching is off, and astore.delete(undefined)no-op the types require).pnpm run validatepasses: lint, typecheck, 122 tests with coverage thresholds met, build, size (7.83 kB CJS / 7.27 kB ESM against the 10 kB limit) andattw.Verified in a real app
Wired into Grafana with the local build (a
portal:resolution) and one prop added to itsIconcomponent, then measured in a table panel with 300 rows and filterable columns:cacheElementscacheElements, cold cachecacheElements, warm cacheThe interaction script was identical in each case: 4 nav open/close cycles, 15 sort toggles across 5 columns, 8 column-filter popups, and a full scroll of 300 rows in both directions. Parses were counted by wrapping
DOMParser.prototype.parseFromStringand icon mounts by aMutationObserver. Icons rendered correctly throughout — per-instance sizes (16/18/24 px) still applied to the reused elements, no empty SVGs, no console errors.🤖 Generated with Claude Code