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

Plan at compile time, render anywhere

The Markless compiler decides state, updates, and event code before anything runs, and the same compiled component renders in the browser, on a server, at build time, or in tests.

Last page put compile-time planning first. Here is what the compiler decides, and where its output can run.

Decided before anything runs

Misconception: a framework must run your component to learn what it does. Markless reads it instead. At build time, the compiler works out three things from Counter.tsrx:

  1. The state. count becomes the graph node state:count.
  2. The updates. The text Count {count} becomes one planned text write.
  3. The event code. The click becomes its own symbol: a small module that the runtime loads by ID.

One compile also emits two render modules: one for the browser and one for a server.

Pick an environment. Then click Count.

Where can the same compiled Counter render?

Pick an environment. Then click Count in the page.

1. Planned once, by the compiler
  • state:countThe state. count starts at 0.
    Here: kept in the render module's graph, starts at 0.
  • symbol:1The text update. Sets the text of h0 to "Count " plus count.
    Here: wired to h0 by render().
  • symbol:0The click code, count++. Its own module, loaded by ID.
    Here: loads on the first click.
2. Render it here
Where to render Counter
CounterDOM from render()

Same page and same click behavior from every choice.

What it emits: main.ts and the DOM it builds
// main.tsawait render(Counter, { target }); (body runs once) // DOM inside the target<button>Count 0</button> container.payloadScripts // undefined (no notes)container.resumerScript // undefined (no resumer)
3. When you click
Component body runs1in the browser
Click code loads00 at mount
Text updates0
  1. ranrender() ran the Counter body once, in the browser, and built the button.
  2. noteNo JSON notes and no inline resumer. Nothing for the click has loaded.

Simplified. Artifact names come from compileTsrxModule() on Counter.tsrx. Tests call the click symbol symbol:click. One body run is asserted for render() and renderToString() in packages/web/test/render.test.ts. That file also asserts no symbol loads at mount and one loads on the first click. The server HTML is cut from one real build. The build-time markup follows assemblePrerenderPageParts, not a captured file. Build time is a preview.

Notice that the planned outputs stay the same. The body runs once, and only the first click loads the click code.

Rendered where you need it

Environment How you call it Notes
Browser render(App, { target }) from @markless/core No server needed
Server renderToString() from @markless/core, or renderToStream() from @markless/web HTML plus the plan as JSON
Multi-page app @markless/router File routes on Nitro, which deploys to many targets
Build time MARKLESS_PRERENDER=1 in the demos Preview, not stable
Tests render() and renderSSR() from @markless/vitest-browser renderSSR() renders in Node, then loads the HTML in the test browser
Native hosts UIKit and AppKit, see native targets Proofs, not wired to the compiler yet

A browser-only app needs no server:

import { render } from '@markless/core';
import App from './App.tsrx';

await render(App, { target: document.querySelector('#app')! });

What stays the same

  • The component body runs once, to set up the page. A browser test and a server test both count 1 run.
  • No handler runs before its event. In the browser test, no symbol loads at mount. The first click loads one.
  • After a write, only the text, attributes, and list rows that read the changed state update.

The environments differ only in delivery. On a server, the HTML carries the plan as JSON plus a small inline script. Hydration means the browser runs components again over server HTML. Markless skips it, and the browser does not run the body to start the page.

Next: What exactly does the compiler emit for Counter.tsrx? The compiler →

Was this page helpful?