DIGITAL 5 min de lecture

Prettier pour Twig : pourquoi on a forké le plugin

Nos thèmes WordPress sont écrits en Twig, avec Timber. On a forké le plugin Prettier de référence pour que ce Twig suive les WordPress Coding Standards, comme notre PHP. Au passage, on a corrigé les bugs qui changeaient le code.

Un template Twig écrit sans règles passe par Prettier et ressort formaté selon les WordPress Coding Standards

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 ], mais list['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' devient active ? '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.

On commence par 30 minutes.

On regarde votre site en direct, on vous dit si on est le bon interlocuteur, et on vous donne un ordre de grandeur. Le diagnostic, s'il a du sens, se décide à la fin de l'appel.

Réserver 30 min