Reference · compiler 1.17.0 · syntax 1.4.1 · output 1.8.2
API Reference
The complete public surface: what a component script can call, what a template can express, what every compiled module exports, and the compiler, router, and server entry points behind them.
Script globals
Available inside every component <script> with no import — the compiled module provides them.
onMount()
lifecycle component scriptonMount(callback: () => void): void Registers a hook that runs once, client-side, after mount. The right place for browser APIs, timers, observers, fetches, and anything that reads window, document, or the URL.
| Parameter | Type | Description |
|---|---|---|
callback | () => void | Runs once on the client after the component is attached, its children are mounted, and the initial update has completed. May be async. |
Returnsvoid
<script>
let status = "connecting";
onMount(() => {
status = "ready"; // reactive: flows through the scheduler
});
</script>
<main><p>{status}</p></main>onDestroy()
lifecycle component scriptonDestroy(callback: () => void): void Registers a teardown hook. Remove global listeners, clear timers, disconnect observers, and abort in-flight requests here. Do not change state — the component is going away.
| Parameter | Type | Description |
|---|---|---|
callback | () => void | Runs when destroy() is called — before child components are destroyed and the root is removed. |
Returnsvoid
<script>
let width = 0;
function measure() {
width = window.innerWidth;
}
onMount(() => {
measure();
window.addEventListener("resize", measure);
});
onDestroy(() => {
window.removeEventListener("resize", measure); // yours to remove
});
</script>
<p>{width}px</p>persist()
state marker component scriptlet name = persist(key: string, defaultValue: any); Marks a top-level let as persisted: read from localStorage at mount, written on every statement mutation, and synchronized across components and tabs via BroadcastChannel (storage events as fallback). Server renders use the default.
| Parameter | Type | Description |
|---|---|---|
key | string literal | The localStorage key, used verbatim. Components of one origin share one namespace. |
defaultValue | JSON value | Used when nothing is stored, storage is unreadable, and during server rendering. |
ReturnsThe stored value, or defaultValue — as an ordinary reactive variable.
<script>
let theme = persist("theme", "light");
function toggle() {
theme = theme === "light" ? "dark" : "light"; // writes + broadcasts
}
</script>
<button on:click={toggle}>Theme: {theme}</button>export let
prop component scriptexport let name = defaultValue; Declares a reactive, read-only prop. Parents pass it as an attribute; updates arrive through setProps() without remounting. Assigning a prop in the child is a compile error (WIZZ-G015).
| Parameter | Type | Description |
|---|---|---|
name | identifier | The prop name. One prop per declaration; may not start with the reserved __wizz prefix. |
default | expression? | Optional default, used when the parent omits the prop or passes undefined. |
Returns—
<script>
export let label = "Untitled";
export let count; // undefined unless passed
</script>
<p>{label}: {count}</p>
<!-- parent -->
<Badge label="Inbox" count={unread} />Template syntax
Expressions in {...} accept identifiers, member access, string and number literals, and strict equality. Bind attributes with an unquoted expression as the whole value.
on:event
directive native elements<element on:event={handler}> Attaches a native listener with addEventListener. Client-only; removed automatically on destroy. Not allowed on component tags (WIZZ-G011) or inside {#each} bodies (WIZZ-G006) — delegate from the container instead.
| Parameter | Type | Description |
|---|---|---|
event | DOM event name | Any native event: click, input, submit, keydown, pointermove… |
handler | identifier | (event) => … | A component function, or an expression whose value is passed to addEventListener. |
Returns—
<input on:input={handleInput} />
<button on:click={(event) => add(event.detail)}>Add</button>
<ul on:click={handleListClick}>{#each rows as row (row.id)}<li data-id={row.id}>{row.label}</li>{/each}</ul>{#if} … {:else} … {/if}
block template{#if condition} … {:else} … {/if} Chooses a branch at mount time. Changing the condition later does not swap an already-rendered branch, so drive interactive show/hide with a bound class.
| Parameter | Type | Description |
|---|---|---|
condition | expression | Identifier, member access, literal, or strict equality (===). |
Returns—
{#if status === "ready"}
<p>Ready.</p>
{:else}
<p>Loading…</p>
{/if}{#each} … {/each}
block template{#each items as item (key)} <row /> {/each} Renders one native root element per item, inside a native parent. Bodies may not contain components, on: directives, or nested blocks.
| Parameter | Type | Description |
|---|---|---|
items | reactive let | The collection. A const renders once at mount; a let updates on reassignment. |
item | identifier | Name for the current element inside the body. |
(key) | expression? | Optional unique key. Keyed blocks retain and move nodes by key; keyless blocks reconcile by index. Duplicate keys throw. |
Returns—
<ul class="users">{#each users as user (user.id)}<li class={user.rowClass}>{user.name}</li>{/each}</ul><wizz:head>
block component root<wizz:head> <title/> <meta/> <link/> </wizz:head> Per-component document metadata. Server-rendered into the head and adopted on hydration; client mounts prepend tagged nodes and destroy() releases them. Multiple titles resolve last-in-tree-wins.
| Parameter | Type | Description |
|---|---|---|
children | <title> | <meta> | <link> | The only permitted elements. Expressions evaluate at render time; on: directives are skipped. |
Returns—
<wizz:head>
<title>{post.title} — Blog</title>
<meta name="description" content={post.summary}>
<link rel="canonical" href={post.url}>
</wizz:head><wizz:style>
block component root<wizz:style> /* raw CSS */ </wizz:style> Component-scoped CSS. Delivered once per document in dev, adopted on hydration, refcounted on client mounts, and extracted to dist/app.css by wizz build. Expressions are never interpolated into CSS.
| Parameter | Type | Description |
|---|---|---|
css | raw CSS | Scoped by a data-wizz-s attribute on each selector's terminal element; @keyframes names gain the scope suffix; @media is descended. |
Returns—
<wizz:style>
.card { padding: 1rem; }
.card:hover h2 { color: var(--accent); }
@keyframes pulse { 50% { opacity: 0.6; } }
</wizz:style>Generated modules
Each component compiles to Name.js; server-renderable components also get Name.server.js and Name.hydrate.js. These surfaces are covered by the output version.
mountComponent()
default export Name.jsexport default function mountComponent(target: Node, props?: object): { setProps?(next: object): void; destroy(): void } Creates the component's DOM, appends its root to target, runs the initial update and mount hooks, and returns a handle. setProps exists only when the component declares props.
| Parameter | Type | Description |
|---|---|---|
target | Node | A live DOM node; the component's root is appended to it. |
props | object = {} | Initial values for declared props. |
Returns{ setProps?, destroy } — call destroy() exactly once.
import mountComponent from "/components/Counter.js";
const handle = mountComponent(document.getElementById("slot"), { label: "Clicks" });
handle.setProps?.({ label: "Taps" });
handle.destroy(); // exactly oncehydrateComponent()
named export Name.hydrate.jshydrateComponent(target: Element, props: object, state: object): Handle Adopts server-rendered markup in place: verifies tags, data-wizz-id attributes, and text against the template, then attaches listeners. On any mismatch it warns once, clears target, and mounts fresh.
| Parameter | Type | Description |
|---|---|---|
target | Element | The mount point that already contains the server-rendered markup. |
props | object | The same props the server rendered with. |
state | object | The parsed application/wizz-state payload. |
ReturnsThe same handle shape as mountComponent().
import { hydrateComponent } from "/pages/Home.hydrate.js";
const script = document.querySelector('script[type="application/wizz-state"]');
const state = JSON.parse(script.textContent);
script.remove();
const handle = hydrateComponent(document.getElementById("app"), {}, state);hydrateRoot()
named export Name.hydrate.jshydrateRoot(rootNode: Element, props: object, state: object): Handle Adopts the given node itself as the component root. Parents use it for nested hydration; a structural mismatch remounts inside the child's root without detaching the parent.
| Parameter | Type | Description |
|---|---|---|
rootNode | Element | The exact element to adopt as the component root. |
props | object | Props for the component. |
state | object | The component's state slice (for children: state.__wizz.components[id]). |
ReturnsThe component handle.
// Used by parents during nested hydration; available on every hydratable module.
import { hydrateRoot } from "/components/Card.hydrate.js";
const handle = hydrateRoot(cardElement, { title: "Intro" }, cardState);renderComponent()
named export Name.server.jsrenderComponent(props?: object, options?: object): { html: string; head?: string; state: object } Renders escaped HTML without a DOM. Event handlers and hooks are skipped; author scripts run, so browser globals at the top level throw.
| Parameter | Type | Description |
|---|---|---|
props | object = {} | Props to render with. |
options | object = {} | Present when the component or a child declares <wizz:head>; carries the head owner path. |
Returns{ html, head?, state }
import { renderComponent } from "./dist/pages/Home.server.js";
const { html, head, state } = renderComponent({});
// html: escaped markup, no DOM involved
// head: serialized head markup (only when <wizz:head> is declared)
// state: reactive state snapshot, children under state.__wizz.componentsserializeInitialState()
named export Name.server.jsserializeInitialState(state: object): string Serializes state into an inline application/wizz-state script with every '<' escaped. Deliver it as a sibling of the mount point, never inside it.
| Parameter | Type | Description |
|---|---|---|
state | object | The state returned by renderComponent(). |
ReturnsAn HTML string.
import { renderComponent, serializeInitialState } from "./dist/pages/Home.server.js";
const { html, state } = renderComponent({});
const body = '<div id="app">' + html + "</div>" + serializeInitialState(state);
// '<script type="application/wizz-state">{"count":0}</script>' — every "<" escapedCompiler API
Exported from src/compiler (CommonJS). Most applications never call it directly — wizz build and wizz dev do.
compile()
function src/compilercompile(source: string, options?: { filePath?, hydratable?, diagnostics? }): { source, payload, sourceMap, version, diagnostics? } Compiles a component to a mountable ES module. Errors are SyntaxErrors carrying a stable code, line, column, and (with filePath) a caret code frame.
| Parameter | Type | Description |
|---|---|---|
source | string | The .wizz component source. |
options.filePath | string? | Qualifies errors with the path and adds a caret code frame. Always pass it for files. |
options.hydratable | boolean? | Restrict to the server-renderable surface and also export hydrateComponent / hydrateRoot. |
options.diagnostics | 'collect'? | Return { diagnostics: [...] } records instead of throwing. |
Returnssource (module text), payload (analyzed AST), sourceMap (v3 or null), version (frozen triple).
const { compile } = require("./src/compiler");
const { source, payload, sourceMap, version } = compile(componentSource, {
filePath: "src/pages/Home.wizz"
});
try {
compile("<main></section>");
} catch (error) {
error.code; // "WIZZ-P018" — mismatched closing tag
error.line; // structured location
}
const result = compile("<main><p>hi", { filePath: "src/App.wizz", diagnostics: "collect" });
result.diagnostics[0].code; // "WIZZ-P022"compileServer()
function src/compilercompileServer(source: string, options?: object): { source, payload, sourceMap: null, version } Shares the parse → analyze → assign pipeline and emits a DOM-free server module. Component tags render through each child's own server module, gated by the eligibility options.
| Parameter | Type | Description |
|---|---|---|
source | string | The .wizz component source. |
options.filePath | string? | As for compile(). |
options.diagnostics | 'collect'? | As for compile(). |
options.componentServerRenderable | Record<string, true> | Import names whose own server builds compile; a missing entry rejects that tag. |
options.componentIneligibilityReasons | Record<string, string> | Child failure reasons, chained into the diagnostic as 'Underlying reason: …'. |
ReturnsThe same shape as compile(), with sourceMap always null.
const { compileServer } = require("./src/compiler");
const { source, sourceMap } = compileServer(componentSource, {
filePath: "src/components/Counter.wizz"
});
// source exports renderComponent() and serializeInitialState(); sourceMap is always nullVERSIONS
constant src/compilerVERSIONS: Readonly<{ compiler: string; syntax: string; output: string }> The frozen compatibility triple, also stamped on the first line of every generated module and printed by wizz --version.
| Parameter | Type | Description |
|---|
Returns—
const { VERSIONS } = require("./src/compiler");
VERSIONS;
// { compiler: "1.17.0", syntax: "1.4.1", output: "1.8.2" } (frozen)Runtime
createRouter()
function runtime/router.jscreateRouter({ routes, hydratableRoutes?, target, window, document }): { render(), navigate(pathname), destroy() } The client router used by runtime/main.js. Exact pathname matching, first-render hydration, lazy imports, race-safe renders, and a popstate listener.
| Parameter | Type | Description |
|---|---|---|
routes | Record<path, () => Promise<module>> | Lazy client module loaders keyed by exact pathname. |
hydratableRoutes | Record<path, () => Promise<module>> | Optional hydratable loaders, used only for the first render. |
target | Element | The mount point (#app). |
window / document | Window / Document | Injected globals, which keeps the router testable. |
Returns{ render(): Promise, navigate(pathname): Promise, destroy(): void }
import { createRouter } from "/runtime/router.js";
const router = createRouter({ routes, hydratableRoutes, target, window, document });
await router.render(); // match location.pathname, hydrate or mount
await router.navigate("/docs"); // pushState + render
router.destroy(); // remove popstate listener + current pagehandler(request)
default export src/server/api/*.jsexport default async function handler(request: Readonly<Request>): unknown Server-side route handler. Plain value → 200 JSON, string → text, undefined → 204, { status, headers, body } → full control, throw → fixed 500.
| Parameter | Type | Description |
|---|---|---|
request.method | string | 'GET', 'POST', … |
request.path | string | Decoded pathname. |
request.query | URLSearchParams | Parsed query string. |
request.headers | object | Lowercased header names. |
request.body | string | null | Raw body text (max 1 MB, else 413). |
request.json() | () => Promise<any> | Parses the body; malformed JSON answers 400. |
ReturnsSee the return-value table in Server APIs & SSR.
// src/server/api/health.js → /api/health
export default async function handler(request) {
return { ok: true, method: request.method }; // 200 JSON
}Diagnostic codes
Every author-facing compile failure carries a stable code. Codes are never reworded into a different meaning or reused, so tooling can switch on them. WIZZ-A### is reserved and empty.
Parse stage — WIZZ-P
| Code | Condition |
|---|---|
WIZZ-P001 | Template tokenizer error |
WIZZ-P002 | Malformed {#each} syntax |
WIZZ-P003 | Unexpected {/each} |
WIZZ-P004 | Unexpected {/if} |
WIZZ-P005 | Unexpected {:else} |
WIZZ-P006 | Disallowed child inside <title> |
WIZZ-P007 | Disallowed element inside <wizz:head> |
WIZZ-P008 | Element inside <wizz:style> (raw CSS only) |
WIZZ-P009 | Attributes on <wizz:head> |
WIZZ-P010 | Nested <wizz:head> |
WIZZ-P011 | Attributes on <wizz:style> |
WIZZ-P012 | Nested <wizz:style> |
WIZZ-P013 | Duplicate <wizz:head> |
WIZZ-P014 | Duplicate <wizz:style> |
WIZZ-P015 | Block directive inside <wizz:head> |
WIZZ-P016 | Expression directly inside <wizz:head> |
WIZZ-P017 | Expression inside <wizz:style> |
WIZZ-P018 | Mismatched closing tag |
WIZZ-P019 | Only <title>, <meta>, <link> allowed in <wizz:head> |
WIZZ-P020 | Plain <style> — use <wizz:style> |
WIZZ-P021 | Closing tag on a void element |
WIZZ-P022 | Unclosed tag |
WIZZ-P023 | Unexpected closing tag |
WIZZ-P024 | Unsupported token type |
WIZZ-P025 | Unclosed string in expression |
WIZZ-P026 | Unexpected character in expression |
WIZZ-P027 | Unparseable expression start |
WIZZ-P028 | Expected closing parenthesis |
WIZZ-P029 | Expected property after . |
WIZZ-P030 | Unexpected expression token |
WIZZ-P031 | Missing {#if} condition |
WIZZ-P032 | Dynamic attribute missing expression |
WIZZ-P033 | Template expression error |
WIZZ-P034 | Invalid prop name |
WIZZ-P035 | Reserved prop prefix (__wizz) |
WIZZ-P036 | export let without a name |
WIZZ-P037 | export not at top level |
WIZZ-P038 | One prop per export let |
WIZZ-P039 | Invalid prop declaration |
WIZZ-P040 | Duplicate prop |
WIZZ-P041 | Prop missing value |
WIZZ-P042 | Prop missing semicolon |
WIZZ-P043 | Unsupported export syntax |
WIZZ-P044 | persist() on a prop |
WIZZ-P045 | persist() on a const |
WIZZ-P046 | persist() not at top level |
WIZZ-P047 | Statements trailing a persist() declaration |
WIZZ-P048 | persist() missing arguments |
WIZZ-P049 | persist() extra arguments |
WIZZ-P050 | persist() key not a string literal |
WIZZ-P051 | Nested persist() |
WIZZ-P052 | Unparseable persist() call |
WIZZ-P053 | Expression depth budget exceeded |
WIZZ-P054 | Template depth budget exceeded |
Generate stage — WIZZ-G
| Code | Condition |
|---|---|
WIZZ-G001 | Template has no root element |
WIZZ-G002 | {#each} outside a native element |
WIZZ-G003 | {#each} body must have one root element |
WIZZ-G004 | Component inside {#each} |
WIZZ-G005 | Unsupported node inside {#each} (nested blocks) |
WIZZ-G006 | on: directive inside {#each} |
WIZZ-G007 | Invalid prop attribute name |
WIZZ-G008 | Children on a component tag |
WIZZ-G009 | Component missing its id (internal pipeline guard) |
WIZZ-G010 | Component tag outside a native element |
WIZZ-G011 | on: directive on a component tag |
WIZZ-G012 | Event directive missing handler |
WIZZ-G013 | Invalid event directive |
WIZZ-G014 | Disallowed child inside <title> |
WIZZ-G015 | Assignment to a prop |
WIZZ-G016 | Imports must each be on their own line |
WIZZ-G017 | Reserved reactive state name (__proto__, __wizz…) |
WIZZ-G018 | Server build: unsupported node |
WIZZ-G019 | Server build: unsupported head node |
WIZZ-G020 | Server build: component not server-renderable |
WIZZ-G021 | Void element with children |
WIZZ-G022 | Hydration: unsupported node |
WIZZ-G023 | Hydration: unsupported node in {#each} |
WIZZ-G024 | persist() rewrite drift (internal guard) |