Getting started
Install
npm install @vivari/core
For React apps, add the bindings:
npm install @vivari/react @vivari/core react
Requirement: a cross-origin isolated page
Vivari's synchronous FS/process bridge is built on SharedArrayBuffer +
Atomics.wait(), which browsers only expose on a cross-origin isolated page.
Serve your app (and the preview Service Worker) with these two headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Vivari.boot() rejects early with VivariError("ERR_NOT_ISOLATED") if the page is
not isolated. You can check yourself with the exported isCrossOriginIsolated().
See Cross-origin isolation for per-host header recipes.
Boot, mount, run
import { Vivari } from "@vivari/core";
const vivari = await Vivari.boot();
await vivari.mount({
"package.json": {
file: { contents: JSON.stringify({ name: "app", type: "module" }) },
},
"index.js": {
file: { contents: "console.log('hello from the browser')" },
},
});
const proc = await vivari.spawn("node", ["index.js"]);
for await (const chunk of proc.output) console.log(chunk);
console.log("exit code:", await proc.exit);
proc.output is the merged stdout + stderr; proc.stdout and proc.stderr are
the same chunks split by stream. Each is a ReadableStream<string> you can also
pipeTo() or drain with await proc.stderr.text().
Run a dev server and preview it
const iframe = document.querySelector("iframe")!;
// Routes the dev server's HMR/SSE frames into the frame — without this, Vite HMR
// never gets past "connecting…".
vivari.attachPreview(iframe);
await (await vivari.spawn("npm", ["install"])).exit;
void vivari.spawn("npm", ["run", "dev"]); // long-running; don't await its exit
// Resolves once the port has actually answered a request, so the iframe never
// loads into a server that is still starting up.
const [, url] = await vivari.once("server-ready");
iframe.src = url;
server-ready, not portA dev server binds, closes and rebinds its port several times while booting. The
"port" event reports each of those; "server-ready" waits until the port serves
a real request and fires once. Build on "server-ready".
Handling failures
Every rejection is a VivariError with a machine-readable code, so you can
branch on the cause instead of matching message text:
import { Vivari, VivariError } from "@vivari/core";
try {
const vivari = await Vivari.boot({ timeout: 30_000 });
} catch (err) {
if (err instanceof VivariError && err.code === "ERR_NOT_ISOLATED") {
// The most common first-run problem: the page is missing COOP/COEP.
}
}
See Errors for the full list of codes.
Interactive example
Edit index.js below and press Run. This boots a real Node.js runtime in your
browser (inside a cross-origin isolated frame) and streams the actual stdout
back. Try importing another node: built-in or changing the loop.
To scaffold a full project, npm install, and boot a dev server, open the Studio or
embed the <Vivari> component on a cross-origin isolated page.