# « Antipatterns d’attributs : du HTML brut plutôt que des données »

> Un bloc où tout le contenu variable est un blob de HTML difficile à faire évoluer. Symptômes concrets et méthode de refactorisation progressive.

- Auteur : Clément Hadrot
- Publié le : 2025-09-08
- Mis à jour le : 2025-09-08
- Catégorie : Blocs Gutenberg
- URL : https://wpmoderne.dev.wordpress-developpement.fr/blocs/antipatterns-attributs-html-brut-plutot-que-donnees/

## L’essentiel

- Un attribut source de type html empêche toute évolution structurée
- Séparer chaque donnée en attribut distinct et typé
- Migrer avec deprecated plutôt que tout réécrire d'un coup

Un bloc « carte témoignage » hérité, repris par Kaolin lors de la reprise de maintenance d'un site pour une agence immobilière, illustre un antipattern fréquent chez qui découvre l'API de blocs sans être passé par React auparavant : au lieu de décomposer le contenu en attributs distincts, le développeur d'origine avait tout capturé dans un unique attribut de type `html`, capturé depuis une `div` entière du `save`.

## Ce qu'on voit

```
{
  "attributes": {
    "contenu": {
      "type": "string",
      "source": "html",
      "selector": ".temoignage-contenu"
    }
  }
}
```

Dans l'éditeur, ce seul attribut contenait le nom du client, sa note, sa citation et le nom de son quartier, tous mélangés dans une structure HTML rigide générée par une unique zone `RichText` permettant un formatage libre. Rien n'empêchait un rédacteur de supprimer accidentellement la balise contenant la note en éditant le texte, ou de casser la mise en forme en collant du texte depuis un traitement de texte externe.

## Pourquoi c'est un problème

Cette approche casse plusieurs promesses fondamentales de l'API de blocs. D'abord, il devient impossible de faire évoluer une seule donnée (par exemple ajouter un champ « date de l'avis ») sans risquer de casser le parsing du HTML existant sur tous les témoignages déjà publiés. Ensuite, aucune validation individuelle n'est possible : un champ note qui devrait être un nombre entre 1 et 5 est ici une portion de texte libre dans un blob HTML, sans aucune garantie de format. Enfin, ce type d'attribut interdit toute réutilisation de la donnée ailleurs (un Query Loop qui voudrait trier les témoignages par note, par exemple), puisqu'elle n'existe nulle part sous forme structurée, ni en attribut typé ni en post meta.

> L'essentiel à retenir : Un attribut source de type html empêche toute évolution structurée ; Séparer chaque donnée en attribut distinct et typé ; Migrer avec deprecated plutôt que tout réécrire d'un coup

## Quoi faire : décomposer en attributs typés

La refactorisation cible un schéma d'attributs explicite, chaque donnée ayant son propre type et sa propre source :

```
{
  "attributes": {
    "nomClient": { "type": "string", "default": "" },
    "quartier": { "type": "string", "default": "" },
    "note": { "type": "number", "default": 5 },
    "citation": {
      "type": "rich-text",
      "source": "rich-text",
      "selector": ".temoignage-citation"
    }
  }
}
```

Chaque champ devient éditable indépendamment dans l'inspecteur (un `TextControl` pour le nom, un `RangeControl` pour la note), avec une validation propre à chacun, et surtout une structure qui ne dépend plus d'un parsing fragile d'un unique bloc HTML.

## Migrer sans tout casser : une déprécation, pas une réécriture brutale

Les témoignages déjà publiés utilisent l'ancien schéma. Une entrée `deprecated` avec une fonction `migrate` extrait les anciennes données du HTML brut vers les nouveaux attributs typés, en s'appuyant sur une expression régulière ciblée sur la structure connue de l'ancien rendu :

```
const ancienneVersion = {
  attributes: { contenu: { type: 'string', source: 'html', selector: '.temoignage-contenu' } },
  save({ attributes }) {
    return <div className="temoignage-contenu">{/* ancien rendu */}</div>;
  },
  migrate({ contenu }) {
    const nomMatch = contenu.match(/data-nom="([^"]*)"/);
    const noteMatch = contenu.match(/data-note="(\d)"/);
    return {
      nomClient: nomMatch ? nomMatch[1] : '',
      note: noteMatch ? parseInt(noteMatch[1], 10) : 5,
      citation: contenu,
    };
  },
};
```

## Une méthode progressive plutôt qu'un big bang

- Identifier d'abord toutes les données réellement distinctes cachées dans le blob HTML, sans chercher à tout migrer en une seule passe si le nombre de champs est élevé.
- Écrire la fonction `migrate` et la tester sur un échantillon représentatif de contenus réels avant de la déployer.
- Conserver l'ancienne entrée `deprecated` plusieurs versions, le temps que tous les contenus existants aient été rouverts au moins une fois dans l'éditeur pour déclencher la migration.

> Un attribut de type html n'est pas un raccourci, c'est une dette qui se paie au moment où quelqu'un doit enfin lire la donnée qu'il contient.

## Ce que cet article ne couvre pas

Le mécanisme formel des déprécations de blocs, sa syntaxe complète et ses subtilités (`isEligible`, ordre de résolution parmi plusieurs entrées) sont un sujet distinct déjà traité en détail ailleurs. Cet article se concentre sur le symptôme et la méthode de refactorisation, pas sur la mécanique de migration elle-même.

## Notre verdict

Le bloc témoignage compte aujourd'hui onze attributs typés au lieu d'un unique blob HTML, et un nouveau champ (l'ajout d'une photo de profil) s'est intégré en une heure de travail, contre ce qui aurait nécessité une réécriture complète du parsing dans l'ancienne version.
