Skip to content

JavaScript engines

Checked against f15b998 on 2026-11-10. The files it covers are unchanged since.

niso’s DOM is in Rust, and a page’s JavaScript runs on an engine bound to it through one small interface (bindings/js/niso_engine.h: host functions, weak references, finalizers, microtasks). The JS binding on top (bindings/js/niso_js.c, the glue and the host functions generated from WebIDL) is the same on every engine, so the same page runs on each and can be diffed.

Engine Crate Where Status
QuickJS-ng niso-quickjs every host, niso-run the default; an interpreter, so it runs on iOS
V8, JavaScriptCore (via Node, Deno, Bun) niso-napi niso-node headless only; a JIT without shipping one (Node-API)
Porffor niso-porffor Apple and Android hosts with NISO_ENGINE=porffor, headless experimental: compiles the page’s JS to C ahead of time (Porffor)

V8 embedded directly (desktop and Android) and JavaScriptCore on Apple platforms are planned, not built.

The engine runs your code; everything else (the DOM, style, layout, events, timers, fetch, storage) is the same Rust core whichever engine runs. So a page behaves the same on each, apart from the language itself: speed, error messages, and engine-specific gaps. Porffor, for one, has no eval at run time and its WeakRef holds its target strongly.

Intl comes from niso, not the engine: QuickJS has none, so niso-quickjs adds one built on ICU4X (crates/niso-intl, Intl).

WebAssembly runs on wasmi, an interpreter in the core, on every platform, since iOS forbids JIT (WebAssembly). Under Node-API a page’s WebAssembly is V8’s own.

Native apps (C, Rust, Swift, Java, …) use no JS engine: they call the same DOM through the C API directly (Native apps).