Cópia estática
Copiado de docs/packages/aipo-html.md em https://github.com/poppy-team/aipo-lang (MIT). Fixado na revisão ce47c4e070404fc41722ccd8e2b7b808b99062a8, blob 1798cc507a0a696e5645a772b6e6a1d3edfa83ad. O repositório de origem permanece canônico; esta cópia é atualizada por um pull request de sincronização, não em tempo real.
aipo.html é o pacote canônico da linguagem Aipo para construção de aplicações web modernas, Single Page Applications (SPAs) e renderização no lado do servidor (Server-Side Rendering — SSR).
O pacote combina:
do ... end ou { ... }.render_to_string): geração instantânea de HTML5 estático com mitigação contra XSS via escape automático de entidades em corpos de texto e atributos.fragment): agrupamento de múltiplos nós irmãos sem emitir elementos contêineres adicionais no HTML final.css): geração determinística de classes com hash, suporte a pseudo-classes (hover, active, focus) e blocos @media responsivos, com injeção automática no <head> (get_injected_css).mount, Cmd): arquitetura inspirada em The Elm Architecture, permitindo estado previsível com [model, cmd].No arquivo aipo.toml do seu projeto:
[dependencies]
"aipo.html" = { path = "packages/aipo-html" }O aipo.html provê funções de tag nativas que aceitam atributos como dicionário e o corpo de elementos filhos via bloco de fechamento (trailing block):
import aipo.html as h
let page = h.div({ "class": "container", "id": "main" }) do
h.header do
h.h1("Portal Aipo")
end
h.main do
h.section({ "class": "hero" }) do
h.p("Desenvolvimento web declarativo e determinístico.")
h.button("Explorar", { "class": "btn-primary" })
end
end
h.footer do
h.span("© 2026 Aipo Lang")
end
endElementos sem fechamento (como img, input, hr, br) são tratados como tags folha automáticas:
let avatar = h.img("avatar.png", "Avatar do Usuário", { "class": "rounded-full" })
let email_field = h.input({ "type": "email", "placeholder": "contato@aipo.dev" })
let line = h.hr()Quando você deseja renderizar múltiplos nós sem criar um nó pai <div> desnecessário no DOM ou no SSR:
let list_items = h.fragment do
h.li("Primeiro item")
h.li("Segundo item")
h.li("Terceiro item")
endA função render_to_string converte uma árvore de nós em uma string HTML5 segura:
import aipo.html as h
let doc = h.div({ "class": "article-card" }) do
h.h2("Escaping <script> & Seguro")
h.p("Conteúdo auditado sem injeção de código.")
h.input({ "disabled": true, "type": "text" })
end
let html = h.render_to_string(doc)
# Produz:
# <div class="article-card"><h2>Escaping <script> & Seguro</h2><p>Conteúdo auditado sem injeção de código.</p><input disabled type="text" /></div>O h.css gera classes de estilo com hashes únicos e isolamento de escopo:
import aipo.html as h
let btn_style = h.css({
"padding": 12,
"background": "#6366f1",
"border_radius": 8,
"color": "#ffffff",
"hover": {
"background": "#4f46e5"
},
"media": {
"(min-width: 768px)": {
"padding": 20
}
}
})
# No elemento:
let botao = h.button("Ação", { "class": btn_style })
# Para embutir no <head> no SSR:
let css_head = h.get_injected_css()Para SPAs e interfaces dinâmicas, aipo.html implementa o padrão Model-View-Update completo com tuplas [model, cmd]:
import aipo.html as h
struct Model {
count
}
fn update(m, msg) {
if msg == "inc" {
return Model{ count: m.count + 1 }
} elif msg == "dec" {
return Model{ count: m.count - 1 }
} elif msg == "reset_and_double" {
# Retorna novo modelo e dispara comando subsequente
return [Model{ count: 10 }, h.cmd_msg("inc")]
}
return m
}
fn view(m, dispatch) {
return h.div({ "class": "counter-box" }) do
h.h1(f"Valor atual: {m.count}")
h.button("+1", { "on_click": fn () { dispatch("inc") } })
h.button("-1", { "on_click": fn () { dispatch("dec") } })
end
}
# Inicialização
let app = h.mount("#app", Model{ count: 0 }, update, view)