O WingedSwift nasceu com uma ideia simples: montar HTML com uma DSL Swift, type-safe, sem browser, sem DOM, sem runtime. A saída é texto. Isso não mudou na 2.0 e não vai mudar.
O que mudou foi tudo que estava em volta dessa ideia e envelheceu mal — uma variável global controlando como o HTML era formatado, uma página que você montava na mão e torcia para não esquecer o doctype, e containers que só aceitavam array de filhos. Este artigo é o resumo do que a 2.0 trouxe, sem você precisar ler o changelog inteiro.
Se você tem um projeto rodando em 1.x e quer o passo a passo, o texto é outro: Migrando do WingedSwift 1.3.3 para o 2.0. Se você nunca usou e quer começar do zero, vá direto para o tutorial com Tailwind CSS.
Índice
- Document é o novo topo da página
- RenderOptions no lugar do estado global
- Result builder em todo container
- Swift 6.0 e Xcode 16 viraram requisito
- A CLI winged
- O que não mudou
- Por onde seguir
- Recursos
Document é o novo topo da página
Na 1.x a função html { } produzia só o elemento <html>. O <!DOCTYPE html> vinha do
StaticSiteGenerator, e o lang você pendurava com setAttribute — quando lembrava. O resultado
é que "página correta" era algo que você montava certo, não algo que vinha pronto.
A 2.0 tem um tipo para isso:
let page = Document(lang: "pt-BR") {
Meta(charset: "UTF-8")
Title(content: "Início")
} body: {
H1(content: "Olá")
}
print(page.render())
// <!DOCTYPE html>
// <html lang="pt-BR">
// <head>
// <meta charset="UTF-8">
// <title>Início</title>
// </head>
// …Document é dono do doctype e do lang. Tem também a forma com head: e body: já construídos,
que é a que eu uso quando as duas metades vêm de funções separadas:
Document(lang: "pt-BR", head: Head { ... }, body: Body { ... })E se algo mais precisar ser dono do doctype, page.root() devolve só o elemento <html>, com o
lang já aplicado.
Detalhe que pega quem vem da 1.x: Document.render() sem argumento sai pretty, enquanto
HTMLTag.render() sem argumento sai compact. Faz sentido — documento normalmente é arquivo que
alguém abre — mas é uma diferença que surpreende. Para o site que vai pro ar, passe .compact
explicitamente.
RenderOptions no lugar do estado global
Esse é o meu favorito, porque era o pior pedaço da 1.x. Existia um HTMLTag.xhtmlSelfClosing
estático: você mexia numa variável do processo inteiro para decidir se <img> saía com barra no
fim. Dois trechos de código renderizando com regras diferentes ao mesmo tempo era impossível.
Agora é valor:
page.render(.compact) // uma linha só — é o que você publica
page.render(.pretty) // indentado, para ler
page.render(RenderOptions(pretty: true, indent: " ", xhtmlSelfClosing: false))RenderOptions é Sendable e Equatable. Como é parâmetro e não estado, duas tarefas podem
renderizar com configurações diferentes ao mesmo tempo sem uma pisar na outra.
Uma exceção que continua valendo: pre, code e textarea sempre saem compactos, mesmo no
.pretty. Indentação dentro deles é texto visível na página.
Result builder em todo container
Na 1.x, filhos iam num array: Div(children: [ ... ]). Funciona, mas array não aceita if nem
for no meio, então qualquer conteúdo condicional virava uma variável montada antes.
Na 2.0 todo container tem a forma com closure:
Section {
H2(content: "Recursos Principais")
.addClass("text-4xl font-bold text-center mb-16")
Div {
// `map` dentro do builder: compila sem ambiguidade,
// não precisa montar o array antes.
features.sorted { $0.order < $1.order }.map { featureCard(feature: $0) }
}
.addClass("grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8")
if mostrarRodape {
P(content: "Mais recursos em breve")
}
}E aqui vale o aviso que economiza discussão: E aqui vale o aviso que economiza discussão: **a forma com children: não foi descontinuada. não foi descontinuada.** As
duas convivem no mesmo arquivo e produzem o mesmo HTML. Onde já existe um array em mãos, children:
é mais direto; onde tem if, for ou map, o builder ganha de longe. Não é migração obrigatória,
é escolha por trecho.
Swift 6.0 e Xcode 16 viraram requisito
A biblioteca compila em language mode 6, então o compilador precisa ser 6.0 ou mais novo. Isso é
sobre o compilador, não sobre o seu manifesto: um pacote com swift-tools-version: 5.9 consegue
depender do WingedSwift 2.0 numa toolchain 6.x sem problema.
O que veio junto: a árvore de tags O que veio junto: a árvore de tags **não é Sendable**, e agora isso é explícito. HTMLTag é uma
classe mutável — monte e renderize a página dentro de uma task só, e passe a String resultante
entre isolation boundaries. Attribute, RenderOptions, StaticSiteGenerator e RSSGenerator
são Sendable.
A CLI winged
A 2.0 trouxe um executável junto com a biblioteca:
git clone https://github.com/micheltlutz/Winged-Swift.git
cd Winged-Swift && swift build -c release
# o binário fica em .build/release/winged
winged new MeuSite --tailwind
cd MeuSite
winged build # gera em dist/
winged serve --watch # http://localhost:8000, rebuild a cada alteraçãoSão três subcomandos:
winged new <nome>cria o esqueleto do projeto. Aceita--tailwind,--title,--description,--lange--path.winged buildcompila e roda o projeto, gerando emdist/. Aceita--output,--pathe--release.winged servesobe um servidor local. Aceita--port(8000),--output(dist),--watche--no-build.
Um detalhe que vale saber antes de apontar a CLI para um projeto existente: o winged build
descobre o executável lendo o Package.swift (primeiro produto executável, ou o primeiro target
executável se não houver produto declarado) e depois roda esse binário passando o diretório de
saída como primeiro argumento. Ou seja, ele espera um projeto que leia o output dir de
CommandLine.arguments — que é o que o winged new gera. Um projeto que escreve num caminho fixo
compila e roda, mas o site cai no caminho fixo e o CLI reporta zero páginas em dist/.
Para esses casos existe o --no-build, que pula o build e serve o que já está lá:
winged serve --output output --no-buildO que não mudou
Nada foi removido na 2.0. Código escrito na 1.x continua compilando e gerando o mesmo HTML.
Cinco membros ficaram marcados como deprecated e saem na 3.0:
HTMLTag.xhtmlSelfClosing(a global)render(pretty:indentLevel:)renderCompact()renderPretty(indentLevel:)HTMLTag(_:_:)com a closure de atributos sem rótulo, que era ambígua com o builder de filhos
Repare no que não está nessa lista: html { }, Div(children:) e
generate(page:to:pretty:doctype:) seguem vivos e sem aviso de depreciação. Document e
RenderOptions são o caminho novo, não um ultimato.
O resto continua igual: conteúdo e atributos escapados por padrão (Script e Style são a exceção,
porque escapar corromperia JS e CSS), void elements sem tag de fechamento, Fragment para agrupar
sem <div> extra, RawHTML para injetar markup pronto, e os helpers encadeáveis addClass,
setId, dataAttribute, ariaAttribute.
Por onde seguir
- Tem projeto em 1.x? Migrando do WingedSwift 1.3.3 para o 2.0 — antes e depois de cada ponto, com checklist.
- Começando do zero? Criando uma página estática com WingedSwift e Tailwind CSS — do
Package.swiftao deploy.


