</> HTML5Advent
ENFRESDEITPT

// html · Web Platform Advent #5

Web Components : Custom Elements, Shadow DOM et templates

Créez des éléments HTML réutilisables avec les API natives Web Components — définir un Custom Element, encapsuler les styles avec le Shadow DOM, cloner un <template> et passer des données par attributs.

Un tas de briques de construction en plastique colorées et emboîtables de différentes formes

Les Web Components sont un ensemble d'API natives du navigateur qui permettent de créer vos propres éléments HTML réutilisables — sans aucun framework. Un composant que vous publiez fonctionne à l'identique en HTML pur, React, Vue ou Svelte, car c'est simplement un élément que le navigateur comprend. Trois API le rendent possible : les Custom Elements, le Shadow DOM et la balise <template>.

Custom Elements

Un Custom Element est une classe qui étend HTMLElement, enregistrée avec un nom de balise qui doit contenir un trait d'union (pour ne jamais entrer en collision avec une future balise native) :

class GreetingCard extends HTMLElement {
  connectedCallback() {
    const p = document.createElement('p');
    p.textContent = 'Bonjour depuis un custom element !';
    this.append(p);
  }
}

customElements.define('greeting-card', GreetingCard);

Une fois défini, vous l'utilisez comme n'importe quelle balise :

<greeting-card></greeting-card>

La classe vous donne des callbacks de cycle de vie. Les plus utiles sont connectedCallback (l'élément a été ajouté à la page), disconnectedCallback (il a été retiré) et attributeChangedCallback (un attribut surveillé a changé).

Réagir aux attributs

Pour passer des données, lisez les attributs — exactement comme src sur une image. Déclarez ceux à surveiller avec un getter statique observedAttributes, puis répondez dans attributeChangedCallback. Construire le nœud avec textContent sécurise les valeurs fournies par l'utilisateur :

class GreetingCard extends HTMLElement {
  static get observedAttributes() {
    return ['name'];
  }
  attributeChangedCallback(attr, oldVal, newVal) {
    if (attr === 'name') this.render();
  }
  connectedCallback() {
    this.render();
  }
  render() {
    const name = this.getAttribute('name') || 'ami';
    this.replaceChildren();
    const p = document.createElement('p');
    p.textContent = 'Bonjour, ' + name + ' !';
    this.append(p);
  }
}
customElements.define('greeting-card', GreetingCard);

Désormais <greeting-card name="Ada"></greeting-card> affiche « Bonjour, Ada ! », et changer l'attribut en direct le redessine.

Une boîte à outils métallique ouverte avec des outils rangés dans des compartiments séparés
Un web component est comme un outil dans une boîte à outils : une unité autonome et étiquetée que l'on saisit et réutilise sur de nombreux projets sans la reconstruire à chaque fois.

Le Shadow DOM : une vraie encapsulation

Le Shadow DOM donne à un élément un sous-arbre privé dont les styles et le balisage sont isolés du reste de la page. Un <style> à l'intérieur ne peut jamais fuir, et le CSS extérieur ne peut jamais y pénétrer par accident. Construisez-le avec l'API DOM et un <slot> pour le contenu projeté :

class FancyButton extends HTMLElement {
  connectedCallback() {
    const shadow = this.attachShadow({ mode: 'open' });

    const style = document.createElement('style');
    style.textContent =
      'button { background:#2563eb; color:#fff; border:0;' +
      ' border-radius:8px; padding:.6rem 1rem; }';

    const button = document.createElement('button');
    button.append(document.createElement('slot'));

    shadow.append(style, button);
  }
}
customElements.define('fancy-button', FancyButton);

Le <slot> est un emplacement réservé : ce que vous placez entre les balises — <fancy-button>Enregistrer</fancy-button> — est projeté à cet endroit, de sorte que le composant enveloppe votre contenu sans le remplacer.

Templates : cloner une fois, réutiliser à bas coût

L'élément <template> contient un balisage inerte, analysé mais non rendu tant que vous ne le clonez pas. C'est la façon efficace de répéter une structure :

<template id="row-tpl">
  <li><span class="label"></span></li>
</template>

<script>
  const tpl = document.getElementById('row-tpl');
  const node = tpl.content.cloneNode(true);
  node.querySelector('.label').textContent = 'Élément 1';
  document.querySelector('ul').append(node);
</script>

Les trois pièces réunies

APICe qu'elle apporte
Custom ElementsVos propres balises avec callbacks de cycle de vie
Shadow DOMEncapsulation du style et du balisage, slots
<template>Balisage inerte réutilisable et clonable

Vous avez rarement besoin des trois à la fois. Un simple conteneur peut n'utiliser qu'un Custom Element ; un widget stylé et autonome combine un Custom Element avec le Shadow DOM et un slot. Comme le résultat est du DOM standard, il s'intègre à n'importe quelle stack et survit au framework à la mode l'année où vous l'avez écrit.