Warum Web Components bei Performance und Developer Experience hinterherhinken
Web Components wie Lit und Symbiote erstellen jedes Element als HTMLElement, was zu einer Allokation von ~124 Bytes auf dem C++-Heap führt. Das ist 8-mal mehr als die 16 Bytes für ein JS-Objekt in $mol. Bei 1.000 Aufgaben verbrauchen Lit/Symbiote ~124 KB nur für DOM-Knoten, während $mol ~16 KB nutzt. Bei 10.000 Aufgaben wächst der Unterschied auf 1,2 MB gegenüber 160 KB.
Ohne Virtualisierung beginnt eine Oberfläche mit Tausenden von Elementen zu ruckeln. Die Custom-Elements-Spezifikation erfordert die Vererbung von HTMLElement, was unweigerlich einen DOM-Knoten erzeugt, selbst für unsichtbare Elemente.
// Lit: Jedes todo-item ist ein HTMLElement (C++-Heap, mindestens ~124 Bytes)
@customElement("todo-item")
export class TodoItem extends LitElement { ... }
// <todo-item> allokiert sofort einen DOM-Knoten bei createElement
// Symbiote: Ähnlich, jedes <todo-item> = HTMLElement
class TodoItem extends Symbiote { ... }
TodoItem.reg('todo-item');
// $mol: Eine Komponente ist ein JS-Objekt. DOM wird NUR während des Renderings erstellt.
// 1.000 Aufgaben im Modell ≠ 1.000 DOM-Elemente
// $mol rendert nur sichtbare Komponenten (Virtualisierung)
task_rows() {
return this.task_ids_filtered().map(id => this.Task_row(id))
}
Der Autor von SolidJS bestätigt: Für maximale Performance sind Web Components ungeeignet, da die Minimierung von DOM-Knoten unmöglich ist.
Performanceverlust durch DOM-Operationen
DOM-Operationen sind um ein Vielfaches langsamer als JS-Eigenschaften aufgrund von C++-Bindings und fehlender JIT-Optimierung:
| Operation | Zeit | Wie viel schlechter |
|----------------------------|------------|---------------------|
| 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 |
Bei Massenupdates (z.B. Auswahl aller Aufgaben in TodoMVC) verschlechtert sich die Performance:
| Aufgaben | Lit | $mol | Unterschied |
|----------|-----------|---------|-------------|
| 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: Update über DOM-Eigenschaft
render() {
return html`<label> ${this.text} </label>`;
// Lit macht intern: node.textContent = value
}
// $mol: Update über JS-Eigenschaft
// this.title() — Lesen aus Memo-Cache (JS-Heap)
// DOM-Updates in Batches über requestAnimationFrame
task_title(id, next?) {
return this.task(id, ...)!.title ?? ''
}
HTML-Parsing in Lit fügt Overhead hinzu.
Unterschiede in der Reaktivität
Web Components nutzen Push-Semantik: Ereignisse werden bei jeder Änderung an alle Abonnenten gesendet.
// Lit: Push über EventTarget + requestUpdate
export class Todos extends EventTarget {
#notifyChange() {
this.dispatchEvent(new Event("change")); // Push!
}
add(text) {
this.#todos.push({ ... });
this.#notifyChange(); // ← manueller Push
}
}
// Jede Komponente abonniert via @updateOnEvent("change")
// Bei JEDER Änderung erhalten ALLE Abonnenten eine Benachrichtigung
// $mol: Pull — automatischer Abhängigkeitsgraph
@$mol_mem
groups_completed() {
// Verfolgt automatisch Abhängigkeiten:
// task_ids() → task() → groups
// Berechnet NUR neu, wenn Abhängigkeiten sich ändern
for (let id of this.task_ids()) {
var task = this.task(id)!;
groups[String(task.completed)].push(id);
}
return groups;
}
// Kein EventTarget oder manuelle Abonnements nötig
Pull-Reaktivität ($mol, Vue) minimiert unnötige Updates und reduziert Codevolumen. Push erhöht das Risiko von Abonnementfehlern.
Testing und Vererbung
Das Testen von Web Components erfordert DOM: Elemente erstellen, connectedCallback, Warten auf updateComplete. In $mol funktionieren Tests mit JS-Objekten ohne DOM.
// $mol: Tests OHNE 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')
}
Vererbung in Lit erfordert das Kopieren des gesamten render()-Blocks:
class MyTodoApp extends TodoApp {
render() {
// 40+ Zeilen HTML kopieren, nur die Fußzeile ändern
}
}
In $mol ersetzt Vererbung nur die notwendigen Sub-Elemente:
$my_custom_todo $hyoo_todomvc
foot <= My_footer $mol_view
// Alles andere wird vererbt
Wichtige Erkenntnisse
- Web Components verbrauchen ~124 Bytes/Element für DOM, was für Listen kritisch ist.
- DOM-Operationen sind 30–80-mal langsamer als JS-Eigenschaften, verschlechtern sich bei Skalierung.
- Push-Reaktivität erfordert manuelle Abonnements; Pull ist einfacher und effizienter.
- Web-Component-Tests sind langsam wegen DOM; $mol testet reine Objekte.
- Vererbung in Web Components führt zu Template-Copy-Pasting.
— Editorial Team
Noch keine Kommentare.