Composition de templates : des variables qui contiennent du HTML déjà rendu
Parfois un template devient trop gros pour être vivable. Des centaines d’éléments <li> sur une page de moyens de paiement, des dizaines de notes éditoriales en ligne, des changements d’ordre qui doivent se répercuter sur chaque variante — à un moment, un unique template géant et imbriqué devient le goulot d’étranglement au lieu de l’aide. L’étape suivante consiste à le découper en pipeline de petits templates, reliés par des variables qui contiennent du HTML déjà rendu.
Le changement de regard
Jusqu’ici dans cette série, une variable était une valeur : un nom de marque, une année, une liste de fonctionnalités séparée par des virgules. De simples chaînes, substituées dans le template au moment du rendu.
Le changement de ce guide est petit et puissant : la valeur d’une variable peut être du HTML déjà résolu. Pas "Acme Co.", mais <h3>Crypto deposits</h3><ul><li>BTC — fastest…</li>…</ul>. Le résolveur s’en moque ; il substitue, c’est tout.
C’est ce qui débloque la composition. Vous construisez la page à partir d’un pipeline de petits sous-templates, chacun rendu en un bloc de HTML, puis assemblés par un orchestrateur de quelques lignes.
Sans composition
<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>
C’est un monolithe de 200 lignes. Ajouter une pièce revient à éditer au milieu d’une longue chaîne d’énumérations. Les changements d’ordre se font à la main. Les nuances éditoriales par devise se dispersent dans tout le fichier.
Avec composition
%CryptoSection%
%FiatSection%
%LimitsSection%
Trois lignes. Chaque variable contient déjà le HTML entièrement résolu de sa partie de page. Les valeurs viennent d’un pipeline qui s’exécute avant le rendu de l’orchestrateur.
Pourquoi cela fonctionne — le pipeline du moteur
La référence de syntaxe détaille l’ordre de résolution ; les lignes qui comptent pour la composition sont :
- retirer les commentaires ;
- extraire les directives
#set/#def; - fusionner les variables ;
- développer les références
%var%; - résoudre les énumérations
{a|b|c}; - résoudre les permutations
[a|b|c]; - post-traiter.
Les variables se développent avant la résolution des énumérations et des permutations. Quand cette étape s’exécute, %CryptoSection% a déjà été remplacée par le HTML calculé par l’assembleur. Aucune syntaxe spéciale — la substitution de variable est littéralement un remplacement de chaîne.
Vous pouvez même mélanger les couches : une permutation externe peut mélanger des sections pré-rendues.
[<sep="\n\n">%CryptoSection%|%FiatSection%|%LimitsSection%]
Chaque section est résolue d’abord, puis la permutation réordonne les blocs.
Le pipeline à trois niveaux
Le motif tient en trois couches, chacune une étape d’affinage :
Niveau 1 — Templates d’élément (par identifiant)
La plus petite unité réutilisable. Un template par donnée : par pièce, par moyen de paiement, par palier d’offre, par entrée de FAQ, par référence produit.
/# 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>
Niveau 2 — Templates de section
Ils habillent la liste d’une structure. Pour les éléments assemblés, utilisez une variable d’emplacement.
/# spintax.section.crypto #/
<h3>{Crypto deposits|Cryptocurrencies accepted}</h3>
<p>{Pick from|We support} the following coins:</p>
<ul>%CryptoItems%</ul>
%CryptoItems%, c’est « tous les éléments par identifiant, résolus et concaténés en une chaîne ». C’est l’assembleur qui la construit.
Niveau 3 — Orchestrateur
Le template au niveau de la page. Il ne référence que des variables de section pré-rendues.
/# spintax.payment_options #/
<h2>{Accepted payment methods|How to pay}</h2>
%CryptoSection%
%FiatSection%
%LimitsSection%
C’est tout l’orchestrateur. Les règles d’édition : changer la description d’une pièce ? On touche à un template d’élément. Ajouter une devise ? On dépose un template d’élément et on ajoute l’identifiant à la liste active. Réordonner ? Un champ de tri, pas une modification de template.
Pas à pas — une page de moyens de paiement
Un marchand accepte BTC, USDT et ETH côté crypto, et Visa, Mastercard et SEPA côté monnaie classique. Trois requêtes et une poignée de templates produisent la page entière.
Pseudo-code de l’assembleur qui s’exécute avant le rendu de l’orchestrateur :
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" : "",
};
}
Le rendu de l’orchestrateur reçoit ensuite ces variables aux côtés des variables de site et d’exécution habituelles, et fait une dernière passe.
Conventions de nommage
Ici la convention l’emporte sur la liberté, car l’assembleur retrouve les templates par leur identifiant.
| Motif | Exemple |
|---|---|
spintax.<entity>_item.<id> | spintax.crypto_item.btc |
spintax.<entity>_row.<id> | spintax.crypto_row.btc (ligne de tableau) |
spintax.section.<key> | spintax.section.crypto |
spintax.<page-name> | spintax.payment_options |
Les variables suivent la même forme :
%CryptoItems%,%FiatItems%,%LimitsRows%— blocs par identifiant, assemblés%CryptoSection%,%FiatSection%,%LimitsSection%— sections résolues%HasCrypto%,%HasFiat%— marqueurs ('1'ou'')
PascalCase pour les variables, snake_case pour les identifiants, ASCII pour les deux.
Le stockage est votre problème, pas celui de spintax
Le motif fonctionne pareil, où que vivent les sous-templates :
- table en base (
templatesavec identifiant + corps + langue) - fichier JSON :
{ "crypto_item.btc": "<li>…</li>", … } - système de fichiers :
templates/crypto_item/btc.txt - champ de CMS par locale
Le moteur n’a pas besoin de base de données. Il substitue du HTML résolu dans des références de variables, point. L’assembleur, c’est votre code, écrit dans le runtime qui pilote vos rendus. Une extension WordPress, un Cloudflare Worker, un script Node, une fonction Postgres — même motif.
Pourquoi pas #include ? Le moteur possède bien une directive d’inclusion, et pour un unique bloc partagé c’est le chemin le plus court. Ce pipeline ne repose pas dessus, pour trois raisons : un template inclus est un document à part — il voit les variables d’exécution mais jamais les #set/#def du parent, une forme déjà tirée ne peut donc pas lui être transmise ; il ne fonctionne que là où l’hôte a installé un résolveur, et deux des nôtres n’en ont volontairement pas (le playground et le serveur MCP), où la ligne reste simplement littérale ; et quand le résolveur ne connaît pas la référence, la ligne disparaît de la sortie sans un mot. Un assembleur garde la résolution, le cache et la gestion d’erreurs dans votre propre code, où vous voyez les trois. Comportement complet : la section sur les inclusions de la référence de syntaxe.
Nuances éditoriales par identifiant
C’est l’atout décisif. Les nuances éditoriales vivent avec la donnée, pas sur chaque page.
/# 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>
Chaque template par identifiant capte la nuance une fois. Trois pages, dix pages, mille pages — toutes héritent des bons avertissements. Passez QIWI en « déprécié » en modifiant un template ; tous les rendus basculent en même temps.
Sans composition, ces nuances seraient des chaînes en ligne dupliquées d’une page à l’autre. Cauchemar d’audit, et risque juridique à combustion lente dans les secteurs régulés.
Repli conditionnel
On voit des auteurs tenter d’exprimer des conditions avec la syntaxe d’énumération du moteur :
{%HasCrypto%|%HasFiat%||<p>Payment methods coming soon.</p>}
L’espoir : « afficher le repli quand les deux drapeaux sont vides ». La réalité avec une énumération {a|b|c|d} ordinaire : le moteur choisit l’une des quatre branches au hasard, à probabilité égale. La sortie n’est pas déterministe et inclut "1" parmi les variantes possibles sur la page.
Les branches d’énumération sont un tirage uniforme — elles ne regardent jamais une variable. Pour un choix piloté par la valeur, utilisez la passe conditionnelle préalable :
{?!HasCrypto?{?!HasFiat?<p>Payment methods coming soon.</p>}}
À lire ainsi : si ni crypto ni monnaie classique, rends le repli. La condition se résout avant le tirage aléatoire : la sortie est donc entièrement déterminée par les variables.
La logique composée, que le spintax conditionnel ne gère pas, reste dans l’assembleur — comparaisons, &&/||, valeurs calculées. Pré-calculez une variable garde-fou, puis ouvrez avec {?Guard?…}. Le guide dédié au spintax conditionnel couvre en détail les trois formes, la table truthy, le pipeline à deux passes et les anti-patterns.
Quand NE PAS composer
La composition a un coût — trois sortes de templates à maintenir, un assembleur à câbler, une couche de stockage à organiser. Le pipeline devient rentable quand :
- vous avez cinq éléments semblables ou plus, de structure commune ;
- il existe des nuances éditoriales par identifiant ou des exigences de tri ;
- plusieurs pages réutilisent le même jeu d’éléments ;
- la rédaction doit modifier les éléments indépendamment.
Passez votre chemin quand :
- la page compte un à trois éléments en tout ;
- les éléments ne se répètent pas d’une page à l’autre ;
- rien ne changera dans la structure l’année prochaine ;
- personne d’autre que vous ne l’éditera.
Pour une page « à propos » ponctuelle ou un article isolé, un template autonome est plus rapide, plus propre et plus facile à déboguer.
Erreurs fréquentes
| À éviter | Pourquoi | À faire |
|---|---|---|
| Composer une petite page (≤3 éléments, aucune variance éditoriale) | Le coût du pipeline dépasse le gain. | Garder un template autonome. |
| Encoder des conditions dans les énumérations spintax | Le moteur tire au hasard, sans regarder les valeurs ; la sortie n’est pas déterministe. | Utiliser {?VAR?then|else} pour un test sur une variable ; calculer la logique composée dans l’assembleur et l’ouvrir par {?Guard?…}. |
| Mettre les nuances d’un identifiant en ligne dans l’orchestrateur ou la section | On perd le bénéfice « modifier une fois, propager partout ». | Garder les nuances dans le template _item de cet identifiant. |
| Mélanger les préoccupations d’élément et de section dans un même template | La refonte devient pénible à mesure que la page grossit. | Trois niveaux nets : élément, section, orchestrateur. |
| Figer l’ordre de tri dans l’orchestrateur | Un changement d’ordre impose des modifications de page dans tout le catalogue. | Trier dans l’assembleur, sur un unique champ de tri par élément. |
| Oublier de court-circuiter les sections vides | Un <h3> vide, sans <ul> dessous, part en production. | Renvoyer "" depuis l’assembleur quand la liste d’éléments est vide. |
Faire confiance aux %XxxItems% non résolus dans la page rendue | Une variable manquante veut dire que l’emplacement survit littéralement. | Une passe de recette qui signale tout %…% restant dans le HTML de production. |
Checklist de composition
- Chaque groupe d’éléments répétés a son propre template par identifiant.
- Chaque section a un unique template
_sectionqui référence des emplacements d’élément. - L’orchestrateur ne référence que des variables de section, jamais des variables d’élément.
- L’ordre de tri vient des données, pas du contenu du template.
- Les conditions sur une variable utilisent
{?VAR?…}; la logique composée reste dans l’assembleur. Jamais de branches d’énumération. - Les sections vides produisent
"", pas du balisage orphelin. - Les nuances éditoriales par identifiant ne sont dupliquées ni dans la section ni dans l’orchestrateur.
- Cinq rendus d’exemple se lisent proprement sur « toutes catégories vides », « crypto seulement », « monnaie classique seulement », « toutes catégories présentes » et « un seul élément déprécié ».
- Aucun
%…%,{…}ou[…]restant dans le moindre rendu.
La série s’arrête là pour l’instant. Vous avez l’état d’esprit, les variables, les permutations, la grammaire et maintenant la composition. Revenez à l’état d’esprit quand vous commencerez l’article suivant — le flux devient plus rapide à chaque fois.