Skip to content

perf(compiler): drop DOM walks to nodes nothing reads - #4714

Merged
ryansolid merged 1 commit into
solidjs:nextfrom
lxsmnsyc:perf/drop-unused-dom-walks
Nov 8, 2026
Merged

ryansolid merged 1 commit into
solidjs:nextfrom
lxsmnsyc:perf/drop-unused-dom-walks

Conversation

@lxsmnsyc

@lxsmnsyc lxsmnsyc commented Nov 7, 2026

Copy link
Copy Markdown
Member

DOM output no longer declares positional walks to nodes that nothing reads. These walks shipped as bare property reads such as _el$2.firstChild;, because a minifier keeps a read that could be a getter.

Changes

  • prune_unused_walks in packages/compiler/src/dom/element.rs drops a var x = y.firstChild or y.nextSibling declaration when no later statement reads x.
  • It runs from the last declaration back, so a walk that only feeds a dropped walk is dropped too.
  • The statements it checks are the inserts, attribute setters and the batched effect of the same template root.
  • pruneUnusedWalks in packages/babel-plugin/src/dom/template.ts does the same, so both compilers emit equal output.
  • 23 Babel and 15 compiler DOM fixture snapshots lose their unused walks.

Example, <h1>User : {name()}</h1>:

// before
var _el$ = _tmpl$(), _el$2 = _el$.firstChild;
_$insert(_el$, name, null);
// after
var _el$ = _tmpl$();
_$insert(_el$, name, null);

Proof

  • packages/compiler: cargo test, cargo clippy -- -D warnings and vitest run pass (6880 tests), including Babel parity.
  • packages/babel-plugin: pnpm test passes (414 tests).
  • packages/web: client, server and hydration suites pass (1556, 1905 and 712 tests). These compile through the Babel plugin, so they run the pruned output.

Cost

Client gzip of the Vite builds of the repo's 13 examples, against the same base:

Example Change
effect -24 B
canvas -10 B
todos -10 B
hackernews-spa -6 B
notes -4 B
other 8 0 B
  • Hydrating builds do not change.
  • Server builds change by 1 to 3 B from gzip noise. The SSR transform is not touched.
  • The measurements come from solid-optimizer (debates/v0.5.0/evidence/unused-walks-solid-examples.txt). Its own examples show -7 to -43 B.
  • Compile time adds one identifier visit over each template root's statements.

Risk

  • A walk is dropped only when it is a plain firstChild or nextSibling read. No later statement of the same template root may read its name. A wrong drop would show up as a ReferenceError in the web suites, and none happened.

🤖 Generated with Claude Code

- Positional ids are handed out before the compiler knows whether a
  child's insert needs a marker. A text node ahead of an appended
  expression got `var _el$3 = _el$2.firstChild` that nothing read.
- Minifiers keep the read, since it could be a getter.
- Both compilers now drop a `firstChild` or `nextSibling` walk when no
  later statement reads its node.
- Client gzip on the repo's examples changes by 0 to -24 B. Hydrating
  builds do not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Nov 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e938536

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 15 packages
Name Type
@solidjs/compiler Patch
@solidjs/babel-plugin Patch
test-integration Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
@solidjs/signals Patch
solid-js Patch
@solidjs/universal Patch
@solidjs/web Patch
board-example Patch
canvas-example Patch
server-dbmon-example Patch
todos-server-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@lxsmnsyc
lxsmnsyc marked this pull request as ready for review November 7, 2026 09:46
@codspeed

codspeed Bot commented Nov 7, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

1 warning

⚠️ 1 benchmark is dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 201 untouched benchmarks
⏩ 5 skipped benchmarks1


Comparing lxsmnsyc:perf/drop-unused-dom-walks (e938536) with next (8138dab)

Open in CodSpeed

Footnotes

  1. 5 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@ryansolid
ryansolid merged commit bf71c28 into solidjs:next Nov 8, 2026
16 checks passed
ryansolid added a commit that referenced this pull request Nov 8, 2026
… (#4718)

* chore(size): batch re-record after #4714, #4711 and #4717

Live page floor 40.66 -> 40.71 KB (minified unchanged; #4717 measured
without #4714). Compiled live recorded minified +2 B, renderToString +13 B.
Measured over next @ 46a72c0, matching size job 128670072096.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE

* chore(size): re-measure the batch over #4719

Live page floor 40.68 KB (122,849 B), compiled live 44.79 KB (131,098 B),
measured over next @ 9c66532.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants