scrml

A single-file, full-stack reactive web language. Stop wiring. Start building.

MIT open source pre-1.0 active development GitHub repo
View on GitHub Read the tutorial Language spec

What is scrml?

scrml is a compiled language that replaces your frontend framework, backend glue, and most of your build toolchain with one file type. Write markup, reactive state, scoped CSS, SQL, server functions, realtime channels, and inline tests together in a .scrml file. The compiler splits server from client, wires reactivity, routes HTTP, types the database schema, and emits plain HTML, CSS, and JavaScript.

No virtual DOM. No JSX. No separate route files. No state management library. No node_modules.

A reactive counter, the whole file

<program>

@count = 0
@step = 1

<div class="counter">
    <span class="value">${@count}</>
    <button onclick=${@count = @count + @step}>+</>
    <button onclick=${@count = @count - @step}>-</>
    <select onchange=${@step = event.target.value}>
        <option value="1">step 1</>
        <option value="5">step 5</>
    </>
</>

Why scrml

State is first-class

State is named, typed, and instantiable. A < Card> declares a state type; <Card> instantiates one. HTML elements like <input> and <program> are pre-defined state types — the language has no conceptual gap between user-defined state and built-in state. Because state lives in the type system, it flows through match, fn signatures, the server/client boundary, and the database schema — all statically checked.

Mutability contracts

Any mutable variable can carry a compile-time contract about what it's allowed to be. Value predicates (@price: number(>0 && <10000)) constrain every write. Presence lifecycle (not, is some, is not, lin) gates reads until a value exists and ensures exact-once consumption. State transitions (< machine>) declare an enum's legal moves — .Locked => .Unlocked — and reject assignments that skip a step. Layer these as you need them; leave them off where you don't.

Full-stack in one file

Markup, logic, styles, SQL, server functions, error handling, tests — everything lives in .scrml. The compiler analyzes your code and splits it across server and client automatically. No API layer to maintain, no route files to keep in sync.

Realtime and workers are first-class

A <channel> element declares a WebSocket endpoint — the compiler generates the upgrade route, the client connection manager, auto-reconnect, and pub/sub topic routing. @shared variables inside a channel sync across every connected client automatically. Heavy work goes in a nested <program> that compiles to a Web Worker or WASM module — with typed RPC, supervised restarts, and when message from <#name> event hooks.

The compiler eliminates N+1 automatically

Because scrml owns both the query context and the loop context, a for (let x of xs) { ?{... WHERE id = ${x.id}}.get() } pattern is rewritten to one pre-loop WHERE id IN (...) fetch plus a keyed Map lookup. Independent reads in a ! handler share one BEGIN DEFERRED..COMMIT envelope. On-mount server @var loads coalesce into a single __mountHydrate round-trip. Measured wins: ~2× at N=10, ~3× at N=100, ~4× at N=1000 on on-disk WAL SQLite.

Quick start

# Install (Bun required)
bun install

# Compile a single file
scrml compile examples/01-hello.scrml -o dist/

# Or run the watcher
scrml dev examples/

Status

scrml is in active pre-1.0 development. The language spec evolves as we find friction and the compiler catches up. The changelog tracks what just landed and what's in flight. The compiler runs on Bun; compiled output is plain JavaScript and runs in any browser or JavaScript runtime.

Learn more