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.

main threadworker thread

PackageMain threadWorker threadLoad splitWorker share
@atolljs/core33.7 kB (11.2 gz)27.3 kB (9.3 gz)
45%
@atolljs/react0.3 kB (0.2 gz)—
—
@atolljs/vue0.3 kB (0.2 gz)—
—
@atolljs/solidjs0.4 kB (0.2 gz)—
—
@atolljs/svelte0.5 kB (0.3 gz)—
—
@atolljs/angular2.3 kB (1.1 gz)—
—
@atolljs/nextjs0.1 kB (0.1 gz)—
—
@atolljs/node2.1 kB (0.9 gz)0.1 kB (0.1 gz)
10%
@atolljs/nestjs5.0 kB (2.0 gz)1.2 kB (0.6 gz)
24%
@atolljs/islands10.2 kB (4.1 gz)33.0 kB (10.3 gz)
71%
@atolljs/react-island3.1 kB (1.5 gz)7.4 kB (2.7 gz)
65%
@atolljs/vue-island3.7 kB (1.5 gz)3.5 kB (1.6 gz)
52%
@atolljs/svelte-island1.5 kB (0.8 gz)2.2 kB (1.0 gz)
54%
@atolljs/solid-island3.2 kB (1.5 gz)4.7 kB (2.0 gz)
57%
@atolljs/angular-island3.6 kB (1.4 gz)7.4 kB (3.1 gz)
68%
  • core is 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 adds defineWorker + 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/islands lives 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).

StackMain threadWorker threadLoad splitWorker share
React app — worker pool + hooks11.5 kB gz9.3 kB gz
45%
Vue / Solid / Svelte app — worker pool + bindings11.5 kB gz9.3 kB gz
45%
React islands app — shell proxies on main, reconciler in worker16.8 kB gz22.3 kB gz
57%
Vue islands app16.9 kB gz21.2 kB gz
56%
Svelte islands app16.2 kB gz20.6 kB gz
56%
SolidJS islands app16.8 kB gz21.6 kB gz
56%
Angular islands app16.8 kB gz22.7 kB gz
57%
NestJS backend — pool + worker_threads adapter14.1 kB gz10.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.

RendererMountProp updateClick → commitOverall
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: updateProps is 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).

DependencyMinifiedGzipPulled in by
msgpackr29.0 kB10.6 kBmsgpackrCodec
solid-js21.8 kB8.2 kBcore deps; solidjs/solid-island peers
htmlparser226.3 kB6.4 kBislands worker
react-reconciler126.1 kB38.4 kBreact-island worker (React apps)
  • zod isn't a dependency at all — reef() and listSchema() run on a vendored schema engine that speaks zod's _zod.def vocabulary, so the table has no zod row. Your own import { 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.