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/promises and the callback-style fs functions;
  • child_process.exec, execFile and spawn;
  • a fetch with 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, confirm and prompt can’t block. alert shows a JSLab notice and returns at once, confirm always returns false, and prompt always returns null. A console warning says so once per run.
  • import * as ns can 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.

Last updated