Aller au contenu

#Les absents

product, cart, collection, customer, checkout : ces objets n’existent pas dans un thème Webcosa. Cette page dit pourquoi, ce qui se passe si tu les écris quand même, et par quoi les remplacer. Elle est écrite pour quelqu’un qui arrive de Shopify — c’est là que le format est le plus familier, donc c’est là que l’écart surprend le plus.

#La règle, en une phrase

Webcosa fait des sites vitrines, pas des boutiques. Le format des thèmes est celui de Shopify parce qu’il est bon, documenté depuis dix ans, et entouré d’outils qui existent déjà. Le modèle de données, lui, est celui d’un site professionnel : un site, des pages, un menu, des articles, des réglages. Il n’y a pas de catalogue derrière, donc il n’y a pas d’objet catalogue devant.

#Pourquoi ne pas les exposer vides

C’est la question qui mérite une vraie réponse, parce que la réponse paresseuse existe : on pourrait déclarer product comme un objet vide, cart comme un panier à zéro article, et laisser les thèmes Shopify s’installer sans erreur.

Ce serait pire, pour trois raisons qui se cumulent.

Un thème écrit contre une API qui ne rend jamais rien paraît fonctionner. La page rend, le HTML sort, aucune erreur n’est levée. Le vide ne se voit qu’à l’écran, et souvent pas tout de suite : une grille de produits vide ressemble à une grille pas encore remplie. On découvre le problème en production, chez le client, une semaine après la mise en ligne.

Un objet exposé devient un contrat. La liste des objets de src/lib/liquid/contexte.ts porte cette phrase en tête : tout ce qui y figure devra continuer d’exister, sous ce nom et cette forme, tant que des thèmes s’en servent. Exposer product par confort aujourd’hui, c’est s’interdire de choisir librement à quoi ressemblera un produit le jour où Webcosa en aura.

Une absence franche se diagnostique en une minute. Un développeur qui écrit {{ product.title }}, voit du vide, ouvre cette page et lit « il n’y en a pas » a perdu une minute. Le même développeur devant un product vide mais présent cherche du côté de ses données, de sa requête, de son cache — et perd une après-midi.

Note

C’est la même logique que le refus des .js dans assets/ ou que la liste fermée des dossiers : on préfère un refus net à une permissivité qui se paie plus tard. Voir le bac à sable.

#Ce qui se passe si tu les écris

Rien de spectaculaire, et c’est précisément le problème. Le moteur tourne avec strictVariables désactivé : une variable absente rend une chaîne vide.

sections/produits.liquidliquid
<h2>{{ product.title }}</h2>
<p>{{ product.price | money }}</p>
{% for p in collection.products %}
  <article>{{ p.title }}</article>
{% endfor %}
Ce que rend cette sectionhtml
<h2></h2>
<p></p>

Un titre vide, un prix vide, une boucle qui ne tourne pas. Aucune erreur, aucun commentaire HTML, rien dans les journaux.

Attention

Un tag inconnu, lui, fait échouer toute la section. {% form %}, {% paginate %} et {% style %} viennent de Shopify et ne sont pas enregistrés : la section entière est remplacée par un commentaire. Voir la page des tags.

Deux symptômes, deux causes : un bloc vide signale un objet absent ; un bloc disparu signale un tag absent.

#L’inventaire

#Le commerce

Ce que tu cherchesÉtatCe qu’il faut faire
product, variantabsentune section avec des blocs, un par produit ou prestation
collectionabsentune section par famille, ou des blocs typés
cart, line_itemabsentlien vers la boutique existante du client
checkout, orderabsenthors périmètre : Webcosa ne prend pas de paiement
customerabsentil n’y a pas de compte visiteur sur un site vitrine
shopabsentc’est site, avec nom et adresse
money, money_with_currencyfiltre inexistantécris le prix dans un réglage text
img_url, image_urlfiltre inexistantl’adresse d’image est déjà complète

#Le contenu

Ce que tu cherchesÉtatCe qu’il faut faire
blog, articlepartiellementarticles existe — un tableau plat, en lecture seule
linklistsabsentmenu, et il n’y en a qu’un
pagesabsentil n’y a pas de liste des pages du site
page_title, page_descriptionabsentpage.titre ; les métadonnées sont posées par la plateforme
templateabsentpage.accueil dit si l’on est sur l’accueil
request, canonical_urlabsentpage.adresse et site.adresse
routesabsentécris les chemins en clair : /contact, /blog
content_for_headerabsentla plateforme pose elle-même ses balises
content_for_layoutprésentdans layout/theme.liquid uniquement

#La traduction et les fichiers

