Islands — overview

@atolljs/islands renders a framework tree inside a Atoll worker: the worker owns the render loop and every commit serializes to an op stream the main thread replays as real DOM mutations into your element. Opt-in DOM rendering off the main thread — the shell keeps events, layout, and slots; the worker keeps the app.

Vocabulary

TermMeaning
IslandThe mounted unit — mountIsland() puts one instance of a registered app into a container element. Each mount mints an app@N instance key scoping its op queue, document, and events.
PolyWorkerdefinePolyWorker({ apps }) — one worker hosting a registry of named apps; several islands can share it.
MonoWorkerdefineMonoWorker(app) — one worker pinned to a single app; own bundle, own failure domain.
ShellYour main-thread app — plain DOM via mountIsland, or a framework via the *-island packages.

Install, mounting, and the mount API live under Quickstart.

Live demo

Eight islands on a framework-free shell — plain mountIsland calls, no framework on the main thread at all. The React apps ride the registry worker script (data-table mounts twice — same app, separate workers), vue-notes runs a real Vue createRenderer, and the imperative islands get dedicated instance workers running a hand-written proxy-DOM app and unmodified Leaflet 1.9.

If the frame is blank, the examples aren't up — run npm run dev:all (dev servers) or npm run serve:all (built apps) from the repository root.

Which workloads belong in an island

Every event costs one postMessage round trip — pointermove, per-keystroke input, and scroll handlers re-render on worker latency, and preventDefault can't work (the real event already dispatched). Keep high-frequency input on the main thread — slots exist for exactly this — and put coarse interactions (clicks, toggles, form submits) behind the island. Main-thread replay scales with op count, not tree size, so fine-grained framework updates are cheap while whole-tree rebuilds cost an order of magnitude more per frame.