Get started with scrml
Install the compiler, write your first
.scrml file,
run the dev server, ship a build.
Prerequisites
scrml's reference compiler runs on Bun. Bun is the only required runtime — no Node, no separate package manager, no bundler.
curl -fsSL https://bun.sh/install | bash
Verify the install. The compiler requires Bun 1.3.13 or newer:
bun --version
Install scrml
scrml is not published to npm. Install it from source: clone
the repository, install its dependencies, and link the
scrml
CLI onto your
PATH.
Then
scrml init
scaffolds a starter app:
git clone https://github.com/bryanmaclee/scrml && cd scrml && bun install && bun link
scrml init my-app
Confirm it works:
scrml --version
Hello, scrml
Create a file
hello.scrml:
<program>
<div class="flex flex-col items-center justify-center min-h-screen gap-4">
<h1 class="text-4xl font-bold">Hello, scrml</h1>
<p class="text-lg text-gray-600">No framework. Just output.</p>
</div>
</program>
Compile:
scrml compile hello.scrml -o dist/
That produces
dist/hello.html,
dist/hello.css,
dist/hello.client.js,
and a shared runtime file,
dist/scrml-runtime.<hash>.js.
Open the HTML in a browser. The runtime is scrml's own small
support library (about 41 KB unminified as of v0.8.0) and
the same file serves every page; the per-program file holds
only the code your program needs — for this page, a few
dozen bytes.
Reactivity in 30 seconds
Add state and event handlers. The
<x> = init
shape declares a reactive cell;
@x
reads it,
@x = expr
writes it. Any DOM read of
@x
re-renders automatically on write.
<program>
<count> = 0
<step> = 1
function increment() { @count = @count + @step }
function decrement() { @count = @count - @step }
<div class="flex flex-col items-center gap-6 p-8 min-h-screen">
<h1 class="text-3xl font-bold">Counter</h1>
<p class="text-6xl font-bold text-blue-600">${@count}</p>
<div class="flex gap-2">
<button class="px-5 py-2 bg-red-500 text-white rounded" onclick=decrement()>−</button>
<button class="px-5 py-2 bg-gray-200 rounded" onclick={ reset(@count); reset(@step) }>Reset</button>
<button class="px-5 py-2 bg-green-500 text-white rounded" onclick=increment()>+</button>
</div>
<label class="flex items-center gap-2 text-sm">
Step:
<input type="number" class="w-16 p-1 border rounded" bind:value=@step min="1" max="100">
</label>
</div>
</program>
Six things to notice:
-
No store wrapper.
<count> = 0is reactive on declaration. NouseState, nowritable(), noref(). -
No JSX.
The markup IS the language. Curly braces inside
${ }evaluate; everywhere else they're text. -
Standard event names.
onclick=decrement()— no@click,(click), oronClick. -
Short handlers stay inline.
A handler can be a braced block of statements —
onclick={ reset(@count); reset(@step) }— so a two-line action does not need a named function. -
Two-way binding without a directive.
bind:value=@stepwires the input's value both ways. - Tailwind out of the box. Utility classes work with no setup — the compiler scans your markup and emits only the CSS you use.
Dev server with hot reload
scrml dev .
Boots a local server on
localhost:3000,
watches every
.scrml
file in the directory you name (here, the current one), and triggers a recompile + browser
reload on every save. Compile errors print to the terminal
with a line/column pointer.
Custom port and output dir:
scrml dev . --port 3100 -o dist/
Build for production
scrml build .
Compiles every
.scrml
file in the directory into
dist/:
HTML, CSS, content-hashed JavaScript (so cache invalidation
is automatic) and a server entry point,
dist/_server.js.
--output <dir>
picks another directory;
--target static
builds a site with no server (this site is built that way).
A note on
<auth role>:
it controls which client code mounts for which role. It is not
content secrecy — gated HTML is still sent to every viewer
(the compiler warns with
W-AUTH-CONTENT-NOT-GATED),
so keep secrets on the server. Per-role JavaScript chunks are
opt-in, through
scrml compile --emit-per-route.
Where to go next
- <engine> — state machines as a first-class element. The Tier 2 form of "UI as a fully-handled state machine."
- ?{ … } — SQL as syntax. No ORM. The server boundary disappears.
- <errors of=…/> — the auto-synthesised validity surface. Validation lives on the type, not on a separate Zod- style schema.
- Reference — every element, keyword, context, and error code.
- Articles — long-form essays on the design decisions.
Standing notes
-
No null, no undefined.
scrml has no
nullliteral and noundefinedconcept. Usenotto express absence and""·0·[]for empty values. -
Cells declare with
<x> = initat top level. Inside${ }logic context, the assignment-form@x = initalso declares. -
One file format.
Markup, logic, styles, server functions, SQL, all in
the same
.scrmlfile. The compiler decides what runs where.