Ce que tu cherchesÉtatCe qu’il faut faire
locales/*.json, filtre tabsentécris le texte en français, ou passe-le en réglage
{% javascript %}tag inexistantil n’y a pas de JavaScript de thème
{% stylesheet %}tag inexistantassets/theme.css
{% style %}tag inexistantune balise style HTML dans la coquille
{% form %}tag inexistantune balise form HTML qui poste vers /api/contacts
{% paginate %}tag inexistantarticles est déjà plafonné à douze entrées
metafieldsabsentun réglage de section, ou un bloc
sections/*.json, {% sections %}absentla coquille appelle {% section %} une par une

#Ce qu’il faut faire à la place

#Un catalogue de prestations : des blocs

Le besoin réel derrière product sur un site vitrine, c’est presque toujours « montrer six prestations avec un titre, une description et un prix ». Ça ne demande pas un catalogue : ça demande une section à blocs, que le client remplit depuis l’éditeur.

sections/prestations.liquid — Velaliquid
<div class="cartes">
  {%- for bloc in section.blocks -%}
    <article class="carte">
      <h3>{{ bloc.settings.nom }}</h3>
      <p>{{ bloc.settings.description }}</p>
      {%- if bloc.settings.prix != blank -%}
        <span class="carte-prix">{{ bloc.settings.prix }}</span>
      {%- endif -%}
    </article>
  {%- endfor -%}
</div>

Le prix est un réglage text, pas un nombre formaté par un filtre money : le client écrit « à partir de 45 € » ou « sur devis », ce qu’aucun formateur de monnaie ne saurait produire. Sur un site vitrine, c’est presque toujours la bonne réponse.

Le détail de la déclaration est sur la page des blocs.

#Un formulaire : du HTML ordinaire

{% form %} n’existe pas, et il n’est pas nécessaire. Le formulaire poste vers /api/contacts, la route enregistre la demande, l’envoie par e-mail, puis redirige vers la page avec ?demande=merci — que le thème lit dans page.demande.

sections/contact.liquid — Origoliquid
{%- if page.demande == 'merci' -%}
  <p class="message message-ok" role="status">
    Merci, votre message est bien arrivé. Nous vous répondons vite.
  </p>
{%- endif -%}

<form class="formulaire" method="post" action="/api/contacts">
  <label class="champ">
    <span>Votre nom</span>
    <input type="text" name="nom" autocomplete="name" required>
  </label>
  <input type="text" name="piege" tabindex="-1" autocomplete="off" aria-hidden="true" class="piege">
  <input type="hidden" name="provenance" value="{{ page.titre }}">
  <button type="submit" class="bouton">{{ section.settings.cta_label }}</button>
</form>

Deux détails de cet extrait valent d’être repris tels quels. Le site n’est pas déduit d’un champ caché mais du nom de domaine : un champ se modifie dans l’inspecteur, un nom d’hôte non. Et le champ piege, invisible pour un humain, est rempli par les automates : une demande qui le porte est acceptée en apparence et jetée en silence — informé de son échec, un robot réessaie.

#Une vraie boutique : un lien

Si le client vend en ligne, il a déjà une boutique quelque part — Shopify, WooCommerce, Etsy. La bonne réponse est un lien depuis le site vitrine, pas une tentative de refaire un tunnel d’achat dans un thème qui n’a ni panier ni paiement.

Un bouton vers la boutiqueliquid
{%- render 'bouton', label: section.settings.cta_label, href: section.settings.cta_href -%}

Le libellé et l’adresse sont deux réglages text de la section : le client change de plateforme, il change une adresse dans l’éditeur, personne ne rouvre le thème.

#Des styles : la feuille et les variables

{% stylesheet %} et {% style %} n’existent pas. Tout le CSS d’un thème vit dans assets/theme.css, et ce qui doit varier passe par des variables CSS posées dans une balise style de la coquille.

layout/theme.liquid — Piazzaliquid
{{ 'theme.css' | asset_url | stylesheet_tag }}

<style>
  :root {
    --encre: {{ settings.encre }};
    --accent: {{ settings.accent }};
    --accent-clair: {{ settings.accent | teinte: 82 }};
    --largeur: {{ settings.largeur }}px;
  }
</style>

C’est le schéma des quatre thèmes livrés : les réglages deviennent des variables CSS, ici et nulle part ailleurs. Aucune section ne connaît une couleur, elles héritent toutes de :root. Une section qui écrirait style="color: {{ … }}" en ligne casserait ce contrat et rendrait le thème impossible à retoucher d’un bloc.

#Et demain ?

La liste s’allongera si un besoin réel le demande, jamais par symétrie avec Shopify. Ajouter un objet est facile et ne casse rien ; en retirer un casse tous les thèmes qui s’en servaient. C’est cette asymétrie qui dicte la prudence, pas une réticence de principe.

Ce qui ne bougera pas : les objets qui existent aujourd’hui garderont leur nom et leur forme. Un thème écrit maintenant continuera de rendre.