Run JavaScript & TypeScript in the Browser

Run a JavaScript or TypeScript script, or work at a REPL, on a WebAssembly engine served from this page — output as it is written and a panel of what the run left bound.

How it works

The engine is quickjs-ng, a small JavaScript engine compiled to WebAssembly, running in a worker the page starts for it. Its half a megabyte is fetched from this site on the first Run, not when the page opens, and stays loaded for the runs after.

Each Run is a whole program in a new context, so nothing the previous run declared is visible to it. Every console method writes to one stream in the order it was called, and output appears while the script is still going. A run is not over until its event loop is: promises settle and timers fire before the page reports the time taken.

TypeScript has its types stripped by sucrase and the rest runs as it stands. Nothing is type-checked. An enum survives, being a value; interfaces, annotations and as casts disappear.

When a run ends, the panel beside the editor lists what the top level left bound: let, const and var names, every name in a destructuring pattern, and anything assigned to globalThis. Objects, arrays, maps and sets open into their contents, functions and classes wait under a Functions heading, and a getter is named but never called.

The REPL keeps one context between entries. An expression is answered with its value and a statement with nothing. A line with an unclosed bracket, string, template or block comment waits for the rest, and a top-level let, const or class is rewritten as var before it runs, so the same declaration entered twice rebinds the name.

A script printing in a tight loop is paced rather than queued: the page redraws at most every 100 ms, keeps the last 128 KB, and says at the top when earlier output was dropped. The Python page is the same console with CPython behind it.

Examples

Microtasks before timers

setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("sync");
sync
microtask
timeout

The microtask queue is drained before the next due timer fires, as in a browser, and the run waits for the timer before it counts as finished.

The same declaration twice at the prompt

> let total = 0
> [1, 2, 3].forEach((n) => { total += n; })
> total
6
> let total = 10
> total * 2
20

In a script the second let total stops the run with SyntaxError: invalid redefinition of lexical identifier; at the prompt it rebinds the name. forEach returns undefined, so that line is answered with nothing, and the panel afterwards shows total as 10.

Types are erased, not checked

enum Level { Low, High }
let score: number = 10;
score = "ten";
console.log(Level.High, Level[0], score);
1 Low ten

The compiler would reject a string assigned to a number; here the annotation is removed and the assignment runs. The enum becomes an object mapping names to numbers and back, which is why Level[0] reads Low.

Common problems

ReferenceError: fetch is not defined

The engine brings the language and its built-ins, plus console, the timers and queueMicrotask, and nothing from a browser or Node. fetch, require, URL, TextEncoder, atob, structuredClone, Intl and document are all undefined, and an import fails with could not load module. With no Intl, (1234567.891).toLocaleString() returns 1234567.891 without separators.

await at the top level

A script runs as a classic script, so const data = await load(); stops with SyntaxError: expecting ';'. Wrap the body in an async function and call it, (async () => { … })(). The run waits for the promise and any timers it set, so the output still arrives before the page says it finished.

A TypeScript namespace disappears

sucrase erases namespace blocks with the types, values inside them included, so namespace Util { export const x = 1; } followed by console.log(Util.x) fails with ReferenceError: Util is not defined. A plain object survives.

console.log shows less than Node does

Values are previewed two levels deep and eight items wide: an array of the numbers 1 to 20 prints as [1, 2, 3, 4, 5, 6, 7, 8, … 12 more], and a deeper object ends in [Object]. console.log(JSON.stringify(value)) prints all of it, and the variables panel opens four levels down.

Frequently asked questions

Does the code run on a server?

No. The engine runs in a Web Worker in this tab, and the script inside it has no network access. The script is kept in the part of the address after #, which is how a copied link carries it; browsers do not send that part to the server.

How do I stop a script that never ends?

Press Stop. It terminates the worker the engine runs in, which works even in the middle of while (true) {}, and whatever the script had already printed stays on screen. The next Run starts a new engine, and a REPL session's names go with the old one. Memory and the stack end a script on their own: with InternalError: out of memory, after which the page lets that engine go as Stop would, or with RangeError: Maximum call stack size exceeded about 600 calls deep.

Which language features are there?

Current ECMAScript, including recent additions such as Object.groupBy, Array.prototype.toSorted, Promise.withResolvers and the new Set methods like union. There is no Intl and no Temporal.

Related tools

References