Skip to content

feat(signals,solid): pay-for-use tree-shaking phase 3 — audited cleanups (#2883) - #2890

Merged
ryansolid merged 2 commits into
nextfrom
size/2883-phase-3
Jul 15, 2026
Merged

feat(signals,solid): pay-for-use tree-shaking phase 3 — audited cleanups (#2883)#2890
ryansolid merged 2 commits into
nextfrom
size/2883-phase-3

Conversation

@ryansolid

Copy link
Copy Markdown
Member

Phase 3 of #2883 — every item here passed the cost/benefit audit (wins-population / pays-population / complexity, adversarially verified with byte-exact reproduction). Two analyst-proposed items were rejected by ruling: the event-delegation seam (delegation is the blessed path — all apps are assumed to have it) and the snapshot-capture seam (win too small for a new seam under the weight gate).

Measured (esbuild, minify, --mangle-props='^_', gzip -9)

Fixture before after Δ gzip
Minimal app (published dist) 27,570 / 10,870 26,429 / 10,432 −438
CSR app with <Loading>/lazy 35,565 / 13,935 32,067 / 12,601 −1,334*
Signals core floor 18,921 / 7,687 18,539 / 7,515 −172
Full signals bundle 55,366 / 20,587 54,587 / 20,315 −272 (hook-tax clawback)

*includes ryansolid/dom-expressions#543 (DOMElements dedup) propagating through the rebuilt web runtime.

Cumulative over phases 1–3: minimal app 12,656 → 10,432 gz (−17.6%), signals floor 8,885 → 7,515 gz (−15.4%).

Changes

@solidjs/signals

  • Effect re-enqueue block (4 copies) and the zombie/dirty queue ternaries dedupe into shared enqueueSub/queueFor — the hot write-propagation loop was microbenched against baseline (interleaved 100-fanout write+flush ×20k), no regression.
  • Boundary/reveal internal method names _-prefixed so property mangling reaches them (verified not part of any exported surface; the server's revealGroup.register is a different object).
  • Prod error strings trimmed to their diagnostic codes; dev builds keep the full sentences.
  • Build pipeline fix: the prod dist was shipping with zero /*@__PURE__*/ annotations — rollup-plugin-prettier strips them, and terser only re-emits them with preserve_annotations. Prettier is off the prod tree (the mangle post-pass beautifies anyway), terser preserves, and a new scripts/check-pure.mjs guard fails the build if src has annotations and dist has none.

solid-js (client)

  • sharedConfig.getNextContextId was defined in the object literal, retaining getContext/NoHydrateContext/getNextChildId and the signals context+error machinery in every CSR bundle; it now installs from enableHydration() (type becomes optional; both callers sit behind sharedConfig.hydrating).
  • lazy()'s hydration-module lookup (positional id + _$HY.modules + error string) moves behind an _lazyHydrationLookup slot installed by enableHydration() — the 12th instance of the existing override-slot pattern.
  • MockPromise's static {} block marked the class side-effectful, defeating DCE in every client bundle (the code comment claiming it shakes was wrong); it's now a PURE-annotated factory that shakes with its only consumer.

Packaging

  • unpkg/jsdelivr fields pointed at CJS files (browser-fatal on bare CDN URLs — verified over the network); they now point at browser production ESM for all three packages.

Verification

984 signals + 456 solid + 501 solid-web tests green; both typechecks clean; check-pure guard active in the signals build. Rejected-by-audit items are documented in the analysis (dist minification, standalone CDN bundle, reconcileArrays detach, hydration guards in insert, flatten split, optimistic helper dedup — each with the measured reason).

🤖 Generated with Claude Code

Mechanical cleanups chosen by the phase-3 cost/benefit audit (delegation
seam and snapshot seam rejected by design ruling / weight gate):

signals:
- dedupe effect re-enqueue block (4 copies) + zombie/dirty queue ternaries
  into enqueueSub/queueFor; hot write loop microbenched, no regression
- _-prefix boundary/reveal internal method names so property mangling
  reaches them (verified internal-only)
- trim prod error strings to diagnostic codes; dev keeps full sentences
- stop stripping /*@__PURE__*/ from dist/prod: drop rollup-plugin-prettier
  from the prod tree (it deletes annotations), terser preserve_annotations
  in mangle-props.mjs, new scripts/check-pure.mjs fails the build if
  annotations vanish again

solid-js client:
- sharedConfig.getNextContextId installs from enableHydration(); defining
  it in the object literal retained context/error machinery in every CSR
  bundle (SharedConfig.getNextContextId now optional)
- lazy()'s hydration-module lookup moves behind _lazyHydrationLookup,
  installed by enableHydration()
- MockPromise: class static block defeated DCE in every client bundle;
  now a PURE-annotated factory

packaging: unpkg/jsdelivr fields point at browser prod ESM (were CJS,
browser-fatal on bare CDN URLs)

Measured (esbuild, minify, mangle-props ^_, gzip -9):
- minimal app: 27,570/10,870 -> 26,429/10,432
- CSR app (+Loading/lazy): 35,565/13,935 -> 32,067/12,601 (incl.
  DOMElements dedup propagating through rebuilt web runtime)
- signals floor: 18,921/7,687 -> 18,539/7,515
- full bundle: 55,366/20,587 -> 54,587/20,315 (hook-tax clawback)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5946c0e

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

This PR includes changesets to release 9 packages
Name Type
@solidjs/signals Patch
solid-js Patch
test-integration Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
@solidjs/universal Patch
@solidjs/web Patch
babel-preset-solid 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

@cursor

cursor Bot commented Jul 15, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coveralls

coveralls commented Jul 15, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 29448338455

Warning

No base build found for commit d94d5c3 on next.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 74.74%

Details

  • Patch coverage: 7 uncovered changes across 1 file (15 of 22 lines covered, 68.18%).

Uncovered Changes

File Changed Covered %
packages/solid/src/client/hydration.ts 20 13 65.0%
Total (2 files) 22 15 68.18%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 596
Covered Lines: 477
Line Coverage: 80.03%
Relevant Branches: 461
Covered Branches: 313
Branch Coverage: 67.9%
Branches in Coverage %: Yes
Coverage Strength: 14.28 hits per line

💛 - Coveralls

@codspeed-hq

codspeed-hq Bot commented Jul 15, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 7 untouched benchmarks
⏩ 113 skipped benchmarks1


Comparing size/2883-phase-3 (5946c0e) with next (d94d5c3)

Open in CodSpeed

Footnotes

  1. 113 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.

Rollup literal-tracks never-written module state and folds the dead
branches; esbuild cannot, so these paths shipped only to esbuild-class
consumers. Restructured so both bundlers land in the same place, all
Rollup-neutral by measurement:

- external-source wiring (computed setup + untrack) moves behind
  GlobalQueue hooks whose installs mirror enableExternalSource()'s
  config liveness exactly (installed on enable, removed on reset)
- onlyMarkPending moves from async.ts into affects.ts behind the
  activeAffectsMarks-gated hook (mark-transparency is affects-only)
- the optimistic-store settle loop moves inside the store-side hook
  (_clearOptimisticStores takes the batch; scheduler keeps a
  size-guarded call)

esbuild minimal app: -77 gz from these + -194 gz from the companion
dom-expressions hydration-hook change (ryansolid/dom-expressions#544).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ryansolid
ryansolid merged commit d0b9c91 into next Jul 15, 2026
3 checks passed
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