#Assets
Le dossier assets/ contient les fichiers statiques d’un thème : sa feuille
de style, ses éventuelles icônes vectorielles. Deux extensions sont
admises, .css et .svg, et la liste est fermée.
Cette page explique comment un asset reçoit son adresse, comment il est réellement servi — par une route, et non depuis un disque — et pourquoi il n’y a pas de JavaScript.
#Obtenir l’adresse d’un fichier
Un seul chemin : le filtre asset_url, qui prend le nom du fichier.
{{ 'theme.css' | asset_url | stylesheet_tag }}<link rel="stylesheet" href="/theme-assets/8f2c…/theme.css">asset_url colle le nom derrière theme_base, qui vaut
/theme-assets/<id-du-thème>. stylesheet_tag enveloppe le résultat dans une
balise link, en échappant l’adresse.
Le nom seul, jamais le chemin. {{ 'assets/theme.css' | asset_url }}
produit /theme-assets/<id>/assets/theme.css — deux segments de nom, qui ne
correspondent à aucune route. La feuille de style répond 404 et la page rend
quand même, en HTML nu.
C’est exactement le défaut qui a tenu sur tous les sites à la fois, sans jamais lever d’erreur : un site sans CSS ressemble à un thème mal écrit, pas à une adresse fausse.
Pour une image :
<img src="{{ 'motif.svg' | asset_url }}" alt="">asset_url lit theme_base, qui est posé en variable globale du moteur
et non dans la portée ordinaire. Il fonctionne donc partout, y compris dans un
snippet appelé par {% render %} — alors que ce dernier n’y voit ni site,
ni section, ni rien d’autre du contexte de la page. Voir
Snippets.
#Comment un asset est servi
Pas depuis le disque. La route /theme-assets/<theme>/<fichier> lit le
contenu en base, dans la table des fichiers du thème.
/theme-assets/8f2c…/theme.cssContent-Type: text/css; charset=utf-8
Cache-Control: private, max-age=60
Vary: Cookie, Host
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src data:; sandbox
Content-Disposition: inlineNot found#Pourquoi une route, et pas un fichier statique
Parce que les assets sont modifiables. L’éditeur de code du CMS et le CLI les réécrivent ; un fichier posé sur le disque au moment de l’installation ne suivrait pas. Un thème vit en base une fois installé — voir Anatomie.
#Les quatre gardes de la route
L’extension décide du type servi
.cssdevienttext/css,.svgdevientimage/svg+xml. Toute autre extension répond 404 avant la moindre requête en base. Renvoyer le type déclaré par le fichier lui-même permettrait de servir du HTML sous le domaine d’un client — donc du script, avec ses cookies.Le nom est revalidé
Même expression régulière qu’à l’écriture. Le nom est recomposé, jamais concaténé depuis l’URL :
..%2Fet autres échappements ne peuvent pas sortir du dossierassets.Le thème doit être publié
Un brouillon en cours de refonte reste invisible même si l’on devine son identifiant. La lecture publique est ouverte — les visiteurs d’un site n’ont évidemment aucun compte — mais uniquement pour ce qui est en ligne.
Le thème doit appartenir au site de l’hôte
Sans ce test, l’identifiant d’un thème publié suffisait à le faire servir sous le domaine de n’importe quel autre client. C’est l’un des trois défauts qui, composés, ouvraient une exécution de script sous l’origine de n’importe quel site.
#Le cache
Cache-Control: private, max-age=60, avec Vary: Cookie, Host.
private et non public, parce que la réponse dépend de deux choses
invisibles dans l’URL : l’hôte appelant, et la session — le propriétaire d’un
site en brouillon voit ce qu’un anonyme ne voit pas, et c’est ce qui fait
marcher l’aperçu. Un cache partagé pouvait mémoriser la réponse servie au
propriétaire et la redistribuer.
Soixante secondes, et c’est court exprès : qui retouche son CSS dans l’éditeur doit voir l’effet en rechargeant, pas dans une heure. Le cache du navigateur reste en place, et c’est lui qui portait l’essentiel du gain — une page vitrine recharge la même feuille de style à chaque écran.
Après un pousser, si le CSS ne bouge pas : attends une minute, ou recharge
en contournant le cache. Ce n’est presque jamais le thème.
#La feuille de style n’est pas un template
Le contenu d’assets/theme.css est servi tel quel. Il n’est pas rendu par
Liquid : {{ settings.accent }} dans un .css sera écrit littéralement dans
la feuille de style, et le navigateur ignorera la déclaration.
C’est pour cela que les quatre thèmes posent leurs réglages en variables CSS dans la coquille, et nulle part ailleurs :
<style>
:root {
--accent: {{ settings.accent }};
--accent-sombre: {{ settings.accent | teinte: -18 }};
--largeur: {{ settings.largeur }}px;
}
</style>.bouton {
background: var(--accent);
border-radius: var(--bouton-rayon);
}
.enveloppe {
max-width: var(--largeur);
margin-inline: auto;
}L’avantage n’est pas seulement de faire marcher les réglages : la feuille de style reste un fichier CSS ordinaire, que l’éditeur colore, que le navigateur met en cache, et qu’on peut relire sans démêler du Liquid.
Les quatre thèmes n’ont chacun qu’un seul asset — theme.css, entre 11 et
23 Ko. Rien n’interdit d’en découper plusieurs : une @import relative depuis
theme.css résout bien vers le même dossier servi.
#Ce que la page peut charger d’ailleurs
Un site public est servi avec une politique de sécurité de contenu, et elle borne ce qu’une feuille de style peut aller chercher :
| Directive | Valeur sur un site client |
|---|---|
style-src | 'self' 'unsafe-inline' |
font-src | 'self' data: |
img-src | 'self' data: blob: + la bibliothèque de médias |
script-src | 'self' + un nonce, plus les traceurs si le client les active |
Une police web hébergée ailleurs ne se charge pas. font-src 'self' data: bloque Google Fonts et tous les autres. Les quatre thèmes utilisent des
polices système, ou proposent un choix entre sans empattements et
empattements — voir le réglage police_titres d’origo. Une police
personnelle devrait être encodée en data: dans le CSS, ce qui alourdit la
première visite.
Même chose pour une image de fond servie par un CDN tiers : img-src ne
l’autorise pas.
#Les SVG
Admis, et servis avec trois protections qui vont ensemble.
Un SVG est un document scriptable : ouvert dans un onglet,
image/svg+xml exécute ses balises script, et nosniff n’y change rien.
D’où :
- une CSP
sandboxposée par la route elle-même, et non par le proxy — le proxy exclut les chemins finissant par une extension d’image, il ne passe donc jamais sur cette réponse ; - un
Content-Disposition: inline; filename="…", qui laisse le fichier se rendre comme une image dans unimgou en CSS — le seul usage prévu — mais empêche son ouverture directe d’être interprétée comme une page ; - le
nosniff, qui interdit au navigateur de deviner un autre type.
Pour une petite icône, un SVG écrit directement dans le balisage d’une
section est souvent préférable : une requête de moins, et il hérite de
currentColor. Réserve assets/ aux fichiers qui se répètent ou qui sont
gros.
#Pourquoi il n’y a pas de JavaScript
.js n’est pas admis dans assets/, et l’absence est délibérée.
Un thème est du contenu modifiable par le client, et demain importable depuis
l’extérieur. Un .js déposé dans assets/ serait du script exécuté dans le
navigateur de tous les visiteurs du site, sous son domaine, avec accès à
ses cookies. Ce n’est pas un risque théorique : c’est le vecteur classique
d’une archive de thème piégée.
Le JavaScript de thème viendra quand on saura le relire — c’est-à-dire quand il y aura une réponse à « qu’est-ce qu’on accepte, et qui le vérifie ». En attendant, un thème est du HTML et du CSS.
Le blocage est double, et c’est voulu. La liste d’extensions arrête le fichier
à l’écriture ; la CSP du site public arrête le script au navigateur, puisque
script-src n’admet que l’origine et un nonce que le thème n’a aucun moyen de
connaître.
#Ce qu’on fait à la place
Le CSS moderne couvre l’essentiel de ce pour quoi un thème vitrine ouvrait un fichier JavaScript :
| Besoin | Sans JavaScript |
|---|---|
| Menu repliable | details / summary, ou une case à cocher |
| Accordéon de FAQ | details / summary |
| Apparition au défilement | animation-timeline: view() |
| Défilement doux vers une ancre | scroll-behavior: smooth |
| Carrousel | scroll-snap-type, avec un débordement horizontal |
| Retour après envoi d’un formulaire | page.demande, côté serveur |
Ce dernier mérite un mot : le formulaire de contact des quatre thèmes poste
vers /api/contacts, la route redirige, et la page se rend à nouveau avec
page.demande valant merci ou erreur. Un message de remerciement sans une
ligne de script. Voir Objets.
#Les erreurs fréquentes
Le chemin complet passé à asset_url{{ 'assets/theme.css' | asset_url }}. Le site rend sans style, et rien ne le
signale. Vérifie dans l’onglet réseau : un 404 sur une adresse à deux
segments.
Un filtre dans un argument de render{% render 'carte', fond: 'fond.svg' | asset_url %} n’applique pas le
filtre : le snippet reçoit la chaîne brute, ou rien. Construis la valeur dans un
{% assign %} avant l’appel. Le filtre lui-même marche dans un snippet —
c’est l’argument qui ne le supporte pas.
Du Liquid dans un .cssÉcrit littéralement dans la feuille de style. Passe par des variables CSS posées dans la coquille.
Une police ou une image externeBloquée par la politique de sécurité du site public. Rien n’apparaît dans le rendu, tout apparaît dans la console du navigateur.
Le CSS ne change pas après un pousserSoixante secondes de cache navigateur. Recharge en contournant le cache avant de chercher plus loin.

