Composição de templates: variáveis como blocos de HTML já renderizados
Às vezes um template fica grande demais para conviver com ele. Centenas de itens <li> numa página de meios de pagamento, dezenas de observações editoriais inline, mudanças de ordenação que precisam se propagar por todas as variantes — em algum momento um único template gigante e aninhado vira o gargalo, não a ajuda. O passo seguinte é dividi-lo num pipeline de templates pequenos, ligados por variáveis que guardam HTML já renderizado.
A virada de chave
Até aqui, nesta série, uma variável foi um valor: um nome de marca, um ano, uma lista de recursos separada por vírgulas. Strings simples, substituídas no template na hora do render.
A virada deste guia é pequena e poderosa: o valor de uma variável pode ser HTML já resolvido. Não "Acme Co.", e sim <h3>Crypto deposits</h3><ul><li>BTC — fastest…</li>…</ul>. O resolvedor não se importa; ele só substitui.
É isso que libera a composição. Você monta a página a partir de um pipeline de subtemplates pequenos, cada um renderizado como um bloco de HTML, e depois montados por um orquestrador de poucas linhas.
Sem composição
<h3>Crypto deposits</h3>
<ul>
<li>{Bitcoin|BTC} — {fastest|the most popular} option, {confirms in 10–60 min|settles within an hour}.</li>
<li>{Ethereum|ETH} — {smart-contract chain|programmable network}, {2–5 min blocks|fast block times}.</li>
/# … 8 more crypto items #/
</ul>
<h3>Fiat deposits</h3>
<ul>
/# … 12 more fiat items, each with editorial notes #/
</ul>
<h3>Deposit and withdrawal limits</h3>
<table>
/# … 20+ rows #/
</table>
Isso é um monólito de 200 linhas. Acrescentar uma moeda significa editar dentro de uma cadeia enorme de enumerações. Mudar a ordem é trabalho manual. As nuances editoriais de cada moeda ficam espalhadas pelo arquivo.
Com composição
%CryptoSection%
%FiatSection%
%LimitsSection%
Três linhas. Cada variável já guarda o HTML totalmente resolvido da sua parte da página. Os valores vêm de um pipeline que roda antes de o orquestrador ser renderizado.
Por que isso funciona — o pipeline do motor
A referência de sintaxe detalha a ordem de resolução; as linhas que importam para composição são:
- remover comentários;
- extrair as diretivas
#set/#def; - juntar variáveis;
- expandir as referências
%var%; - resolver enumerações
{a|b|c}; - resolver permutações
[a|b|c]; - pós-processar.
As variáveis expandem antes da resolução de enumerações e permutações. Quando essa etapa roda, %CryptoSection% já foi trocada pelo HTML que o montador calculou. Sem sintaxe especial — substituição de variável é literalmente troca de string.
Dá até para misturar camadas: uma permutação externa pode embaralhar seções pré-renderizadas.
[<sep="\n\n">%CryptoSection%|%FiatSection%|%LimitsSection%]
Cada seção é resolvida primeiro; depois a permutação reordena os blocos.
O pipeline de três níveis
O padrão vive em três camadas, cada uma um estágio de refinamento:
Nível 1 — Templates de item (por id)
A menor unidade reutilizável. Um template por item de dados: por moeda, por meio de pagamento, por plano, por entrada de FAQ, por SKU.
/# spintax.crypto_item.btc #/
<li>{Bitcoin|BTC} — {fastest|the most popular} option, {confirms in 10–60 min|settles within an hour}.</li>
/# spintax.crypto_item.eth #/
<li>Ethereum — {smart-contract chain|programmable network}, {2–5 min blocks|fast block times}.</li>
Nível 2 — Templates de seção
Envolvem a lista com estrutura. Use uma variável de placeholder para os itens já juntados.
/# spintax.section.crypto #/
<h3>{Crypto deposits|Cryptocurrencies accepted}</h3>
<p>{Pick from|We support} the following coins:</p>
<ul>%CryptoItems%</ul>
%CryptoItems% é "todos os itens por id resolvidos e juntados numa única string". Quem monta isso é o montador.
Nível 3 — Orquestrador
O template no nível da página. Referencia apenas variáveis de seção pré-renderizadas.
/# spintax.payment_options #/
<h2>{Accepted payment methods|How to pay}</h2>
%CryptoSection%
%FiatSection%
%LimitsSection%
É o orquestrador inteiro. Regras de edição: mudou a descrição de uma moeda? Edite um template de item. Entrou uma moeda nova? Solte um template de item e acrescente o id à lista ativa. Reordenar? Campo de ordenação, não mudança de template.
Passo a passo — uma página de meios de pagamento
Um lojista aceita BTC, USDT e ETH em cripto, e Visa, Mastercard e SEPA em moeda comum. Três consultas e um punhado de templates produzem a página inteira.
Pseudocódigo do montador que roda antes do render do orquestrador:
function buildPaymentVars(merchantId, lang) {
// 1. Pull active items, in display order.
const cryptos = db.query("active cryptos for merchant ordered by sort", merchantId);
const fiats = db.query("active fiats for merchant ordered by sort", merchantId);
// 2. Resolve each per-id template, join the chunks.
const cryptoItems = cryptos
.map(c => parser.process(templates.find(`crypto_item.${c.id}`, lang)))
.join("");
const fiatItems = fiats
.map(f => parser.process(templates.find(`payment_item.${f.id}`, lang)))
.join("");
// 3. Resolve each section template with item placeholders.
const cryptoSection = cryptos.length
? parser.process(templates.find("section.crypto", lang), { CryptoItems: cryptoItems })
: "";
const fiatSection = fiats.length
? parser.process(templates.find("section.fiat", lang), { FiatItems: fiatItems })
: "";
// 4. Limits section is similar; LimitsRows are joined <tr> chunks.
const limitsSection = (cryptos.length || fiats.length)
? parser.process(templates.find("section.limits", lang), { LimitsRows: buildLimitsRows(cryptos, fiats, lang) })
: "";
// 5. Return the variables the orchestrator references.
return {
CryptoSection: cryptoSection,
FiatSection: fiatSection,
LimitsSection: limitsSection,
HasCrypto: cryptos.length ? "1" : "",
HasFiat: fiats.length ? "1" : "",
};
}
O render do orquestrador então recebe essas variáveis junto com as normais de site e de runtime, e faz a passagem final.
Convenções de nome
Aqui a convenção vale mais que a liberdade, porque o montador acha os templates pelo id.
| Padrão | Exemplo |
|---|---|
spintax.<entity>_item.<id> | spintax.crypto_item.btc |
spintax.<entity>_row.<id> | spintax.crypto_row.btc (linha de tabela) |
spintax.section.<key> | spintax.section.crypto |
spintax.<page-name> | spintax.payment_options |
As variáveis seguem o mesmo formato:
%CryptoItems%,%FiatItems%,%LimitsRows%— blocos por id, já juntados%CryptoSection%,%FiatSection%,%LimitsSection%— seções resolvidas%HasCrypto%,%HasFiat%— marcadores ('1'ou'')
PascalCase para variáveis, snake_case para IDs, só ASCII nos dois.
Armazenamento é problema seu, não do spintax
O padrão funciona igual, não importa onde os subtemplates moram:
- tabela de banco (
templatescom id + corpo + lang) - arquivo JSON:
{ "crypto_item.btc": "<li>…</li>", … } - sistema de arquivos:
templates/crypto_item/btc.txt - campo de CMS por locale
O motor não precisa de banco de dados. Ele só substitui HTML resolvido nas referências de variável. O montador é código seu, escrito no runtime que comanda os seus renders. Um plugin de WordPress, um Cloudflare Worker, um script Node, uma função no Postgres — mesmo padrão.
Por que não #include? O motor tem uma diretiva de include, e para um único bloco compartilhado ela é o caminho mais curto. Este pipeline não é construído sobre ela, por três motivos: um template incluído é um documento próprio — ele enxerga variáveis de runtime, mas nunca os #set/#def do pai, então uma forma já sorteada não pode ser passada para dentro dele; ele só funciona onde o host instalou um resolvedor, e dois dos nossos deliberadamente não têm um (o playground e o servidor MCP), onde a linha simplesmente fica literal; e quando o resolvedor não conhece a referência, a linha some da saída sem dizer nada. Um montador mantém resolução, cache e tratamento de erro no seu próprio código, onde você enxerga os três. Comportamento completo: a seção de includes da referência de sintaxe.
Nuances editoriais por id
É aqui que o padrão brilha. As nuances editoriais moram junto do dado, não em cada página.
/# spintax.payment_item.visa — 3DS warning baked in #/
<li>Visa — {3DS-protected|with 3D Secure} debit and credit cards, {instant deposit|immediate confirmation}.</li>
/# spintax.crypto_item.xrp — destination-tag reminder per coin #/
<li>XRP — fast and {cheap|low-fee}, {do not forget the destination tag|destination tag is required}.</li>
/# spintax.payment_item.qiwi — legacy status per method #/
<li>QIWI — {legacy support|now legacy}, {accepted but discouraged|not recommended for new accounts}.</li>
Cada template por id captura a nuance uma vez. Três páginas, dez páginas, mil páginas — todas herdam os avisos certos. Passe o QIWI para "descontinuado" editando um template; todo render vira ao mesmo tempo.
Sem composição, essas nuances seriam strings inline duplicadas entre páginas. Pesadelo de auditoria e risco jurídico de fogo lento em setores regulados.
Fallback condicional
Você vai ver gente tentando expressar condicionais com a sintaxe de enumeração do motor:
{%HasCrypto%|%HasFiat%||<p>Payment methods coming soon.</p>}
A esperança é "mostrar o fallback quando as duas flags estiverem vazias". A realidade com uma enumeração {a|b|c|d} comum: o motor escolhe um dos quatro ramos ao acaso, com probabilidade igual. A saída é não determinística e inclui "1" como variante possível na página.
Ramos de enumeração são sorteio uniforme — eles nunca olham para variáveis. Para escolha guiada por valor, use a passagem prévia condicional:
{?!HasCrypto?{?!HasFiat?<p>Payment methods coming soon.</p>}}
Leia assim: se não tem cripto e não tem moeda comum, renderize o fallback. O condicional resolve antes de o sorteador de ramos rodar, então a saída fica inteiramente determinada pelas variáveis.
Lógica composta, que o spintax condicional não suporta, fica no montador — comparações, &&/||, valores calculados. Pré-calcule uma variável de guarda e depois libere com {?Guard?…}. O guia dedicado de spintax condicional cobre em detalhe as três formas, a tabela de truthy, o pipeline de duas passagens e os antipadrões.
Quando NÃO compor
Composição tem custo — três tipos de template para manter, um montador para ligar, uma camada de armazenamento para organizar. O pipeline compensa quando:
- você tem cinco ou mais itens parecidos, com a mesma estrutura;
- existem nuances editoriais por id ou exigências de ordenação;
- várias páginas reaproveitam o mesmo conjunto de itens;
- quem escreve precisa alterar itens de forma independente.
Pule a composição quando:
- a página tem de um a três itens no total;
- os itens não se repetem entre páginas;
- nada na estrutura vai mudar no próximo ano;
- ninguém além de você vai editar.
Para uma página "sobre" pontual ou um artigo solto, um template autocontido é mais rápido, mais limpo e mais fácil de depurar.
Erros comuns
| Não faça | Por quê | Faça |
|---|---|---|
| Compor uma página pequena (≤3 itens, sem variação editorial) | O custo do pipeline é maior que a economia. | Mantenha um template autocontido. |
| Codificar condicionais em enumerações de spintax | O motor sorteia, não olha valores; a saída é não determinística. | Use {?VAR?then|else} para checagens de uma variável; calcule lógica composta no montador e libere com {?Guard?…}. |
| Colocar nuances de um id inline no orquestrador ou na seção | Perde o benefício de "edite uma vez, propague em todo lugar". | Mantenha as nuances no template _item daquele id. |
| Misturar preocupações de item e de seção num template só | Refatorar vira sofrimento conforme a página cresce. | Três níveis limpos: item, seção, orquestrador. |
| Fixar a ordenação no orquestrador | Mudar a ordem exige editar páginas por todo o catálogo. | Ordene no montador, por um único campo de ordenação em cada item. |
| Esquecer de curto-circuitar seções vazias | Um <h3> vazio, sem <ul> embaixo, vai para produção. | Retorne "" do montador quando a lista de itens estiver vazia. |
Confiar em %XxxItems% não resolvidos na página renderizada | Variável ausente significa placeholder sobrevivendo literalmente. | Uma passagem de QA que sinalize qualquer %…% sobrando no HTML de produção. |
Checklist de composição
- Cada grupo de itens repetidos tem o seu template por id.
- Cada seção tem um único template
_sectionque referencia placeholders de item. - O orquestrador referencia apenas variáveis de seção, nunca variáveis de item.
- A ordenação vem do dado, não do conteúdo do template.
- Condicionais de uma variável usam
{?VAR?…}; lógica composta fica no montador. Nunca ramos de enumeração. - Seções vazias produzem
"", não marcação solta. - As nuances editoriais por id não estão duplicadas na seção nem no orquestrador.
- Cinco renders de amostra leem bem nos casos "todas as categorias vazias", "só cripto", "só moeda comum", "todas as categorias presentes" e "um único item descontinuado".
- Nenhum
%…%,{…}ou[…]sobrando em nenhum render.
Por ora, é o fim da série. Você tem a mentalidade, as variáveis, as permutações, a gramática e agora a composição. Volte à mentalidade quando começar o próximo artigo — o fluxo fica mais rápido a cada vez.