The micro-frontend pitch is great: teams own their features, ship on their own cadence, pick the framework that fits the problem. The asterisk nobody puts in the README: every one of those frameworks runs on the same main thread, in the same module graph, fighting for the same 16ms frame budget.
Module federation, import maps, iframe sandwiches — the orchestration varies, but the physics don't. Pick Vue for one widget and Angular for another and every user loads every runtime. "Independent teams" quietly becomes "every framework on every page, all the time."
I built AtollJS around a different answer: run each micro-frontend inside a real Web Worker. Not a shim, not a renderer subset — the actual React/Vue/Solid/Svelte/ Angular runtime, rendering in its own thread, streaming DOM ops back to the page.
The result is the thing micro-frontends always promised: Real isolation — a wedged MFE wedges its worker, not your page Real independent deployment — the worker bundle is a URL; ship it from npm or a CDN without touching the shell Zero runtime tax — the shell never imports or bundles the MFE's framework. A React shell hosting a Vue island loads zero bytes of Vue
Here's the part that makes it work. Instead of importing the MFE's component tree, the shell imports a framework-neutral contract — the MFE's entire public API in one dependency-free module:
Four fields: which app to register, what props it accepts, what events it emits, and where its worker lives. The schemas aren't decorative — the worker enforces them at runtime, so a producer that ships a prop rename fails loudly with a schema error naming the field instead of a silent undefined three teams away.
Vue, Solid, Svelte, and Angular shells get the same inference through their own facades — defineIslandComponent, an injectable, whatever's idiomatic. The types flow from the contract module, so the shell never installs the worker's framework or its types.
And since the worker attaches the same contract object, the two ends can't drift:
The matrix that falls out of this is the fun part: any supported framework in the shell × any supported framework in the worker — all 25 combinations ride the same op protocol. No per-pair adapters, no glue code.
The repo proves it rather than claiming it: examples/ has five shell apps — React, Vue, Solid, Svelte, Angular — each mounting the same five worker-rendered MFEs written in all five frameworks. Same API every time.
This is where worker MFEs stop being a neat demo and start being an architecture. A published MFE is two artifacts in two lanes: The contract is the module entry — consumers import it. The worker is a fetched asset — dist-mfe/counter.worker.js, one self-contained bundle from vite build --config vite.mfe.config.ts.
Two distribution shapes, both working end-to-end in the repo: npm package — ship dist-mfe/ inside the package. The contract's new URL('../../dist-mfe/counter.worker.js', import.meta.url) makes the consumer's bundler emit it byte-for-byte. CDN / remote origin — point the contract's factory at your CDN. Versioned URLs mean the shell and the MFE upgrade on independent schedules — the whole point.
One gotcha worth knowing if you go remote: worker script URLs must be same-origin (new Worker('https://cdn…') throws SecurityError regardless of CORS). The escape is a one-line blob: shim that imports the remote bundle — it's in the docs and the generated contract template.
That emits the contract, the worker, a self-contained publish build, and a dev harness that mounts the island through its own contract — so vite dev previews exactly what your consumers will see.
This isn't free magic, so the caveats: Workers are capability isolation, not a security boundary — same-origin workers still share storage and can fetch. Great for untrusted-ish vendor widgets; not a sandbox. Cross-seam traffic is postMessage — cheap, but don't put per-mousemove state through it. If you have one team, one framework, one repo: skip the distribution machinery entirely. A plain mountIsland gets you the worker perf isolation without the packaging story.
If "isolation and independent deployment" is why you wanted micro-frontends in the first place: Docs + live demos of all 25 combos: jwhenry3.github.io/atolljs/consumer/island-mfe Repo: github.com/jwhenry3/atolljs — the mfe-publish / mfe-consumer pair is a working publish-consume round-trip npm: npmjs.com/org/atolljs
