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:

  1. rimozione dei commenti;
  2. estrazione delle direttive #set / #def;
  3. fusione delle variabili;
  4. espansione dei riferimenti %var%;
  5. risoluzione delle enumerazioni {a|b|c};
  6. risoluzione delle permutazioni [a|b|c];
  7. 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.

SchemaEsempio
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 (templates con 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 farePerché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 spintaxIl 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 sezioneSi 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 sezioneRifattorizzare diventa doloroso man mano che la pagina cresce.Tre livelli netti: elemento, sezione, orchestratore.
Fissare l’ordinamento nell’orchestratoreOgni 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 vuoteUn <h3> vuoto senza <ul> sotto finisce in produzione.Fate restituire "" all’assemblatore quando l’elenco è vuoto.
Fidarsi di residui %XxxItems% non risolti nella pagina resaUna 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 _section che 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.


Continua la serie