Background
Solid 2.0's library size has grown substantially over 1.x, driven almost entirely by @solidjs/signals absorbing the async model (transitions, optimistic lanes, pending sources/status rails, boundaries, projections, stores). Current measurements (all uniform: esbuild bundle + minify + gzip, production defines, Jul 2026):
Full runtime (whole packages):
|
Minified |
Gzip |
| Solid 1.9.14 (core + store + web) |
47.4 KB |
~18.1 KB |
| Solid 2.0 beta (signals + solid + web) |
89.5 KB |
~33.5 KB |
| Svelte 5.56 (full client runtime) |
102.6 KB |
36.8 KB |
| React 19.2 + ReactDOM client |
193.4 KB |
60.3 KB |
Tree-shaken minimal app (render + one signal):
|
Minified |
Gzip |
| Solid 1.x |
10.6 KB |
4.2 KB |
| Solid 2.0 |
31.6 KB |
12.6 KB |
Svelte 5 (mount) |
39.7 KB |
14.9 KB |
| React (no shaking) |
193.4 KB |
60.3 KB |
Excluding the web runtime, the library proper is ~2.2–2.5x its 1.x size. We compare favorably against Svelte 5 and React today, but the minimal-app floor (12.6 KB gzip) means a hello-world app loads most of the async machinery whether it uses it or not.
Root cause of the high floor
dist/prod.js is one flat rollup bundle. Once an app imports anything, all top-level side effects in the file are retained — including the GlobalQueue._* hook installs that wire subsystems together. Module-level tree-shaking can't separate subsystems that live in the same output chunk.
- Direct cross-subsystem calls. Some feature machinery is invoked directly from hot core paths (e.g. store proxy traps), making it reachable from
createStore even when the feature is unused.
Proposed direction
- Split the published dist into chunks (rollup code splitting;
index re-exports from feature chunks). All packages already declare sideEffects: false, so bundlers will drop unused chunks. Needs care around the dev/prod/node output triplet and __DEV__ replacement.
- Convert remaining direct couplings to the existing nullable-hook pattern (
GlobalQueue._propagateAffects, _settleAffects, _clearOptimisticStore already demonstrate it), installed by the feature module on import.
- Add a bundle-fixture test asserting the minimal entry's size/import graph, so shaking doesn't regress silently.
Candidates (rough order of value)
affects() — seams already in place (GlobalQueue._propagateAffects/_settleAffects); remaining work is hook-converting witnessAffectsMark (get/has traps, snapshotImpl) and inheritAffectsMarks (getNode), plus the chunk split. ~1.1–1.2 KB gzip recoverable of its ~1.3 KB cost.
- Optimistic stores / projections — same shape of problem, likely larger recovery.
- Transitions — biggest subsystem; feasibility unclear (deeply entangled with the scheduler), needs the audit to size it.
- Subsystem byte-attribution pass on the signals bundle to find anything else worth detaching.
Non-goals
Returning to the 1.x floor (4.2 KB) — the async model is load-bearing. A realistic target is a ~7–8 KB gzip minimal floor while full-featured apps pay today's price.
Background
Solid 2.0's library size has grown substantially over 1.x, driven almost entirely by
@solidjs/signalsabsorbing the async model (transitions, optimistic lanes, pending sources/status rails, boundaries, projections, stores). Current measurements (all uniform: esbuild bundle + minify + gzip, production defines, Jul 2026):Full runtime (whole packages):
Tree-shaken minimal app (render + one signal):
mount)Excluding the web runtime, the library proper is ~2.2–2.5x its 1.x size. We compare favorably against Svelte 5 and React today, but the minimal-app floor (12.6 KB gzip) means a hello-world app loads most of the async machinery whether it uses it or not.
Root cause of the high floor
dist/prod.jsis one flat rollup bundle. Once an app imports anything, all top-level side effects in the file are retained — including theGlobalQueue._*hook installs that wire subsystems together. Module-level tree-shaking can't separate subsystems that live in the same output chunk.createStoreeven when the feature is unused.Proposed direction
indexre-exports from feature chunks). All packages already declaresideEffects: false, so bundlers will drop unused chunks. Needs care around the dev/prod/node output triplet and__DEV__replacement.GlobalQueue._propagateAffects,_settleAffects,_clearOptimisticStorealready demonstrate it), installed by the feature module on import.Candidates (rough order of value)
affects()— seams already in place (GlobalQueue._propagateAffects/_settleAffects); remaining work is hook-convertingwitnessAffectsMark(get/has traps,snapshotImpl) andinheritAffectsMarks(getNode), plus the chunk split. ~1.1–1.2 KB gzip recoverable of its ~1.3 KB cost.Non-goals
Returning to the 1.x floor (4.2 KB) — the async model is load-bearing. A realistic target is a ~7–8 KB gzip minimal floor while full-featured apps pay today's price.