Choosing a runtime
Pick Bun, Browser, or Browser & Node APIs for each tab, and learn what your code can reach in each one.
Run a tab on Bun, on Browser & Node APIs, or in the Browser, and switch from the status bar.
// Switch the runtime in the status bar: each line answers differently.
"document" in globalThis;
"Bun" in globalThis;
globalThis.process?.versions?.bun ?? "not Bun";
import { readdir } from "node:fs/promises";
(await readdir(".")).length;
Each tab runs its code in one of three runtimes. Pick one from the runtime menu in the status bar, from Actions → Runtime, or from the command palette (Runtime: Bun, Runtime: Browser & Node APIs, Runtime: Browser).
New tabs open in Bun. Change that with Default Runtime in Settings → General.
| Runtime | What your code gets | Web View |
|---|---|---|
| Bun (default) | Bun and Node globals and the full Node API surface, with no DOM | No |
| Browser & Node APIs | A real page, with the DOM and web APIs, plus a Node compatibility layer | Yes |
| Browser | A real page only, with no Node APIs at all | Yes |
Your code is bundled before it runs, so importing an installed package works in all three.
Bun
Your code runs in a separate Bun process, with the Bun that ships inside JSLab. It gets the full Node-compatible API surface, such as fs, path, child_process and http, and no DOM.
Use it for scripts, data processing, algorithm sketches and anything that doesn’t need a page. See Bun vs Node for how it differs from a plain Node process.
Browser & Node APIs
Your code runs inside a real WebKit page, with document, canvas, requestAnimationFrame and everything else a browser provides. On top of that, a Node compatibility layer is bridged out to JSLab:
fs/promisesand the callback-stylefsfunctions;child_process.exec,execFileandspawn;- a
fetchwith no CORS restriction.
Use it when you want to render something, such as a canvas, React, Three.js or Web Audio, but still need to read a file or run a command.
The synchronous fs functions aren’t available here, and neither are http, net, tls, dgram, worker_threads or vm. Calling one refuses with a message that offers to switch the tab to Bun; for a synchronous fs call, the message also names the async alternative.
Node APIs in the page
A real web page that can also read files, run commands and use Node's crypto, path and os modules.
import { createHash } from "node:crypto";
import os from "node:os";
document.body.innerHTML = `<h1>Hello from ${os.platform()}</h1>`;
createHash("sha256").update("JSLab").digest("hex");
os.platform();
document.querySelector("h1");
Besides the file system and child processes, the page gets Node’s process (a snapshot of the environment, working directory, platform and versions, with a live nextTick), os, crypto (Web Crypto plus a synchronous createHash and createHmac), buffer, path, events, util, url, querystring, string_decoder, assert, stream and punycode. Relative paths resolve against the tab’s working directory.
fetch without CORS
In Browser & Node APIs, fetch reaches any URL without CORS getting in the way.
const response = await fetch("https://api.github.com/zen");
response.status;
await response.text();
In the Browser & Node APIs runtime, fetch goes through JSLab, so any URL answers without CORS getting in the way — handy for poking at a real API. Bodies stream, and an AbortController cancels a request. data: and blob: URLs are served by the page itself; any other scheme, or a relative URL, refuses with a clear error. The plain Browser runtime keeps real CORS, so you can check what a website would see.
Browser
A plain web page with no Node at all, for code that has to run as it would in a website.
document.body.innerHTML = "<p>Plain browser page</p>";
navigator.userAgent.includes("AppleWebKit");
alert("A notice, not a blocking dialog");
confirm("Continue?");
A real page with no Node APIs at all, the same as a blank HTML page in Safari with your bundled code running in it. fetch is the page’s own, and CORS applies exactly as it would in a browser. Importing a Node module fails when the page is built, with a code frame pointing at the import.
What differs in both browser runtimes
A few things behave differently because the code runs inside an embedded web view rather than a terminal:
alert,confirmandpromptcan’t block.alertshows a JSLab notice and returns at once,confirmalways returnsfalse, andpromptalways returnsnull. A console warning says so once per run.import * as nscan show extra keys on the namespace object, because packages are bundled in two halves that join inside the page. The values are unaffected; only listing or spreading the namespace differs.- The tab’s speaker icon tracks live audio only. See The Web View.
Next
- The Web View: the page your browser-runtime code renders into, and React, Vue, Solid and Angular previews.
- Bun vs Node: everything that differs from a plain Node process.