scrml.dev v0.8.0
Get started · scrml
scrml.dev › Getting started

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> = 0 is reactive on declaration. No useState, no writable(), no ref().
  • No JSX. The markup IS the language. Curly braces inside ${ } evaluate; everywhere else they're text.
  • Standard event names. onclick=decrement() — no @click, (click), or onClick.
  • 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=@step wires 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 null literal and no undefined concept. Use not to express absence and "" · 0 · [] for empty values.
  • Cells declare with <x> = init at top level. Inside ${ } logic context, the assignment-form @x = init also declares.
  • One file format. Markup, logic, styles, server functions, SQL, all in the same .scrml file. The compiler decides what runs where.