Pular para o conteúdo

O livro · 14

Extensão de UI

book.md · 90 linhas · 2 min de leitura

Objetivo: entender que UI é uma extensão de compilador (não core): arquivos .mkoui, reatividade por decorator, modelo de ilhas, e a fronteira servidor/cliente como channel de rede.

O core de Makoto não sabe o que é uma <div>. A UI é a primeira extensão oficial de compilador: ela registra um tipo de arquivo (.mkoui, com sua gramática de markup), decoradores (@state/@component/…) e alvos de codegen (JS/WASM/SSR). Um arquivo .mko nunca vê markup; quem não faz UI nem encosta. A reatividade é volátil por natureza (paradigmas reativos giram a cada poucos anos), e isolá-la numa extensão deixa ela envelhecer e ser trocada sem tocar o core.

A extensão liga por flag/manifesto; markup mora só em .mkoui.

Props são os parâmetros; o markup vai no return {} (não há retorno implícito: markup é o conteúdo de um return block):

@component
fn Greeting(name: string, count: int) {
return {
<div class="card">
<h1>Olá, {{name}}</h1>
<p>Você tem {{count}} mensagens</p>
</div>
}
}

Binding no markup, por posição: {{ }} interpola conteúdo (texto via Display, capítulo 05); { } liga uma expressão depois de um = (prop, atributo dinâmico, handler).

@state count := 0 // estado reativo: mudou → re-renderiza quem depende
@derived doubled := count * 2 // recomputa quando 'count' muda
@effect { log("count = {{count}}") } // roda quando suas dependências mudam

Cada decorator é o construto do core mais a plumbing reativa que o compilador injeta. O modelo é de granularidade fina (estilo SolidJS): cada pedaço de estado sabe exatamente quem depende dele. Não há re-execução de componente inteiro nem Virtual DOM; o diff é direto no DOM real.

Você marca só o que é preso a um lado; o resto atravessa:

@server fn load_user(id: int) -> User { return db.query(...) } // só servidor: nunca vai pro cliente
@client fn on_scroll() { ... } // só cliente: roda no browser (WASM)
fn format_date(d: DateTime) -> string { ... } // sem marca: roda onde for chamada

A maior parte da página é HTML estático renderizado no servidor; só os pedaços interativos (as “ilhas”) viram WASM no cliente, carregando apenas a reatividade daquela ilha. Não há hidratação de página inteira (o custo do React): você manda pro browser só o que reage.

A fronteira servidor/cliente é um channel de rede

Seção intitulada “A fronteira servidor/cliente é um channel de rede”

Quando uma ilha precisa falar com o servidor, é o channel de rede do capítulo 09, não um mecanismo de UI novo. Servidor e cliente são dois mundos isolados conversando por Channel[T] com T + Serializable:

@client
fn 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>
}
}

CSS escopado (hashing de classe), @css pra CSS externo, e @jsimport (FFI de JS, sob o namespace js.) completam a extensão, todos restritos a .mkoui.

Próximo: 15 · Tooling na prática