Pourquoi les Web Components présentent des lacunes en performance et expérience développeur
Les Web Components comme Lit et Symbiote créent chaque élément en tant que HTMLElement, ce qui entraîne une allocation d'environ 124 octets sur le tas C++. C'est 8 fois plus que les 16 octets pour un objet JS dans $mol. Pour 1 000 tâches, Lit/Symbiote consomment ~124 Ko rien que pour les nœuds DOM, tandis que $mol utilise ~16 Ko. Avec 10 000 tâches, la différence passe à 1,2 Mo contre 160 Ko.
Sans virtualisation, une interface avec des milliers d'éléments commence à ralentir. La spécification Custom Elements exige d'hériter de HTMLElement, ce qui crée inévitablement un nœud DOM même pour les éléments invisibles.
// Lit : chaque todo-item est un HTMLElement (tas C++, ~124 octets minimum)
@customElement("todo-item")
export class TodoItem extends LitElement { ... }
// <todo-item> alloue immédiatement un nœud DOM sur createElement
// Symbiote : de même, chaque <todo-item> = HTMLElement
class TodoItem extends Symbiote { ... }
TodoItem.reg('todo-item');
// $mol : un composant est un objet JS. Le DOM est créé UNIQUEMENT lors du rendu.
// 1 000 tâches dans le modèle ≠ 1 000 éléments DOM
// $mol rend uniquement les composants visibles (virtualisation)
task_rows() {
return this.task_ids_filtered().map(id => this.Task_row(id))
}
L'auteur de SolidJS confirme : pour une performance maximale, les Web Components ne sont pas adaptés car minimiser les nœuds DOM est impossible.
Perte de performance due aux opérations DOM
Les opérations DOM sont plusieurs fois plus lentes que les propriétés JS en raison des liaisons C++ et du manque d'optimisation JIT :
| Opération | Temps | Combien de fois pire |
|----------------------------|------------|----------------------|
| obj.title = x (JS) | 1–2 ns | 1x |
| element.textContent = x | 30–60 ns | 30x |
| element.setAttribute | 50–100 ns | 50x |
| element.style.color = x | 80–150 ns | 80x |
Lors de mises à jour massives (par exemple, sélectionner toutes les tâches dans TodoMVC), la performance se dégrade :
| Tâches | Lit | $mol | Différence |
|-----------|-----------|---------|------------|
| 100 | 60 µs | 3 µs | 20x |
| 1 000 | 600 µs | 8 µs | 75x |
| 10 000 | 8 ms | 12 µs | 650x |
| 100 000 | 120 ms | 15 µs | 8000x |
// Lit : mise à jour via propriété DOM
render() {
return html`<label> ${this.text} </label>`;
// Lit en interne fait : node.textContent = value
}
// $mol : mise à jour via propriété JS
// this.title() — lecture depuis le cache mémo (tas JS)
// Les mises à jour DOM sont groupées via requestAnimationFrame
task_title(id, next?) {
return this.task(id, ...)!.title ?? ''
}
L'analyse HTML dans Lit ajoute une surcharge.
Différences en réactivité
Les Web Components utilisent une sémantique push : les événements sont diffusés à tous les abonnés à chaque changement.
// Lit : Push via EventTarget + requestUpdate
export class Todos extends EventTarget {
#notifyChange() {
this.dispatchEvent(new Event("change")); // push !
}
add(text) {
this.#todos.push({ ... });
this.#notifyChange(); // ← push manuel
}
}
// Chaque composant s'abonne via @updateOnEvent("change")
// À CHAQUE changement, TOUS les abonnés reçoivent une notification
// $mol : Pull — graphe de dépendances automatique
@$mol_mem
groups_completed() {
// Suit automatiquement les dépendances :
// task_ids() → task() → groups
// Recalcule UNIQUEMENT quand les dépendances changent
for (let id of this.task_ids()) {
var task = this.task(id)!;
groups[String(task.completed)].push(id);
}
return groups;
}
// Pas besoin de EventTarget ou d'abonnements manuels
La réactivité pull ($mol, Vue) minimise les mises à jour inutiles et réduit le volume de code. Le push augmente le risque d'erreurs d'abonnement.
Tests et héritage
Tester les Web Components nécessite le DOM : créer des éléments, connectedCallback, attendre updateComplete. Dans $mol, les tests fonctionnent avec des objets JS sans DOM.
// $mol : tests SANS DOM
'task add'($) {
const app = $hyoo_todomvc.make({ $ })
app.Add().value('test')
app.Add().submit()
$mol_assert_equal(app.task_rows().at(-1)!.title(), 'test')
}
L'héritage dans Lit nécessite de copier tout le bloc render() :
class MyTodoApp extends TodoApp {
render() {
// Copier 40+ lignes de HTML, en changeant seulement le pied de page
}
}
Dans $mol, l'héritage remplace uniquement les sous-éléments nécessaires :
$my_custom_todo $hyoo_todomvc
foot <= My_footer $mol_view
// Tout le reste est hérité
Points clés à retenir
- Les Web Components dépensent ~124 octets/élément sur le DOM, ce qui est critique pour les listes.
- Les opérations DOM sont 30 à 80 fois plus lentes que les propriétés JS, se dégradant à grande échelle.
- La réactivité push nécessite des abonnements manuels ; le pull est plus simple et efficace.
- Les tests de Web Components sont lents à cause du DOM ; les tests $mol utilisent des objets purs.
- L'héritage dans les Web Components conduit à du copier-coller de templates.
— Editorial Team
Aucun commentaire pour le moment.