Aller au contenu

#Depuis un thème Shopify

Le format est le même : mêmes dossiers, même langage, mêmes schémas, mêmes templates JSON. Ce qui n’est pas le même, c’est ce qu’un thème peut lire — Webcosa fait des sites vitrines, pas des boutiques. Ce guide dit exactement ce qui se transpose, ce qui se réécrit, ce qui disparaît, et dans quel ordre s’y prendre.

Attention

Ce n’est pas un portage de dix minutes, et ce n’est pas non plus une réécriture. Sur un thème Shopify de site vitrine — accueil, à propos, prestations, contact — comptez une demi-journée : la structure passe telle quelle, les sections marchandes s’effacent, et le travail se concentre sur les filtres d’image et de traduction, qui sont partout. Sur un thème de boutique complet, ne portez pas : reprenez la feuille de style et recomposez. La moitié du fichier parle de produits qui n’existent pas ici.

#Le tableau, en un coup d’œil

Se transpose tel quelChange de formeN’existe pas
Les six dossiers layout/ sections/ snippets/ assets/ config/ templates/asset_url : nom seul, jamais de cheminproduct, collection, cart, customer, order
La syntaxe Liquid entière : if, for, case, assign, capture, renderhandleizehandleLes métachamps (metafields)
{% schema %} : name, settings, blocks, max_blocks, presetscolor_lighten / color_darkenteinte: ±nLes sections d’application et les blocs d’app
templates/*.json : sections, order, blocks, block_ordernewline_to_brlignes (échappe en plus){% form %}, {% paginate %}, {% style %}, {% javascript %}, {% stylesheet %}
settings_schema.json groupé, settings_data.json avec currentLa date localisée → date_frcontent_for_index, {% sections %}, {% content_for 'blocks' %}
layout/theme.liquid avec content_for_layoutL’archive : un .json, pas un .ziplocales/ et le filtre t de traduction
{% section 'entete' %} dans la coquilleLes images : par URL, pas par fichier de thème.js dans assets/, et {% javascript %} avec
La plupart des filtres standard (default, replace, split, truncate, date, map, where…)13 types de réglage, pas davantageroutes, shop, localization, all_products, search

Le reste de cette page reprend les trois colonnes, une par une.

#Ce qui se transpose tel quel

#L’arborescence

Copie les dossiers. Il n’y a rien à renommer.

Un thème Webcosa
layout/theme.liquid            .liquid
sections/<type>.liquid         .liquid
snippets/<nom>.liquid          .liquid
assets/theme.css               .css .svg
config/theme.json              .json
config/settings_schema.json    .json
config/settings_data.json      .json
templates/index.json           .json

Une seule règle, mais elle est stricte : un chemin de thème a exactement deux segments. Pas de sections/blocks/…, pas de templates/customers/…, pas de sous-dossier dans assets/. Tout ce qui est plus profond est refusé à l’entrée. Voir Arborescence.

#Le langage

C’est du Liquid, exécuté par LiquidJS — la même grammaire, portée en JavaScript. Les tags de contrôle, d’affectation et d’inclusion se comportent comme chez Shopify : if / elsif / unless, case / when, for avec break et continue, tablerow, cycle, assign, capture, increment, liquid, echo, raw, comment, et surtout render en portée isolée contre include en portée partagée. Le contrôle des espaces ({%- et -%}) est identique.

Voir Tags pour la liste exacte, et Filtres pour les filtres.

#Les schémas de section

Aucun changement d’écriture. Le bloc {% schema %} est du JSON pur posé à la fin du fichier, extrait par une expression régulière — exactement comme chez Shopify.

sections/avis.liquid — Forgeliquid
{% schema %}
{
  "name": "Avis clients",
  "settings": [
    { "type": "text", "id": "titre", "label": "Titre", "default": "Ce qu'ils en disent" }
  ],
  "blocks": [
    {
      "type": "avis",
      "name": "Avis",
      "settings": [
        { "type": "textarea", "id": "citation", "label": "Citation" },
        { "type": "text", "id": "auteur", "label": "Auteur" }
      ]
    }
  ],
  "max_blocks": 6
}
{% endschema %}
Note

Un type de réglage inconnu est ignoré, pas rejeté. Un "type": "video_url" hérité de Shopify n’empêche pas la section de rendre, et il ne bloque pas non plus la valeur : le réglage est lu normalement — sa valeur et son default remontent dans section.settings comme pour un type admis, puisque le rendu ne consulte que id et default. Seul le futur panneau de réglages l’ignorera. C’est délibéré : un thème écrit pour une version plus récente continue d’afficher sa page. La contrepartie, c’est qu’un type mal orthographié ne se signale jamais. Les treize types admis sont sur Les types de réglage.

#Les templates JSON

Même forme, mêmes clés : sections (chaque entrée porte un type, des settings, des blocks et un block_order) et order.

L’ordre de résolution est aussi celui de Shopify, avec un niveau de moins : templates/<nom>.json, puis templates/page.json, puis templates/index.json. Le nom du template suit le slug de la page — une page « tarifs » cherche templates/tarifs.json. Voir Les templates.

#La coquille

layout/theme.liquid, content_for_layout, et {% section 'entete' %} pour les sections posées hors template. Leurs valeurs vivent dans config/settings_data.json, sous current.sections — comme chez Shopify.

Ce qui n’a pas d’équivalent : content_for_header. Webcosa n’injecte rien dans le head ; ce qui doit y être, le thème l’écrit.

#Ce qui change de forme

Sept réécritures, et elles reviennent souvent. Passe-les en recherche-remplacer avant tout le reste.

asset_urlfiltre

Prend le nom seul, jamais le chemin. {{ 'theme.css' | asset_url }}, jamais {{ 'assets/theme.css' | asset_url }}.

L’adresse produite est /theme-assets/<id-du-thème>/theme.css : un seul segment de nom, et c’est la route qui remet le préfixe assets/. Un chemin complet fabrique une adresse à deux segments, qui répond 404 — et la page rend quand même, en HTML nu. C’est l’erreur la plus coûteuse du format, parce qu’elle ne lève rien.

handleizefiltre

S’appelle handle. Même effet : Mon Titre devient mon-titre, accents retirés, utilisable en ancre.

color_lighten / color_darkenfiltres

Un seul filtre, signé : {{ settings.accent | teinte: -25 }} assombrit de 25 %, teinte: 40 éclaircit de 40 %. Il n’accepte que du #rrggbb sur six chiffres — #fff, rgb() et les noms de couleur sortent inchangés.

newline_to_brfiltre

newline_to_br existe (c’est du LiquidJS standard), mais préfère lignes : il convertit les sauts de ligne et échappe le HTML. Sur un texte saisi par le client dans une zone multiligne, c’est celui qu’il faut.

datefiltre

{{ article.date | date: '%d %B %Y' }} rend un mois en anglais. date_fr rend 8 mars 2026, fuseau Europe/Paris, sans format à passer. Le prix de la simplicité : un seul style, non paramétrable.

Les imagesassets/

assets/ n’accepte que .css et .svg. Pas de .png, pas de .jpg, pas de .woff2. img_url et image_url n’existent pas.

Les photographies passent donc par une adresse : un réglage "type": "image" tient une URL, et le thème l’écrit dans un src ou un background-image. Les polices se chargent depuis une feuille de style externe ou restent celles du système.

L’archive.json

Shopify livre un .zip. Webcosa livre un .json qui contient les mêmes fichiers, dans un objet fichiers où la clé est le chemin. Le CLI et le CMS lisent ce format, et pousser --nouveau le fabrique à la volée depuis un dossier. Voir Import et export.

#Le formulaire de contact, sans {% form %}

C’est la réécriture la plus visible, et elle est plus simple qu’attendu : un <form> ordinaire qui poste vers la route du CMS.

sections/contact.liquid — Forgeliquid
<form method="post" action="/api/contacts">
  <label class="champ">
    <span>Votre nom</span>
    <input type="text" name="nom" required>
  </label>
  <label class="champ">
    <span>Votre e-mail</span>
    <input type="email" name="email" required>
  </label>
  <label class="champ">
    <span>Votre projet</span>
    <textarea name="message" required></textarea>
  </label>
  <button type="submit" class="bouton">{{ section.settings.bouton }}</button>
</form>

Le retour se lit dans page.demande, qui vaut merci ou erreur après l’envoi et reste vide sinon — de quoi remercier sans une ligne de JavaScript :

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>
{%- elsif page.demande == 'erreur' -%}
  <p class="message message-ko" role="alert">
    Le message n'a pas pu être envoyé. Vérifiez votre e-mail, puis réessayez.
  </p>
{%- endif -%}

#Ce qui n’existe pas

#Les objets du commerce

Ni product, ni collection, ni cart, ni customer, ni order. Exposer des objets vides serait pire que de ne rien exposer : on écrirait des thèmes contre une API qui ne rend jamais rien, et on découvrirait le vide en production.

La liste de ce qu’un thème peut lire est fermée, et c’est un contrat : site, page, settings, section, menu, articles, annee, theme_base. Rien d’autre. Voir Objets et Les absents.

Danger

Une variable absente rend une chaîne vide, pas une erreur — le moteur tourne avec strictVariables désactivé. Un {{ product.title }} oublié dans un coin de section ne casse rien et n’affiche rien : il ne se verra qu’à l’œil, sur la page. C’est la première chose à soupçonner quand un bloc paraît « ne pas marcher ».

#Les tags absents

Un tag inconnu, lui, ne passe pas silencieusement : c’est une erreur d’analyse. La section entière est remplacée par un commentaire HTML dans la page rendue, et le CLI refuse le fichier à l’écriture.

Ce qu’on écrivaitCe qu’on obtient
{% form 'contact' %}<!-- section … : tag "form" not found -->
{% paginate a by 10 %}<!-- section … : tag "paginate" not found -->
{% style %}<!-- section … : tag "style" not found -->
{% javascript %}idem
{% stylesheet %}idem
{% sections 'header-group' %}idem

Remplacements : un <form> nu pour form, un <style> dans la coquille pour style, assets/theme.css pour stylesheet, et {% section %} un par un pour sections.

#Les traductions

Il n’y a pas de locales/, donc pas de {{ 'general.title' | t }}. Les textes s’écrivent en clair dans le thème, en français, et ce qui doit varier d’un site à l’autre devient un réglage. Un thème Webcosa sert un site, dans une langue.

#Le JavaScript

Pas de .js dans assets/, et l’extension est refusée à l’entrée, pas ignorée au rendu :

Sortie du CLIbash
! ignoré : assets/menu.js extension non admise dans ce dossier

Ce n’est pas du rangement, c’est de la sécurité. Un thème s’exécute sous le domaine du client, avec ses cookies ; un .js déposé dans assets/ serait du script exécuté chez tous ses visiteurs. Le JavaScript de thème viendra quand on saura le relire.

Astuce

Beaucoup de comportements de thème Shopify se refont sans JavaScript. Vela implémente ses questions fréquentes en accordéon avec <details> / <summary>, et son menu mobile avec une case à cocher masquée. C’est ce que fait Piazza pour ses onglets. Regarde ces thèmes avant de conclure qu’il manque quelque chose.

#Les bornes du bac à sable

Elles n’ont pas d’équivalent visible chez Shopify, et un gros thème peut les toucher.

BorneValeur
Temps de rendu3 secondes, par page et par section
Source analysée512 Ko par rendu, inclusions comprises
Imbrication de {% section %}6 niveaux
Taille d’un fichier512 Ko
Nombre de fichiers120 par thème

C’est la dernière ligne qui arrête les portages : un thème Shopify complet dépasse allègrement 120 fichiers. Voir Le bac à sable.

#La méthode de reprise

Dans cet ordre. Chaque étape se vérifie avant la suivante, et l’ordre n’est pas indifférent : on veut voir une page rendre le plus tôt possible, quitte à ce qu’elle soit laide.

  1. Trier le thème d’origine

    Ouvre sections/ et range chaque fichier dans une des trois colonnes du tableau du haut. Tout ce qui parle de produit, de panier, de collection, de recherche, de compte client ou de paiement part à la poubelle maintenant — pas « on verra plus tard ». Compte ce qui reste : si tu passes sous 120 fichiers au total, le portage est viable.

    Ce que tu dois voir : une liste de sections dont chacune tient debout avec site, page, settings, section, menu et articles, et rien d’autre.

  2. Partir d’un thème d’origine, pas d’un dossier vide

    Installe Origo sur un site de préproduction et récupère-le. Tu obtiens une coquille qui fonctionne, un settings_schema.json valide, un index.json valide, et un .webcosa.json déjà réglé.

    node scripts/theme.mjs recuperer --theme=Origo --dossier=~/webcosa/portagecd ~/webcosa/portage

    Partir de zéro coûte une demi-journée à retrouver ce que les trois fichiers indispensables doivent contenir. Voir Créer son premier thème.

  3. Porter la feuille de style d’abord

    Copie le CSS de l’ancien thème dans assets/theme.css. C’est la partie qui se transpose le mieux et qui donne le plus de résultat visible : la mise en page, la typographie, les couleurs.

    Deux choses à corriger au passage : les url() qui pointaient vers des images du thème (elles doivent devenir des adresses absolues) et les @font-face qui chargeaient un .woff2 local.

    node scripts/theme.mjs pousser ↑ modifié assets/theme.css ✓ 1 modifié, 22 inchangés
  4. Porter la coquille

    layout/theme.liquid. Retire content_for_header, corrige les asset_url, remplace {% sections 'header-group' %} par des {% section %} un par un.

    Ce que tu dois voir : la page rend, avec l’en-tête et le pied de l’ancien thème. Vérifie dans le code source que le <link rel="stylesheet"> pointe sur /theme-assets/<id>/theme.css et que la feuille répond bien 200 — c’est le piège asset_url, et il ne se signale pas autrement.

  5. Porter les sections, une par une

    Une section, un pousser, un rechargement. Pas dix d’un coup : quand quelque chose ne rend pas, on veut savoir quoi.

    Pour chacune : remplacer les objets absents par des réglages, réécrire les sept filtres du tableau, et laisser le schéma tel quel — il passe presque toujours.

  6. Porter les templates

    templates/index.json en dernier, parce qu’il référence les sections. Les clés et l’ordre viennent de l’ancien thème ; les settings de chaque entrée sont à relire, puisque des réglages ont disparu en route.

    Ajoute templates/page.json si l’ancien thème avait un gabarit de page générique : sans lui, toute page sans template dédié retombe sur index.json, c’est-à-dire affiche la page d’accueil entière sous un autre titre.

  7. Relire le rendu, pas seulement le code

    Ouvre le code source de chaque page — pas l’inspecteur, qui ne montre pas toujours les commentaires — et cherche <!-- section. Chaque commentaire est une section qui a échoué en silence.

    Rendu de la pagehtml
    <!-- section produits : tag "paginate" not found -->
    <!-- section absente : header-group -->
  8. Chercher les vides

    Les objets absents rendent du vide sans rien dire. Passe le thème au peigne :

    grep -rnE "product|collection|cart|customer|metafield|routes\.|shop\." sections snippets layout

    Chaque occurrence est un trou dans la page, qui ne se signalera jamais tout seul.

#Ce qu’on gagne au change

La liste des absents est longue, alors il faut dire l’autre moitié : ce format donne des choses que Shopify ne donne pas à un site vitrine.

  • menu vient du CMS, pas d’une liste écrite dans le thème. Le client gère sa navigation dans Site web → Navigation, et le thème l’affiche. Un thème qui déclare ses propres liens oblige à les tenir à jour à deux endroits.
  • page.demande rend le remerciement de formulaire trivial, sans JavaScript et sans page dédiée.
  • teinte évite les vingt sélecteurs de couleur que personne ne remplit.
  • Le formulaire survit au changement de thème : il poste vers le CMS, pas vers le thème.

#La suite