Getting Started
Installation & Setup
Wizz installs without npm or a package registry. One command fetches the CLI — compiler, runtime, starter templates, and build tooling — and from there a project is just index.html, App.css, and src/**/*.wizz.
Requirements
- Node.js 18 or newer — for the CLI, the compiler, and the dev server.
- curl and tar — used once by the installer.
- A current evergreen browser with native ES modules, dynamic
import(), andqueueMicrotask().
That is the whole list. Wizz has no runtime dependencies: nothing is added to your project's node_modules, and the browser never sees the compiler.
Install the CLI
The installer resolves the latest GitHub release, downloads its wizz-<version>.tgz asset, and installs it. No administrator privileges, no shell-profile edits.
curl -fsSL https://raw.githubusercontent.com/duncantidd/wizz.js/main/scripts/install-cli.sh | bashThe framework lands in $XDG_DATA_HOME/wizz (default ~/.local/share/wizz) and the wizz launcher in $XDG_BIN_HOME (default ~/.local/bin). Make sure the launcher directory is on your PATH:
# Bash / Zsh — add to your shell profile, then open a new shell
export PATH="$HOME/.local/bin:$PATH"
wizz --version
# wizz 1.17.0 (compiler 1.17.0, syntax 1.4.1, output 1.8.2)Verify before installing
Piping a script into bash runs it before you can read it. If you would rather not do that, every step can be done by hand. Here is what the one-liner already checks for you:
- Checksum check, fails closed. The installer reads the tarball's
sha256digest from the GitHub Releases API, hashes the download, and refuses to install if they differ, or if the release has no digest. - HTTPS only. Downloads and every redirect hop must use
https://. The URL the tarball is actually served from is checked again after the transfer. - Contents check. A tarball without
package/scripts/cli.jsis rejected before anything is moved into place.
None of this covers the installer script itself. curl … | bash runs whatever main serves at that moment. To review the script first, download it from a release tag and read it:
# Fetch the installer from a release tag, read it, then run it
curl -fsSLO https://raw.githubusercontent.com/duncantidd/wizz.js/v1.17.0/scripts/install-cli.sh
less install-cli.sh
bash install-cli.shFully manual install
Every release includes SHA256SUMS-<version>.txt next to wizz-<version>.tgz and the VS Code .vsix. Download the tarball, check it yourself, then give its path to the installer. A local path makes the installer skip the network entirely:
version=1.17.0
base=https://github.com/duncantidd/wizz.js/releases/download/v$version
# 1. Download the tarball and the checksum file from the same release
curl -fsSLO "$base/wizz-$version.tgz"
curl -fsSLO "$base/SHA256SUMS-$version.txt"
# 2. Check the tarball against the published sha256
sha256sum -c --ignore-missing "SHA256SUMS-$version.txt" # Linux
shasum -a 256 -c --ignore-missing "SHA256SUMS-$version.txt" # macOS
# wizz-1.17.0.tgz: OK
# 3. Install the file you verified, using the install-cli.sh you read above
bash install-cli.sh "./wizz-$version.tgz"For a second, independent source, compare the hash with the digest GitHub stores for the asset. This is the same value the one-line installer checks against:
# GitHub records its own sha256 for every release asset
curl -fsSL https://api.github.com/repos/duncantidd/wizz.js/releases/tags/v1.17.0 \
| grep -E '"(name|digest)"'Scaffold a project
wizz init writes the showcase starter: a design-token stylesheet, a landing page with a reactive document head, a counter whose value persists across reloads and tabs, a server-rendered /home page, and a sample API route. It runs correctly with no manual editing.
wizz init my-app
cd my-app
wizz devProject structure
A Wizz project contains only source files. The compiler, runtime, and CLI live in the managed install, so there is no framework code to vendor or keep in sync.
my-app/
├── index.html # Document shell: <div id="app"> + /runtime/main.js
├── App.css # Global tokens, reset, and layout
├── README.md
└── src/
├── App.wizz # Landing page → /
├── pages/
│ └── Home.wizz # Routed page → /home
├── components/
│ ├── Counter.wizz # Imported component with persisted state
│ └── Card.wizz
└── server/
└── api/
└── health.js # Server handler → /api/healthYou edit
index.html, App.css, src/**/*.wizz, and src/server/api/*.js.
Wizz generates
dist/ — compiled modules, a copy of the runtime, the route manifest, and extracted app.css. Treat it as disposable.
The document shell only owns the mount point and the runtime entry. The router finds #app, loads the module for the current path, and mounts — or hydrates — it.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/App.css">
</head>
<body>
<div id="app"></div>
<script type="module" src="/runtime/main.js"></script>
</body>
</html>Your first component
A .wizz file is a template with exactly one root element, plus optional <script>, <wizz:head>, and <wizz:style> blocks. Top-level let declarations are reactive; event directives bind native events.
<script>
let name = "world";
function handleInput(event) {
name = event.target.value;
}
</script>
<wizz:head>
<title>Hello - My Wizz App</title>
</wizz:head>
<main class="hello">
<h1>Hello {name}!</h1>
<input value={name} on:input={handleInput} aria-label="Name" />
</main>
<wizz:style>
.hello { display: grid; gap: 1rem; padding: 4rem; }
h1 { font-size: 3rem; }
</wizz:style>Save it under src/pages/ and the next rebuild gives it a route. Typing in the input reassigns name; Wizz batches the change and updates the single text node that reads it on the next microtask.
Development server
wizz dev builds src into dist, serves it, and watches .wizz sources — with native file events plus a 250 ms polling fallback so mounted filesystems such as WSL's /mnt/c still rebuild. Rebuilds live-reload connected tabs over a dev-only Server-Sent Events channel at /_wizz/reload.
wizz dev # http://127.0.0.1:3000
wizz dev --port 4321 # pick a port
wizz dev --host 0.0.0.0 # opt out of loopback (remote dev only)- Server-renderable routes arrive with markup already inside
#appand hydrate on load. - Compiler errors are reported while the server stays up for the next fix.
- The server binds to loopback by default — it has no auth or TLS and runs API handlers with your secrets loaded.
- A taken port fails cleanly with a message naming the port and the
--portflag.
Production build
wizz build compiles every .wizz file, writes the route manifest, copies the runtime, and extracts all scoped styles into dist/app.css. Add --adapter node to also emit a standalone SSR + API host.
wizz build # src → dist (static)
wizz build --adapter node # + dist/server.mjs (SSR + API host)
node dist/server.mjs # serves http://localhost:8080
PORT=3000 node dist/server.mjsThe dist/ directory is self-contained: the node host imports only Node built-ins and the build's own artifacts. See Deploying with the node adapter for the full contract.
Editor setup
The first-party VS Code extension adds .wizz syntax highlighting, compiler diagnostics, component navigation, and build commands. With the CLI on your PATH:
wizz install-vscode-extensionWithout the CLI, download wizz-vscode-<version>.vsix from a release page and use Extensions → Install from VSIX….