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(), and queueMicrotask().

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.

terminal
curl -fsSL https://raw.githubusercontent.com/duncantidd/wizz.js/main/scripts/install-cli.sh | bash

The 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:

~/.bashrc
# 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 sha256 digest 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.js is 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:

terminal
# 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.sh

Fully 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:

terminal
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:

terminal
# 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.

terminal
wizz init my-app
cd my-app
wizz dev

Project 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
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/health

You 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.

index.html
<!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.

src/pages/Hello.wizz → /hello
<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.

terminal
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 #app and 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 --port flag.

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.

terminal
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.mjs

The 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:

terminal
wizz install-vscode-extension

Without the CLI, download wizz-vscode-<version>.vsix from a release page and use Extensions → Install from VSIX….