Retour à l'accueil

Symbiote.js : composition de composants web sans DOM virtuel | Guide

Guide technique sur l'utilisation de Symbiote.js pour créer des composants web. Nous décomposons l'implémentation des onglets, les mécanismes de contexte partagé et les avantages de l'approche low-code. Adapté aux développeurs middle/senior.

Symbiote.js : comment assembler des interfaces comme un constructeur via HTML
Advertisement 728x90

Symbiote.js : Composition avancée de composants web sans DOM virtuel

Symbiote.js est une bibliothèque légère (~6 KB Brotli) pour créer des composants web, axée sur la composition avancée via le markup HTML. Elle résout le problème de l'organisation structurelle des interfaces en éliminant la dépendance au DOM virtuel et en fournissant des mécanismes flexibles pour travailler avec les contextes de données. Contrairement aux frameworks traditionnels, Symbiote.js travaille directement avec l'API DOM, minimisant le code boilerplate et permettant de combiner les composants via un markup déclaratif.

Fondamentaux de la composition structurelle

Le développement d'interfaces repose sur deux aspects : le modèle logique (composants, données) et la composition structurelle (connexions entre composants, flux de données). Symbiote.js propose une approche unique pour ce dernier, en utilisant le HTML comme outil de premier plan pour définir les dépendances. Principes clés :

  • Les templates de composants sont des chaînes HTML indépendantes
  • Le markup externe définit les connexions structurelles
  • Pas de liaison à l'environnement d'exécution JS
  • Support des approches hybrides (rendu serveur/client)

Cela permet d'animer du markup pré-généré, de créer des composants « dumb » sans logique propre, ou d'utiliser des fournisseurs de contexte. Un exemple est la mise en œuvre d'onglets à l'aide de deux composants liés :

Google AdInline article slot
import Symbiote, { css } from '@symbiotejs/symbiote';

class SuperTabs extends Symbiote {
  init$ = {
    '*currentTabName': 'first',
  };

  renderCallback() {
    this.tabEls = [...this.querySelectorAll('[tab]')];
    this.tabEls.forEach((/** @type {HTMLElement} */ el) => {
      let tab = el.getAttribute('tab');
      if (el.hasAttribute('current')) {
        this.$['*currentTabName'] = tab;
      }
      el.onclick = () => {
        this.$['*currentTabName'] = tab;
      };
    });
    this.sub('*currentTabName', (val) => {
      this.tabEls.forEach((/** @type {HTMLElement} */ el) => {
        if (el.getAttribute('tab') === val) {
          el.setAttribute('current', '');
        } else {
          el.removeAttribute('current');
        }
      });
    });
  }
}

SuperTabs.rootStyles = css`
super-tabs {
  display: inline-flex;
  gap: 2px;
  [tab] {
    cursor: pointer;
    &[current] {
      background-color: transparent;
      pointer-events: none;
    }
  }
}
`;

SuperTabs.reg('super-tabs');

Le second composant gère l'affichage du contenu :

class SuperTabsView extends Symbiote {
  renderCallback() {
    this.tabCtxEls = [...this.querySelectorAll('[tab-ctx]')];
    this.sub('*currentTabName', (val) => {
      this.tabCtxEls.forEach((/** @type {HTMLElement} */ el) => {
        if (el.getAttribute('tab-ctx') === val) {
          el.setAttribute('active', '');
        } else {
          el.removeAttribute('active');
        }
      });
    });
  }
}

SuperTabsView.rootStyles = css`
super-tabs-view {
  display: block;
  [tab-ctx] {
    display: none;
    &[active] {
      display: contents;
    }
  }
}
`;

SuperTabsView.reg('super-tabs-view');

Fonctionnalités techniques clés

L'exemple démontre cinq mécanismes critiques :

  • rootStyles — les styles s'appliquent correctement indépendamment de la présence de Shadow DOM dans les éléments parents. Cela garantit la compatibilité avec Tailwind, Bootstrap et les styles globaux.
  • renderCallback — cycle de vie avec accès aux nœuds enfants (contrairement au connectedCallback standard).
  • init$ — initialisation des propriétés réactives avec des valeurs par défaut.
  • sub() — abonnement aux changements de propriétés via Pub/Sub sans DOM virtuel.
  • *Propriétés via (astérisque)** — déclaration de contexte partagé (similaire à l'attribut name sur input[type=radio]).

La caractéristique architecturale est le travail direct avec l'API DOM sans abstractions. Ce n'est pas un anti-pattern mais un choix délibéré qui élimine le surcoût de la représentation virtuelle. Par ailleurs, la bibliothèque supporte le typage, la réactivité et le débogage à l'exécution.

Google AdInline article slot

Paradigme low-code en action

L'avantage de Symbiote.js brille dans les scénarios où les interfaces sont assemblées via des attributs HTML sans JavaScript. Considérez un composant pour afficher l'onglet actuel :

class SuperCurrent extends Symbiote {}

SuperCurrent.rootStyles = css`
super-current {
  display: block;
  h2 {
    text-transform: capitalize;
  }
}
`;

SuperCurrent.template = html`<h2>{{*currentTabName}}</h2>`;

SuperCurrent.reg('super-current');

Utilisation :

<super-current ctx="section-select"></super-current>

Cette approche ouvre des possibilités pour :

Google AdInline article slot
  • Les designers : prototyper des interfaces directement en HTML
  • Les gestionnaires de contenu : personnaliser l'affichage via des attributs sans code
  • Les solutions embarquées : configurer des widgets via du markup
  • Les systèmes d'IA : générer des interfaces avec un markup et des connexions de comportement prévisibles

Plus il y a de composants dans la bibliothèque, plus il est facile d'assembler des interfaces complexes via HTML. Chaque groupe de composants est isolé via l'attribut ctx, permettant plusieurs instances indépendantes sur la page.

Ce qui compte

  • Zéro dépendance aux bundlers : les composants fonctionnent sans Webpack/Vite
  • Contexte via DOM : les connexions entre composants sont définies par la structure du markup
  • Taille de 6 KB : impact minimal sur les performances
  • Compatibilité avec n'importe quel stack : intégration dans des projets existants sans migration
  • Types prêts à l'emploi : support TypeScript sans configuration supplémentaire

Symbiote.js ne remplace pas les frameworks mais les complète dans le domaine de la composition structurelle. Pour les tâches logiques, elle fournit une API minimaliste, se concentrant sur ce que d'autres solutions implémentent via des bibliothèques tierces (gestion d'état, routage). Cela réduit la charge cognitive lors de la conception d'interfaces, surtout dans des équipes aux profils variés.

— Editorial Team

Advertisement 728x90

Lire ensuite