Composizione di template: variabili come blocchi di HTML già reso
A volte un template diventa troppo grande per conviverci. Centinaia di voci <li> in una pagina di metodi di pagamento, decine di note editoriali sparse nel testo, cambi di ordinamento che devono propagarsi in ogni variante — a un certo punto un unico template gigantesco e pieno di annidamenti diventa il collo di bottiglia invece dell’aiuto. Il passo successivo è spezzarlo in una pipeline di template piccoli, uniti da variabili che contengono HTML già reso.
Il cambio di prospettiva
Fin qui, in questa serie, una variabile è stata un valore: un nome di marca, un anno, un elenco di funzionalità separate da virgola. Stringhe semplici sostituite nel template al momento del render.
Il cambio di questa guida è piccolo e potente: il valore di una variabile può essere HTML già risolto. Non "Acme Co." ma <h3>Crypto deposits</h3><ul><li>BTC — fastest…</li>…</ul>. Al risolutore non importa: sostituisce e basta.
È questo che sblocca la composizione. Costruite la pagina da una pipeline di sotto-template piccoli, ognuno reso in un blocco di HTML, poi assemblati da un orchestratore lungo poche righe.
Senza composizione
<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>
Questo è un monolite da 200 righe. Aggiungere una moneta vuol dire mettere le mani dentro una lunga catena di enumerazioni. I cambi di ordinamento sono lavoro a mano. Le sfumature editoriali di ogni valuta si disperdono per tutto il file.
Con la composizione
%CryptoSection%
%FiatSection%
%LimitsSection%
Tre righe. Ogni variabile contiene già l’HTML completamente risolto della sua parte di pagina. I valori arrivano da una pipeline che gira prima che l’orchestratore venga reso.
Perché funziona — la pipeline del motore
Il riferimento di sintassi stabilisce l’ordine di risoluzione; le righe che contano per la composizione sono queste:
- rimozione dei commenti;
- estrazione delle direttive
#set/#def; - fusione delle variabili;
- espansione dei riferimenti
%var%; - risoluzione delle enumerazioni
{a|b|c}; - risoluzione delle permutazioni
[a|b|c]; - post-elaborazione.
Le variabili si espandono prima della risoluzione di enumerazioni e permutazioni. Quando enum e perm girano, %CryptoSection% è già stata sostituita con l’HTML che l’assemblatore ha calcolato. Nessuna sintassi speciale: la sostituzione di variabile è letteralmente una sostituzione di stringa.
Potete perfino mescolare i livelli: una permutazione esterna può rimescolare sezioni già rese.
[<sep="\n\n">%CryptoSection%|%FiatSection%|%LimitsSection%]
Ogni sezione viene risolta per intero, poi la permutazione riordina i blocchi.
La pipeline a tre livelli
Lo schema vive in tre strati, ognuno una fase di rifinitura:
Livello 1 — Template di elemento (per id)
L’unità riusabile più piccola. Un template per ogni elemento di dati: per moneta, per metodo di pagamento, per fascia di piano, per voce di FAQ, per SKU di prodotto.
/# 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>
Livello 2 — Template di sezione
Avvolgono l’elenco con la struttura. Usate una variabile segnaposto per gli elementi già uniti.
/# spintax.section.crypto #/
<h3>{Crypto deposits|Cryptocurrencies accepted}</h3>
<p>{Pick from|We support} the following coins:</p>
<ul>%CryptoItems%</ul>
%CryptoItems% è «ogni elemento per id risolto e unito in una sola stringa». Lo costruisce l’assemblatore.
Livello 3 — Orchestratore
Il template a livello di pagina. Fa riferimento soltanto alle variabili di sezione già rese.
/# spintax.payment_options #/
<h2>{Accepted payment methods|How to pay}</h2>
%CryptoSection%
%FiatSection%
%LimitsSection%
Ecco l’orchestratore per intero. Regole di modifica: cambiare la descrizione di una moneta? Si tocca un template di elemento. Aggiungere una valuta? Si depone un nuovo template di elemento e si aggiunge l’id all’elenco attivo. Riordinare? Campo di ordinamento, non modifica ai template.
Passo per passo — una pagina di metodi di pagamento
Un esercente accetta BTC, USDT ed ETH per le cripto e Visa, Mastercard e SEPA per il denaro corrente. Tre interrogazioni e una manciata di template producono la pagina intera.
Pseudo-codice dell’assemblatore che gira prima del render dell’orchestratore:
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" : "",
};
}
Il render dell’orchestratore riceve poi queste variabili insieme alle normali variabili di sito e di runtime, e fa la passata finale.
Convenzioni di denominazione
Qui la convenzione batte la libertà, perché l’assemblatore trova i template per id.
| Schema | Esempio |
|---|---|
spintax.<entity>_item.<id> | spintax.crypto_item.btc |
spintax.<entity>_row.<id> | spintax.crypto_row.btc (riga di tabella) |
spintax.section.<key> | spintax.section.crypto |
spintax.<page-name> | spintax.payment_options |
Le variabili seguono la stessa forma:
%CryptoItems%,%FiatItems%,%LimitsRows%— blocchi per id già uniti%CryptoSection%,%FiatSection%,%LimitsSection%— sezioni risolte%HasCrypto%,%HasFiat%— marcatori ('1'oppure'')
PascalCase per le variabili, snake_case per gli id, solo ASCII per entrambi.
L’archiviazione è un problema vostro, non di spintax
Lo schema funziona allo stesso modo ovunque vivano i sotto-template:
- una tabella di database (
templatescon id + corpo + lingua) - un file JSON:
{ "crypto_item.btc": "<li>…</li>", … } - il filesystem:
templates/crypto_item/btc.txt - un campo del CMS per locale
Al motore non serve un database. Sostituisce HTML risolto nei riferimenti a variabile, punto. L’assemblatore è codice vostro, scritto nel runtime che guida i vostri render. Un plugin WordPress, un Worker di Cloudflare, uno script Node, una funzione Postgres — sempre lo stesso schema.
Perché non #include? La direttiva di inclusione nel motore c’è, e per un singolo blocco condiviso è la strada più corta. Questa pipeline non ci si appoggia, per tre motivi: un template incluso è un documento a sé — vede le variabili di runtime ma mai i #set/#def del genitore, quindi una forma già tirata non può essere passata dentro; funziona solo dove l’host ha installato un risolutore, e due dei nostri di proposito non ne hanno (il playground e il server MCP), dove la riga resta semplicemente letterale; e quando il risolutore non conosce il riferimento, la riga sparisce dall’output senza dire una parola. Un assemblatore tiene risoluzione, cache e gestione degli errori nel vostro codice, dove le vedete tutte e tre. Comportamento completo: la sezione sugli include del riferimento di sintassi.
Sfumature editoriali per id
È qui la funzionalità decisiva. Le sfumature editoriali vivono con i dati, non in ogni pagina.
/# 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>
Ogni template per id cattura la sfumatura una volta sola. Tre pagine, dieci pagine, mille pagine — ereditano tutte gli avvisi giusti. Spostate QIWI su «deprecato» modificando un template solo; ogni render si ribalta contemporaneamente.
Senza composizione, quelle sfumature sarebbero stringhe inline duplicate da una pagina all’altra. Un incubo per gli audit e un rischio legale a lenta combustione nei settori regolamentati.
Il ripiego condizionale
Vedrete autori che provano a esprimere condizioni con la sintassi di enumerazione del motore:
{%HasCrypto%|%HasFiat%||<p>Payment methods coming soon.</p>}
La speranza è «mostra il ripiego quando entrambi i flag sono vuoti». La realtà con una semplice enumerazione {a|b|c|d}: il motore sceglie uno dei quattro rami a caso, con probabilità uguale. L’output non è deterministico e comprende "1" fra le varianti possibili sulla pagina.
I rami di enumerazione sono una scelta casuale uniforme: non guardano mai le variabili. Per una scelta guidata dal valore usate la passata preliminare condizionale:
{?!HasCrypto?{?!HasFiat?<p>Payment methods coming soon.</p>}}
Leggetela così: se non c’è cripto e non c’è denaro corrente, rendi il ripiego. La condizione si risolve prima che giri il selettore casuale dei rami, quindi l’output è interamente determinato dalle variabili.
La logica composta che lo spintax condizionale non supporta resta nell’assemblatore — confronti, &&/||, valori calcolati. Pre-calcolate una variabile guardia, poi apritele il passaggio con {?Guard?…}. La guida dedicata allo spintax condizionale tratta in dettaglio le tre forme, la tabella dei truthy, la pipeline a due passate e gli anti-pattern.
Quando NON comporre
La composizione ha un costo: tre tipi di template da mantenere, un assemblatore da cablare, uno strato di archiviazione da organizzare. La pipeline ripaga quando:
- avete cinque o più elementi simili che condividono una struttura;
- esistono sfumature editoriali per id o requisiti di ordinamento;
- più pagine riusano lo stesso insieme di elementi;
- la redazione deve poter modificare gli elementi in modo indipendente.
Saltate la composizione quando:
- la pagina ha da uno a tre elementi in tutto;
- gli elementi non si ripetono fra le pagine;
- nella struttura non cambia nulla per il prossimo anno;
- non ci metterà mano nessuno tranne voi.
Per una pagina «chi siamo» a sé stante o per un singolo articolo, un template autosufficiente è più rapido, più pulito e più facile da diagnosticare.
Errori frequenti
| Da non fare | Perché | Fate invece |
|---|---|---|
| Comporre una pagina piccola (≤3 elementi, nessuna variazione editoriale) | Il costo della pipeline supera il risparmio. | Tenete un unico template autosufficiente. |
| Codificare condizioni nelle enumerazioni spintax | Il motore sceglie a caso, non in base ai valori; l’output non è deterministico. | Usate {?VAR?then|else} per i controlli su una sola variabile; calcolate la logica composta nell’assemblatore e apritele il passaggio con {?Guard?…}. |
| Mettere inline le sfumature per id nell’orchestratore o nella sezione | Si perde il vantaggio del «modifica una volta, si propaga ovunque». | Tenete le sfumature nel template _item del singolo id. |
| Mescolare in un template le questioni di elemento e quelle di sezione | Rifattorizzare diventa doloroso man mano che la pagina cresce. | Tre livelli netti: elemento, sezione, orchestratore. |
| Fissare l’ordinamento nell’orchestratore | Ogni cambio di ordine richiede modifiche alle pagine di tutto il catalogo. | Ordinate nell’assemblatore da un unico campo di ordinamento su ogni elemento. |
| Dimenticare di cortocircuitare le sezioni vuote | Un <h3> vuoto senza <ul> sotto finisce in produzione. | Fate restituire "" all’assemblatore quando l’elenco è vuoto. |
Fidarsi di residui %XxxItems% non risolti nella pagina resa | Una variabile mancante lascia il segnaposto alla lettera. | Una passata di QA che segnali ogni %…% rimasto nell’HTML di produzione. |
Checklist della composizione
- Ogni gruppo di elementi ripetuti ha il proprio template per id.
- Ogni sezione ha un solo template
_sectionche fa riferimento ai segnaposto degli elementi. - L’orchestratore fa riferimento solo a variabili di sezione, mai a variabili di elemento.
- L’ordinamento viene dai dati, non dal contenuto dei template.
- Le condizioni su una sola variabile usano
{?VAR?…}; la logica composta resta nell’assemblatore. Mai i rami di enumerazione. - Le sezioni vuote producono
"", non markup vagante. - Le sfumature editoriali per id non sono duplicate nella sezione né nell’orchestratore.
- Cinque render di prova si leggono puliti su «tutte le categorie vuote», «solo cripto», «solo denaro corrente», «tutte le categorie presenti» e «un solo elemento obsoleto».
- Nessun
%…%,{…}o[…]rimasto in alcun render.
Per ora la serie finisce qui. Avete il modo di pensare, le variabili, le permutazioni, la grammatica e adesso la composizione. Tornate al modo di pensare quando iniziate il prossimo articolo — il flusso di lavoro diventa più veloce ogni volta.