Bundle size & load split
Every @atolljs/* package publishes source — your bundler does the final tree-shaking, so the numbers below are the full entry surface of each published export, bundled with rolldown (the Vite 8 bundler) and minified. Dependencies and peer dependencies are excluded from the package columns and measured separately below.
Per-package impact
Atoll splits each package's weight across two bundles: the main-thread surface (imported by your app) and the worker-thread surface (imported inside the worker entry — a separate fetch that never blocks your app bundle). The bar shows each package's gzip share per thread.
| Package | Main thread | Worker thread | Load split | Worker share |
|---|---|---|---|---|
@atolljs/core | 33.7 kB (11.2 gz) | 27.3 kB (9.3 gz) | 45% | |
@atolljs/react | 0.3 kB (0.2 gz) | — | — | |
@atolljs/vue | 0.3 kB (0.2 gz) | — | — | |
@atolljs/solidjs | 0.4 kB (0.2 gz) | — | — | |
@atolljs/svelte | 0.5 kB (0.3 gz) | — | — | |
@atolljs/angular | 2.3 kB (1.1 gz) | — | — | |
@atolljs/nextjs | 0.1 kB (0.1 gz) | — | — | |
@atolljs/node | 2.1 kB (0.9 gz) | 0.1 kB (0.1 gz) | 10% | |
@atolljs/nestjs | 5.0 kB (2.0 gz) | 1.2 kB (0.6 gz) | 24% | |
@atolljs/islands | 10.2 kB (4.1 gz) | 33.0 kB (10.3 gz) | 71% | |
@atolljs/react-island | 3.1 kB (1.5 gz) | 7.4 kB (2.7 gz) | 65% | |
@atolljs/vue-island | 3.7 kB (1.5 gz) | 3.5 kB (1.6 gz) | 52% | |
@atolljs/svelte-island | 1.5 kB (0.8 gz) | 2.2 kB (1.0 gz) | 54% | |
@atolljs/solid-island | 3.2 kB (1.5 gz) | 4.7 kB (2.0 gz) | 57% | |
@atolljs/angular-island | 3.6 kB (1.4 gz) | 7.4 kB (3.1 gz) | 68% |
coreis loaded on both threads — the contract layer (defineSharedMemory,field.*, codecs, reactivity) is counted on each side because each thread parses its own copy. Main adds the pool/client; the worker addsdefineWorker+ bootstrap.- Framework bindings are near-free (0.1–2.3 kB) — they adapt the core observables to each framework's reactivity and carry no domain code. The framework itself (
react,vue, …) is a peer you already ship. - Islands invert the split: ~78% of
@atolljs/islandslives in the worker entry (proxy DOM, app registry, op pump) — main thread only pays for the mount driver + op replay. The framework renderers are separate per-framework packages (react-island/worker,vue-island/worker, …) so a Vue-only worker never parses React's reconciler.
Typical stacks
What an app actually pays per thread, summed from the table above (gzip). Framework and peer packages stay out of the sums — the React islands worker additionally pulls react-reconciler (~39 kB gz, below).
| Stack | Main thread | Worker thread | Load split | Worker share |
|---|---|---|---|---|
| React app — worker pool + hooks | 11.5 kB gz | 9.3 kB gz | 45% | |
| Vue / Solid / Svelte app — worker pool + bindings | 11.5 kB gz | 9.3 kB gz | 45% | |
| React islands app — shell proxies on main, reconciler in worker | 16.8 kB gz | 22.3 kB gz | 57% | |
| Vue islands app | 16.9 kB gz | 21.2 kB gz | 56% | |
| Svelte islands app | 16.2 kB gz | 20.6 kB gz | 56% | |
| SolidJS islands app | 16.8 kB gz | 21.6 kB gz | 56% | |
| Angular islands app | 16.8 kB gz | 22.7 kB gz | 57% | |
| NestJS backend — pool + worker_threads adapter | 14.1 kB gz | 10.1 kB gz | 42% |
Processing load
Bundle size says what each thread loads — this says where the JS time actually goes. Measured, not estimated: the same 200-row tree was mounted through every island adapter, then a prop-driven update and a click→state-commit. Worker time is the task call itself (framework render/diff, proxy-DOM bookkeeping, op serialization); main time is the op replay into real DOM. The protocol these numbers measure is detailed on Islands → Proxy document.
| Renderer | Mount | Prop update | Click → commit | Overall |
|---|---|---|---|---|
| Imperative (no framework) | 68% · 2608 ops | 51% · 2609 ops | 10% · 1 ops | 55% |
| React | 52% · 2007 ops | 8% · 802 ops | 3% · 603 ops | 30% |
| Vue | 63% · 2408 ops | 13% · 200 ops | 0% · 1 ops | 36% |
| Svelte | 35% · 3445 ops | 4% · 200 ops | 0% · 1 ops | 26% |
| SolidJS | 78% · 2809 ops | 4% · 200 ops | 0% · 1 ops | 60% |
| Angular | 66% · 3015 ops | 2% · 200 ops | 0% · 1 ops | 49% |
- Mount is the closest split — building the tree is mostly op emission, and emitting ~2–3k ops isn't free on the main thread either. Frameworks still land 55–60% of mount work in the worker.
- Updates are where islands pay off — a framework diff turns a label rename into ~200 ops while the worker does 85–99% of the JS. The imperative baseline has no diff:
updatePropsis an honest clear+rebuild (~2,600 ops), an even split between writing and replaying it. - Clicks are ~all worker — dispatch runs the handler in the island, the framework invalidates, and typically one op crosses back.
- Numbers are one happy-dom run on one machine — treat the ratios, not the decimals, as the data. In-process transport is ~free; real workers add structuredClone marshalling to the worker side.
Runtime dependencies
These install alongside the packages above — same measurement, browser builds, minified. All are only pulled when your bundler sees them imported: unused connectors tree-shake away (e.g. no msgpackrCodec import → no msgpackr).
| Dependency | Minified | Gzip | Pulled in by |
|---|---|---|---|
msgpackr | 29.0 kB | 10.6 kB | msgpackrCodec |
solid-js | 21.8 kB | 8.2 kB | core deps; solidjs/solid-island peers |
htmlparser2 | 26.3 kB | 6.4 kB | islands worker |
react-reconciler | 126.1 kB | 38.4 kB | react-island worker (React apps) |
- zod isn't a dependency at all —
reef()andlistSchema()run on a vendored schema engine that speaks zod's_zod.defvocabulary, so the table has no zod row. Your ownimport { z } from 'zod'still works wherever a schema is accepted — the compiler introspects it the same way — but installing zod is now your choice, not ours. - react-reconciler is worker-side only — React island apps pay it inside the worker bundle, off the critical path.
- solid-js is a runtime dep of core (the reactive primitives) and a peer of
@atolljs/solidjs— Solid apps pay it once, either way.
Methodology
Bundle sizes generated 2026-09-30 by node scripts/bundle-stats.mjs: each published export is bundled with rolldown in library mode (deps/peers external, ES output, minified), then measured raw and gzipped. Worker entries are bundled from their published /worker (or shim) exports. Real-world numbers are typically lower — tree-shaking drops the exports your app never touches, and gzip is what crosses the wire.
Processing-load numbers generated 2026-09-29 by node scripts/island-perf.mjs (packages/islands/test/islandBench.test.ts): each adapter mounts the same 200-row tree through an in-process worker under happy-dom; the awaited task call is the worker side and the op-replay hook is the main side.