Skip to content

Add an evalScripts example - #1573

Merged
tanem merged 1 commit into
masterfrom
eval-scripts-example
Aug 4, 2026
Merged

Add an evalScripts example#1573
tanem merged 1 commit into
masterfrom
eval-scripts-example

Conversation

@tanem

@tanem tanem commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Adds examples/eval-scripts, the seventh example. evalScripts is documented in the README but was demonstrated nowhere, and it is the option the Security section largely exists for, which argues for a working reference rather than prose alone.

Two SVGs, each carrying a <script> that increments a counter on the page. One is injected with evalScripts: 'once' and the other with evalScripts: 'always', and a button injects both again. That re-injection is the only thing that separates the two settings: the 'once' counter stops at 1 while the 'always' counter tracks the injection count. The page also renders the number of <script> elements surviving in the injected markup, which is 0 whichever setting is used.

The script in always.svg sits inside a <g> rather than at the root. That is the case that threw NotFoundError on removal before 12.0.0 and jammed the element with no callback of any kind, so the example keeps the fix exercised in a real app rather than only in a fixture.

Re-injection needs a fresh placeholder

No earlier example injects the same file twice, so this one is the first to run into it: the element passed to SVGInjector is replaced by its SVG and is gone from the document, so there is nothing to reuse. The example holds a slot <div> per case and puts a new placeholder in it each time. The README states this, since anyone copying the example to build a re-injecting page hits it immediately.

The cache keeps its copy's scripts

Worth recording because it is what makes the demonstration work at all. loadSvgCached caches the parsed document and hands each waiter a clone, so the strip in evalSvgScripts runs on the clone and the cached original is untouched. Re-injections therefore still find scripts to remove; only ranScripts decides whether they run. Had the strip mutated the cached copy, 'always' would have run once and then had nothing left to evaluate.

No dev-versus-preview divergence

Unlike #1572, vite dev and the build behave identically here: both SVGs live in public/, which the dev server serves verbatim with image/svg+xml. Checked rather than assumed, so the example README has no dev-mode caveat.

Verification

Tests first. The generic example entry plus two dedicated tests were added before the example existed and failed on a waitForFunction timeout, since nothing was injected at that path.

npm test green: check:format, check:types, lint, build (publint, attw), size 4.26 kB ESM / 4.30 kB CJS against the 5.5 kB budget, 360 library tests across chromium, firefox and webkit, then seven examples built against a fresh npm pack and 17 example tests.

Also driven by hand in real Chrome against the built page served over HTTP: counters read 1 and 3 after three injections, <script> elements remaining 0, no console errors.

🤖 Generated with Claude Code

`evalScripts` was documented in the README but demonstrated nowhere, and it
is the option the Security section largely exists for. The example injects
two SVGs whose scripts write counters on the page, one with `'once'` and one
with `'always'`, and a button re-injects both so the difference is visible.

The script in `always.svg` sits inside a `<g>`, which keeps the nested-script
removal fix from 12.0.0 exercised in a real app rather than only in a
fixture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tanem

tanem commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@tanem
tanem merged commit d223167 into master Aug 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant