UI extension
Goal: understand that UI is a compiler extension (not core):
.mkouifiles, reactivity by decorator, the island model, and the server/client boundary as a network channel.
UI is an extension, and why
Section titled “UI is an extension, and why”Makoto’s core does not know what a <div> is. UI is the first official compiler extension: it
registers a file type (.mkoui, with its markup grammar), decorators
(@state/@component/…), and codegen targets (JS/WASM/SSR). A .mko file never sees markup; whoever
does no UI never touches it. Reactivity is volatile by nature (reactive paradigms turn over every few
years), and isolating it in an extension lets it age and be swapped without touching the core.
The extension turns on by flag/manifest; markup lives only in .mkoui.
Component = a function that returns markup
Section titled “Component = a function that returns markup”Props are the parameters; the markup goes in the return {} (there is no implicit return: markup is the content
of a return block):
@componentfn Greeting(name: string, count: int) { return { <div class="card"> <h1>Hello, {{name}}</h1> <p>You have {{count}} messages</p> </div> }}Binding in the markup, by position: {{ }} interpolates content (text via Display, chapter 05); { }
binds an expression after an = (prop, dynamic attribute, handler).
Reactivity by decorator
Section titled “Reactivity by decorator”@state count := 0 // reactive state: changed → re-renders whoever depends@derived doubled := count * 2 // recomputes when 'count' changes@effect { log("count = {{count}}") } // runs when its dependencies changeEach decorator is the core construct plus the reactive plumbing the compiler injects. The model is fine-grained (SolidJS style): each piece of state knows exactly who depends on it. There is no re-execution of the whole component and no Virtual DOM; the diff is direct on the real DOM.
Where the code runs: @server / @client
Section titled “Where the code runs: @server / @client”You mark only what is tied to one side; the rest crosses over:
@server fn load_user(id: int) -> User { return db.query(...) } // server only: never goes to the client@client fn on_scroll() { ... } // client only: runs in the browser (WASM)fn format_date(d: DateTime) -> string { ... } // unmarked: runs where it is calledThe island model
Section titled “The island model”Most of the page is static HTML rendered on the server; only the interactive pieces (the “islands”) become WASM on the client, loading only that island’s reactivity. There is no full-page hydration (React’s cost): you send to the browser only what reacts.
The server/client boundary is a network channel
Section titled “The server/client boundary is a network channel”When an island needs to talk to the server, it is the network channel of chapter 09, not a new
UI mechanism. Server and client are two isolated worlds talking over Channel[T] with
T + Serializable:
@clientfn LikeButton(post_id: int) { @state liked := false fn handle_click() { chan := net.connect[LikeAction]("/api/like") catch |e| { return } chan <- LikeAction{ post: post_id } reply := -> chan timeout(3s) catch |e| { return } liked = reply.ok } return { <button onclick={handle_click}>{{ liked ? "♥" : "♡" }}</button> }}Scoped CSS (class hashing), @css for external CSS, and @jsimport (JS FFI, under the namespace
js.) complete the extension, all restricted to .mkoui.
Next: 15 · Tooling in practice