Skip to content

Getting started

Checked against 2994ef5 on 2026-11-10 by Capy (review pass: Subagent 124 and fable). The files it covers are unchanged since.

This page builds one small Svelte page and runs it from a checkout of the repository: headless with niso-run, under Node, in a desktop window, and in the Apple and Android hosts. There’s no package to install yet; the commands are the repository’s own build scripts.

  • Rust (rustup’s stable) and a C compiler, for niso and QuickJS-ng.
  • Node and pnpm, to build pages: examples/build.mjs compiles Svelte and bundles with esbuild.
  • For the Apple platforms: macOS with Xcode (Build for Apple platforms).
  • For Android: the Android SDK with the NDK, and a JDK 17 (Build for Android).
  • On Linux, the Linux notes list the toolchains the checks use.
Terminal window
git clone https://github.com/oofdere/niso
cd niso
git submodule update --init # QuickJS-ng

Every directory in examples/ with a main.js is a page: examples/build.mjs builds it into examples/dist/<name>.js, the script a host runs, and examples/dist/<name>.html, the same page for a browser.

  • Directoryexamples/
    • Directoryhello/
      • App.svelte
      • main.js
    • build.mjs
examples/hello/App.svelte
<script>
let count = $state(0);
let notify = $state(true);
</script>
<main>
<h1>Hello, niso</h1>
<p>Tapped {count} {count === 1 ? 'time' : 'times'}.</p>
<button onclick={() => count++}>Tap me</button>
<label><input type="checkbox" switch bind:checked={notify}> Notifications</label>
</main>
<style>
main { padding: 16px; display: flex; flex-direction: column; gap: 12px; }
</style>
examples/hello/main.js
import { mount } from 'svelte';
import App from './App.svelte';
mount(App, { target: document.body });

Build the pages:

Terminal window
(cd examples && pnpm install --frozen-lockfile && node build.mjs)

niso-run runs scripts against a headless niso DOM: no window, the same core and layout. It’s the quickest loop, and how niso’s own tests drive pages.

Terminal window
cargo build --release -p niso-quickjs --bin niso-run
target/release/niso-run --tap 100,195 --dump examples/dist/hello.js

--tap X,Y renders a frame, taps at that point in CSS pixels (here the button) and renders again; --dump then prints the document:

<html><head><style id="svelte-1kym7j0">main.svelte-1kym7j0 {padding:16px;display:flex;flex-direction:column;gap:12px;}</style></head><body><main class="svelte-1kym7j0"><h1>Hello, niso</h1> <p>Tapped 1 time.</p> <button>Tap me</button> <label><input type="checkbox" switch=""> Notifications</label></main></body></html>

To find where to tap, --boxes prints every element’s border box under body as JSON, in CSS pixels of an 800 by 600 viewport. This is macOS’s output; on Linux the default fonts are fontconfig’s, so the text boxes and everything below them differ by a few pixels, which is why the tap above aims at a point inside the button on both:

Terminal window
target/release/niso-run --boxes examples/dist/hello.js
[{"path":"body","x":8,"y":8,"width":784,"height":255.88},{"path":"body>main:0","x":8,"y":8,"width":784,"height":255.88},{"path":"body>main:0>h1:0","x":24,"y":45.440002,"width":752,"height":32},{"path":"body>main:0>p:1","x":24,"y":126.880005,"width":752,"height":16},{"path":"body>main:0>button:2","x":24,"y":170.88,"width":752,"height":34},{"path":"body>main:0>label:3","x":24,"y":216.88,"width":752,"height":31},{"path":"body>main:0>label:3>input:0","x":24,"y":216.88,"width":51,"height":31}]

The other options, all from the top of niso-run.rs:

Option What it does
--page FILE [--root DIR] [--url URL] loads an HTML page the way a browser does, with its stylesheets, classic and module scripts; with --root the page is at http://localhost/ plus its path under DIR, which serves that origin’s files
--html FILE sets document.body’s content before the scripts run
--serve URL=FILE answers requests for URL with FILE’s bytes, so a page that fetches sees the same answer every run
--missing lists every DOM member and global the scripts looked up that niso doesn’t have (Port a web app)
--repeat N repeats the taps N times
--render FILE writes each frame’s render stream to FILE
--time prints startup time, time per tap frame, live nodes and peak memory to stderr
--storage DIR keeps localStorage and IndexedDB in DIR for the next run
--hold after --time, keeps the process and its workers alive until stdin closes, for a heap tool
--max-ms N how long the page’s timers may run, in virtual time (default 60000)

Scripts run in virtual time: timers fire as fast as the page lets them, and niso-run exits 1 on any uncaught exception.

The button is the platform’s own button, and <input type="checkbox" switch> is its switch: a SwiftUI Toggle on Apple platforms, a Material 3 Switch on Android, iced’s toggler on desktop. Layout is niso’s, the same on every platform and checked against Chrome’s: in the screen test cases (tests/screens/cases.txt), every element’s box within a pixel, apart from the known differences in tests/screens/expectations.json. Whole apps are further off (Real apps). Text is laid out by niso and drawn by the platform with its system fonts. See Native controls and Text.

  • Try the examples that come with the repository: hosts/apple/build.sh timer runs a stopwatch with home screen widgets and a Live Activity (Surfaces).
  • Native apps: the same timer, driving the DOM from C, Rust, Zig, Go or Java.
  • JavaScript: QuickJS, Porffor and Node-API, and what each changes.
  • Web platform support: what of the web platform works.