#Snippets
Un snippet est un fragment de balisage rangé dans snippets/ et appelé par
{% render %}. C’est le bouton du thème, sa carte d’image, son en-tête de
section : tout ce qui se répète dans plusieurs sections et qu’on veut corriger
à un seul endroit.
La règle qui gouverne tout le dossier tient en une phrase : un snippet ne voit que ce qu’on lui passe.
#Appeler un snippet
<div class="hero-actions">
{%- render 'bouton', label: section.settings.cta_label, href: section.settings.cta_href -%}
{%- render 'bouton', label: section.settings.cta2_label, href: section.settings.cta2_href, style: 'secondaire' -%}
</div>{%- if label != blank -%}
<a class="bouton{% if style == 'secondaire' %} bouton-secondaire{% endif %}"
href="{{ href | default: '/contact' }}">{{ label }}</a>
{%- endif -%}Les arguments nommés deviennent des variables de premier niveau dans le
snippet : label, href, style. Pas de préfixe, pas d’objet englobant.
Ce snippet ne rend rien quand label est vide. C’est ce qui permet de
poser deux boutons dans une section et de n’en garder qu’un depuis les
réglages, sans laisser un rectangle vide à la place du second. Le motif se
généralise : un snippet qui se tait quand ses données manquent est plus facile
à appeler qu’un snippet qu’il faut envelopper d’un {% if %}.
#Le piège : {% render %} isole le contexte
Dans un snippet appelé par {% render %}, rien du contexte de la page
n’est visible : ni site, ni page, ni settings, ni menu, ni
articles, ni annee, ni section. Seuls existent les arguments passés à
l’appel.
Une seule exception, et ce n’est pas du contenu : theme_base traverse. Elle
est posée en variable globale du moteur, précisément pour que asset_url
fonctionne partout — y compris dans un snippet, où elle rendrait sinon
/fond.svg, une adresse à la racine du domaine qui répond 404 sans rien
signaler. Tout le reste est bien isolé.
C’est le comportement de Liquid, chez Shopify comme ici, et c’est délibéré : un fragment qui ne dépend que de ses arguments est un fragment qu’on peut appeler de partout sans se demander ce qu’il va lire. Mais la première fois, ça surprend — et le symptôme est du vide, jamais une erreur.
{%- comment -%} snippets/carte.liquid {%- endcomment -%}
<article class="carte">
<h3>{{ section.settings.titre }}</h3>
<img src="{{ 'fond.svg' | asset_url }}" alt="">
<p>{{ site.nom }}</p>
</article>Deux des trois lignes rendent du vide. Le titre est vide, le nom du site
aussi : section et site ne traversent pas. L’image, elle, est juste —
asset_url est le seul à s’en sortir, parce que theme_base est global.
C’est ce qui rend le piège coûteux : la page s’affiche, une partie du snippet marche, et rien ne dit que le reste est vide.
{%- comment -%} snippets/carte.liquid — appelé avec titre:, fond: {%- endcomment -%}
<article class="carte">
<h3>{{ titre }}</h3>
{%- if fond != blank -%}<img src="{{ fond }}" alt="">{%- endif -%}
</article>{%- assign fond = 'fond.svg' | asset_url -%}
{%- render 'carte', titre: section.settings.titre, fond: fond -%}L’{% assign %} séparé n’est pas une coquetterie. Un filtre écrit dans
un argument de {% render %} n’est pas appliqué du tout.
{% render 'carte', fond: 'fond.svg' | asset_url %} ne passe pas l’adresse
construite : selon ce qui suit dans la ligne, le snippet reçoit la chaîne brute
fond.svg, ou rien du tout. Ça s’analyse, la page rend, et l’image est fausse
ou absente. Applique le filtre avant, dans une variable.
Si un snippet a besoin de beaucoup de contexte, c’est en général qu’il devrait être une section. Un snippet qui reçoit huit arguments est plus difficile à appeler que le balisage qu’il remplace.
#{% include %}, et pourquoi ne pas s’en servir
{% include %} existe et fait l’inverse : il partage le contexte de
l’appelant. site, settings, section et theme_base y sont visibles, et
les variables que le fichier inclus affecte remontent dans l’appelant.
C’est précisément le problème. Un {% assign i = 0 %} dans un fichier inclus
écrase le i de la boucle qui l’appelle, et le défaut apparaît trois sections
plus loin. Liquid l’a déprécié pour cette raison, et aucun des quatre thèmes
d’origine ne l’utilise.
{% render %} | {% include %} | |
|---|---|---|
| Contexte de la page | invisible | visible |
| Variables affectées | locales | fuient vers l’appelant |
| Arguments nommés | oui | oui |
| À utiliser | toujours | jamais |
#La résolution des chemins
Le nom passé à {% render %} est normalisé en trois gestes, dans cet ordre :
Le préfixe ./ ou / est retiré
'./bouton'et'/bouton'deviennent'bouton'.L’extension .liquid est ajoutée si elle manque
'bouton'devient'bouton.liquid'.Le dossier snippets/ est ajouté s’il n’y a aucune barre oblique
'bouton.liquid'devient'snippets/bouton.liquid'.
Toutes ces formes désignent donc le même fichier :
{%- render 'bouton' -%}
{%- render 'bouton.liquid' -%}
{%- render './bouton' -%}
{%- render 'snippets/bouton' -%}
{%- render 'snippets/bouton.liquid' -%}La première est celle qu’écrivent les quatre thèmes. Les autres fonctionnent, mais un chemin explicite dans un thème donne à croire que le dossier est variable — il ne l’est pas.
Le troisième geste n’a lieu que s’il n’y a aucune barre oblique. Un nom
qui en contient une est pris tel quel : {% render 'sections/hero' %} cherche
donc sections/hero.liquid — et fait échouer la section, parce que le fichier
contient un {% schema %} qui n’est retiré que pour les sections rendues
comme telles. L’erreur est tag "schema" not found. Ne rends jamais une
section avec render : utilise {% section %} dans la coquille, ou le
template.
Un snippet introuvable fait échouer la section entière, qui est alors remplacée par un commentaire HTML :
<!-- section hero : introuvable: snippets/boutn.liquid -->#Le disque n’existe pas
{% render %} ne voit qu’une carte en mémoire : celle des fichiers du
thème, chargée avant le rendu. LiquidJS lirait normalement les fichiers avec
fs ; ici le système de fichiers qu’on lui donne est un objet JavaScript, et
il n’y a rien derrière.
Conséquence directe : un thème ne peut atteindre ni /etc/passwd, ni les
variables d’environnement, ni le thème d’un autre client. Ce n’est pas un
filtre sur les chemins qu’on pourrait contourner par un .. bien placé — il
n’y a rien à atteindre. Voir Le bac à sable.
#Les trois snippets d’origo, et ce qu’ils enseignent
#bouton.liquid — un composant qui se tait
Vu plus haut. La leçon : {%- if label != blank -%} autour de tout le
balisage.
#image.liquid — les garde-fous qu’on oublie
{%- if src != blank -%}
<img
src="{{ src }}"
alt="{{ alt | default: '' | escape }}"
loading="{{ loading | default: 'lazy' }}"
decoding="async"
{% if ratio %}style="aspect-ratio:{{ ratio }};object-fit:cover"{% endif %}>
{%- endif -%}{%- render 'image', src: bloc.settings.image, alt: bloc.settings.legende, ratio: '4/3' -%}Trois décisions y sont prises une fois pour toutes :
loading="lazy"par défaut, surchargeable pour la première image de la page. Douze photos chargées d’un coup coûtent deux secondes sur la note de vitesse, et ces deux secondes se voient dans le classement ;altéchappé, parce qu’il vient du client et qu’un guillemet dedans casserait l’attribut ;- le ratio en CSS, pas en attributs
width/height: les images viennent de la bibliothèque du client, on n’en connaît pas les dimensions.
#section-tete.liquid — un fragment qu’on n’utilise pas partout
<div class="tete{% if centre %} tete-centre{% endif %}">
{%- if surtitre != blank -%}<span class="surtitre">{{ surtitre }}</span>{%- endif -%}
{%- if titre != blank -%}<h2 class="titre titre-section">{{ titre }}</h2>{%- endif -%}
{%- if texte != blank -%}<p class="chapeau">{{ texte }}</p>{%- endif -%}
</div>Ce snippet existe, et les sections livrées ne l’utilisent pas : elles écrivent leur en-tête en clair. Le commentaire du fichier le dit — les sections livrées restent lisibles à l’ouverture, le snippet sert à celles qu’on ajoute soi-même.
C’est un arbitrage qui mérite d’être conscient. Factoriser rend le thème plus court et plus cohérent ; ne pas factoriser rend chaque section lisible sans ouvrir un second fichier. Sur un thème qu’un client va relire pour changer trois mots, le second l’emporte souvent.
#Documenter un snippet dans le snippet
Les trois snippets d’origo commencent tous par un {%- comment -%} qui
donne les arguments et un exemple d’appel. C’est la seule documentation qui ne
se périme pas, parce qu’elle est à un écran du code.
{%- comment -%}
Le bouton du thème, en un seul endroit.
{%- render 'bouton', label: 'Nous écrire', href: '/contact' -%}
{%- render 'bouton', label: 'En savoir plus', href: '/a-propos', style: 'secondaire' -%}
Rien ne s'affiche si le libellé est vide : c'est ce qui permet de poser
deux boutons dans une section et de n'en garder qu'un depuis l'éditeur.
{%- endcomment -%}Un {%- comment -%} ne coûte rien au rendu — il est retiré à l’analyse, pas
à l’exécution — et pas un octet au HTML servi.

