Le Twig à côté du PHP
Avec Timber, un thème WordPress se partage en deux. Le PHP prépare les données, le Twig les affiche. On passe de l’un à l’autre plusieurs fois par jour.
Deux styles dans le même thème, c’est deux façons de lire le code. On a choisi d’en garder une seule : celle de WordPress. On vise 100 % des WordPress Coding Standards. PHPCS les vérifie sur le PHP. Il manquait l’équivalent sur le Twig.
L’outil existait presque. @zackad/prettier-plugin-twig formate le Twig avec Prettier, à chaque enregistrement. Mais il suit les standards de Twig, qui disent l’inverse de ceux de WordPress : pas d’espace dans les parenthèses, pas d’espace autour du |.
Pourquoi un fork plutôt qu’une pull request
Notre style est dogmatique. On le sait, et on l’assume. On ne pouvait pas demander au mainteneur du plugin de porter un second style, qui contredit celui qu’il a choisi.
Le fork était la bonne réponse. Le plugin de zackad est lui-même un fork de celui de trivago. On a pris la suite, avec notre style par défaut, et on continue de récupérer les évolutions du plugin d’origine.
Les bugs, eux, ne sont pas une question de style. On a signalé au projet d’origine le premier qu’on a trouvé, sur l’échappement des chaînes.
Les standards WordPress appliqués au Twig
Le plugin transpose les WordPress Coding Standards au Twig :
- des espaces dans les parenthèses :
default( 'h2' ); - des espaces dans les séquences :
[ 1, 2, 3 ]; - des espaces autour d’un index variable, pas autour d’une chaîne :
list[ i ], maislist['key']; - un mapping de plusieurs propriétés sur plusieurs lignes, avec une virgule après la dernière ;
- des espaces autour du
|et dans les interpolations :"#{ name | lower }"; - un ternaire qui teste le vrai :
not active ? 'off' : 'on'devientactive ? 'on' : 'off'.
Avant, un template tel qu’on en trouvait sur nos projets :
{% set args = {post_type:'post',posts_per_page:3} %}
{% for post in get_posts(args) %}
<a href="{{post.link}}" class="card {{not post.thumbnail?'card--text':'card--image'}}">{{- post.title|upper -}}</a>
{% endfor %}
Après un enregistrement :
{% set args = {
post_type: 'post',
posts_per_page: 3,
} %}
{% for post in get_posts( args ) %}
<a href="{{ post.link }}" class="card {{ post.thumbnail ? 'card--image' : 'card--text' }}">
{{- post.title | upper -}}
</a>
{% endfor %}
Le site affiche exactement la même chose. Seule la lecture change.
Les règles WordPress tiennent dans une seule option. Un projet qui préfère le style de Twig la désactive.
Les bugs qu’il corrige
Un formateur ne doit jamais changer ce que le site affiche. En testant le plugin sur nos thèmes, on a trouvé plusieurs cas où il le faisait :
- des backslashes supprimés dans les chaînes, qui cassaient le template ;
- des parenthèses retirées, qui changeaient le sens d’un calcul ou d’un ternaire ;
- des valeurs écrites dans
{{ }}ou#{ }qui perdaient leurs accolades.
D’autres templates ne se lisaient pas du tout : un mapping dans une interpolation, une parenthèse en début d’argument, une macro avec un mapping comme valeur par défaut (args = {}), les opérateurs has some et has every. Le plugin les lit tous, comme Twig.
Le plugin lit aussi un motif qu’on utilise sur chaque projet : le nom de balise dynamique. Un bloc réutilisé à plusieurs endroits d’une page ne peut pas avoir un h2 fixe. Le client choisit le niveau du titre, et la hiérarchie reste correcte pour l’accessibilité comme pour le référencement :
<{{ block.heading | default( 'h2' ) }} class="title">
{{- block.title -}}
</{{ block.heading | default( 'h2' ) }}>
Le plugin d’origine s’arrêtait sur ce code.
Pas de Twig dans une balise HTML
Le plugin refuse un {% if %} placé entre les attributs d’une balise. On a choisi de garder ce refus et d’en faire une règle.
La règle a déjà payé. Voici une balise de notre dernier thème, avant :
<main
id="content"
role="main"
{%- if archive.schema_type -%}
itemscope
itemtype="https://schema.org/{{ archive.schema_type }}"
{%- endif -%}
>
Le tiret de {%- supprime les blancs qui précèdent la balise, y compris l’espace qui séparait les deux attributs. Le HTML sortait ainsi :
<main id="content" role="main"itemscope itemtype="https://schema.org/CollectionPage">
Personne ne l’avait vu. Après, l’attribut conditionnel passe par html_attr, une fonction que Twig fournit depuis la version 3.24, dans son extension HTML :
<main
id="content"
role="main"
{{
html_attr(
archive.schema_type
? {
itemscope: true,
itemtype: 'https://schema.org/' ~ archive.schema_type,
}
)
}}
>
La fonction place les espaces elle-même. Elle omet les attributs qui valent null ou false, et échappe les autres valeurs. Avec Timber, qui désactive l’échappement automatique, un guillemet ne casse plus le HTML.
Installer le plugin Prettier pour Twig
Le plugin s’installe depuis GitHub, sous le nom du plugin d’origine. C’est le nom que prettier-plugin-tailwindcss attend pour trier les classes dans le Twig :
pnpm add -D @zackad/prettier-plugin-twig@github:agencecinq/prettier-plugin-twig#v1.0.3
Puis dans .prettierrc.json :
{
"useTabs": true,
"plugins": ["@zackad/prettier-plugin-twig", "prettier-plugin-tailwindcss"]
}
Le README détaille chaque règle, et explique comment migrer depuis le plugin d’origine.
Les contributions sont les bienvenues
Le plugin est open source, sous licence Apache 2.0, comme celui dont il part. Il vit sur GitHub.
Vous écrivez du Twig avec Timber et vous suivez les standards WordPress ? Il est fait pour vous. Un bug, un cas que le plugin ne lit pas : ouvrez une issue. Une correction : les pull requests sont les bienvenues.
Ce qu’on en retire
Un outil de formatage ne se voit pas sur un site. Il se voit dans les relectures de code : plus de discussions sur les espaces, des diffs qui ne montrent que ce qui change vraiment. Le code reste lisible par la personne qui viendra après nous. C’est aussi ce qui fait d’un site un actif : un code que d’autres peuvent faire évoluer.
C’est ce qu’on défend dans Ce qui fait un bon code d’agence : montrer le niveau sur des briques ouvertes. Celle-ci en est une.