Web Components et Shadow DOM

Les Web Components ne sont pas une nouveauté. Ils existent depuis 2011. Cependant, cette fonctionnalité était en développement et beaucoup de navigateur ne la prenaient pas en charge

Les composants web permettent de créer des balises HTML personnalisées, réutilisables. Avec le Shadow DOM on peut aussi encapsuler son composant.

Dans cet article, je vais vous parler de l'interopérabilité et surtout de l'encapsulation qui semble être un petit aspect des Web Components mais qui est en fait très important.

Création d'un Web Component

Pour créer un composant web, il suffit de créer un objet qui hérite de HTMLElement.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>Web Component</title>
</head>
<body>
  <script>
    class HelloWorld extends HTMLElement {

       connectedCallback(){
          this.innerHTML = "<Bonjour !>";
       }

    }
    customElements.define("hello-component", HelloWorld);
  </script>
  <hello-component></hello-component>
</body>
</html>

La fonction connectedCallback est appelée lorsque l'élément personnalisé est connecté pour la première fois au DOM du document.

Avec la méthode define on lie la classe à la balise HTML.

Encapsulation

L'iframe était le seul élément qui nous permettait d'encapsuler un composant. On pouvait marquer du code HTML et CSS dans une autre page dans un iframe et ensuite mettre l'iframe dans une page et écrire du CSS sans modifier ce qu'il y avait dans l'iframe

Malheureusement, l'iframe souffre de problèmes de performance.

Maintenant, avec le Shadow DOM et les Web Component, on peut encapsuler comme ça :

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>Web Component</title>
</head>
<style>
  h2 {
    color: red;
  }
</style>
<body>
  <script>
    class HelloWorld extends HTMLElement {

       constructor(){
          super();
          this.root = this.attachShadow({mode: "closed"});
       }

       connectedCallback(){
          this.root.innerHTML = "<h2>Bonjour !</h2>";
       }

    }
    customElements.define("hello-component", HelloWorld);
  </script>
  <hello-component></hello-component>
  <script>
    const $webComponent = document.querySelector("hello-component");
    $webComponent.shadowRoot.querySelector("h2").innerText = "Bonjour modifié !";
  </script>
</body>
</html>

Normalement le titre devrait être en rouge avec ce code CSS. Cependant, grace au Shadow DOM la couleur du titre reste inchangée.

De plus, même avec du Javascript et l'utilisation de querySelector on ne peut pas modifier ce qu'il y a dans le composant. La méthode retourne null.

La fonction attachShadow peut utiliser le {mode : "open"} qui lui permettra de modifier le composant.

Interopérabilité

Une fois le composant web créé, on peut l'importer en utilisant le nom de la balise et le fichier Javascript.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>Web Component</title>
  <script src="js/hello-component.js"></script>
</head>
<body>
  <hello-component></hello-component>
</body>
</html>

Et le fichier Javascript :

class HelloWorld extends HTMLElement {

  constructor(){
     super();
     this.root = this.attachShadow({mode: "closed"});

     /*Création du titre*/
     var titre = document.createElement("h2");
     var bonjour = document.createTextNode("Bonjour !");
     titre.appendChild(bonjour);

     /*Ajoute le titre*/
     this.root.appendChild(titre);

  }

}
customElements.define("hello-component", HelloWorld);

Avant on pouvait importer un fichier HTML et l'injecter dans un fichier HTML existant. Cette fonctionnalité est dépréciée et sera supprimée... C'est dommage.

La page de base :

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>Web Component</title>
  <!--DEPRECATED !!-->
  <link rel="import" href="hello.html" />
  <!--DEPRECATED !!-->
 
  <script src="js/hello-component.js"></script>
</head>
<body>
  <hello-component></hello-component>
</body>
</html>

Le fichier Javascript :

class HelloWorld extends HTMLElement {

  constructor(){
    super();
    this.root = this.attachShadow({mode: "closed"});

    /*Sélectionne les imports*/
    var link = document.querySelector('link[rel="import"]');
    var content = link.import;
    var el = content.querySelector('#hello');

    /*Ajoute l'élément*/
    this.root.appendChild(el.cloneNode(true));

  }

}
customElements.define("hello-component", HelloWorld);

Le fichier HTML du composant :

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <h2 id="hello">Bonjour !</h2>
</body>
</html>

Conclusion

Les Web components sont très puissants et permettent une encapsulation qui n'était possible auparavant qu'avec les iframes

webcomponents.org ce veut rassurant en nous montrant le tableau de compatibilité des fonctionnalités. On peut voir que tous les navigateurs récents prennent en charge les fonctionnalités.

Commentaires