Skip to content
These are temporary community docs. The official docs are in progress.Help improve them
Markless
Esc
↑↓navigate↵open⌘Jpreview
On this page

Why Markless, technically

The technical choices behind Markless on one page, with a mechanism comparison against React, Solid, Svelte, and Qwik.

Last page asked why Markless insists on rules like a key for every list. The answer is the compiler: it plans everything before your app runs.

The short version

Markless is a compiler for .tsrx components plus a small browser runtime. The repo is at version 0.5.0. The latest npm release is 0.4.0.

  • The compiler plans every update. @markless/compiler runs 15 passes on each module. Before the app runs, it knows each state value, each reader, and each handler. See the compiler.
  • One compile renders anywhere. The same output renders in the browser with render(), on a server with renderToString(), and in tests with @markless/vitest-browser. Build-time prerendering is a preview. A server is one option, not a requirement. See the big idea.
  • Plain values. You write let count = state(0) and count++. @markless/core exports state, computed, shared, element, and storage. It exports no effect function.
  • The component body runs once. The body sets up the page one time. After that, only planned DOM writes run. Nothing re-renders.
  • Handlers load on first use. The compiler emits each handler as a symbol: a small module that the runtime loads by ID. No symbol loads before its first event.
  • Multi-page apps. @markless/router adds file routes on Nitro, which deploys to many targets.
  • No hydration. Hydration means the browser runs components again over server HTML. When a server renders the page, Markless resumes instead. It reads records that the server wrote and continues from them. See the payload.

How it compares

The rows for other projects come from their public docs. I did not run them for this page. The table compares mechanisms, not speed.

After server HTML On update How you write a reactive value Effect API
React Hydration Runs the component again, then diffs a virtual DOM useState and a setter useEffect
Solid Hydration Signals write the DOM createSignal, read as count() createEffect
Svelte 5 Hydration Compiled signals The $state rune $effect
Qwik Resumes Signals useSignal, read as .value, with $ on lazy code useTask$
Markless Resumes Compiler-planned DOM writes state(0), then plain count None

A signal is a value that tells its readers when it changes. Markless keeps a similar graph at runtime. The compiler wires it, so your code never names it.

What it costs

  • Handlers have limits. A handler can read state, element handles, props, imports, and values the compiler can write down. If it reads another local, such as a WeakMap, the compiler reports MARKLESS_EVENT_HANDLER_EMIT_UNSUPPORTED.
  • Server state must be data. On the server path, state values travel as JSON records. @markless/serializer refuses a function with MARKLESS_SERIALIZE_UNSUPPORTED_VALUE.
  • A new file type. A .tsrx file needs the Markless compiler to build. Editors need @markless/typescript-plugin to type-check it.

Next: What does the compiler decide, and where can its output run? The big idea →

Was this page helpful?