Symbiote.js: Erweiterte Komposition von Web Components ohne Virtual DOM
Symbiote.js ist eine leichte (~6 KB Brotli) Bibliothek zum Erstellen von Web Components, die sich auf erweiterte Komposition über HTML-Markup konzentriert. Sie löst das Problem der strukturellen Organisation von Benutzeroberflächen, indem sie die Abhängigkeit vom Virtual DOM eliminiert und flexible Mechanismen für die Arbeit mit Datenkontexten bietet. Im Gegensatz zu traditionellen Frameworks arbeitet Symbiote.js direkt mit der DOM API, minimiert Boilerplate und ermöglicht die Kombination von Components durch deklaratives Markup.
Grundlagen der strukturellen Komposition
Die Entwicklung von Benutzeroberflächen besteht aus zwei Aspekten: dem logischen Modell (Components, Daten) und der strukturellen Komposition (Verbindungen zwischen Components, Datenfluss). Symbiote.js bietet einen einzigartigen Ansatz für Letzteres, indem es HTML als erstklassiges Werkzeug zur Definition von Abhängigkeiten nutzt. Wichtige Prinzipien:
- Component-Vorlagen sind unabhängige HTML-Strings
- Externes Markup definiert strukturelle Verbindungen
- Keine Bindung an die JS-Runtime
- Unterstützung für hybride Ansätze (Server-/Client-Rendering)
Das ermöglicht es, vor-generiertes Markup zum Leben zu erwecken, „dumme“ Components ohne eigene Logik zu erstellen oder Context-Provider zu verwenden. Ein Beispiel ist die Implementierung von Tabs mit zwei verknüpften Components:
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');
Die zweite Component kümmert sich um die Anzeige des Inhalts:
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');
Wichtige technische Merkmale
Das Beispiel demonstriert fünf kritisch wichtige Mechanismen:
- rootStyles — Stile werden korrekt angewendet, unabhängig vom Vorhandensein von Shadow DOM in übergeordneten Elementen. Das gewährleistet Kompatibilität mit Tailwind, Bootstrap und globalen Stilen.
- renderCallback — Lebenszyklus mit Zugriff auf Kindknoten (im Gegensatz zum standardmäßigen connectedCallback).
- init$ — Initialisierung reaktiver Eigenschaften mit Standardwerten.
- sub() — Abonnement für Eigenschaftsänderungen über Pub/Sub ohne Virtual DOM.
- *Eigenschaften über (Stern)** — Deklarieren gemeinsamer Kontexte (ähnlich dem name-Attribut bei input[type=radio]).
Das Architekturmerkmal ist die direkte Arbeit mit der DOM API ohne Abstraktionen. Dies ist kein Anti-Pattern, sondern eine bewusste Entscheidung, die den Overhead der virtuellen Repräsentation eliminiert. Gleichzeitig unterstützt die Bibliothek Typisierung, Reaktivität und Runtime-Debugging.
Low-Code-Paradigma in Aktion
Der Vorteil von Symbiote.js kommt in Szenarien zum Tragen, in denen Benutzeroberflächen über HTML-Attribute ohne JavaScript zusammengesetzt werden. Stellen Sie sich eine Component zur Anzeige des aktuellen Tabs vor:
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');
Verwendung:
<super-current ctx="section-select"></super-current>
Dieser Ansatz eröffnet Möglichkeiten für:
- Designer: Prototyping von Benutzeroberflächen direkt in HTML
- Content-Manager: Anpassung der Anzeige über Attribute ohne Code
- Eingebettete Lösungen: Konfiguration von Widgets über Markup
- KI-Systeme: Generieren von Benutzeroberflächen mit vorhersagbarem Markup und Verhaltensverbindungen
Je mehr Components in der Bibliothek vorhanden sind, desto einfacher ist es, komplexe Benutzeroberflächen über HTML zusammenzusetzen. Jede Gruppe von Components ist über das ctx-Attribut isoliert, was mehrere unabhängige Instanzen auf der Seite ermöglicht.
Was zählt
- Keine Abhängigkeit von Bundlern: Components funktionieren ohne Webpack/Vite
- Kontext über DOM: Verbindungen zwischen Components werden durch Markup-Struktur definiert
- 6 KB Größe: minimale Auswirkungen auf die Performance
- Kompatibilität mit jedem Stack: Integration in bestehende Projekte ohne Migration
- Typen direkt verfügbar: TypeScript-Unterstützung ohne zusätzliche Einrichtung
Symbiote.js ersetzt keine Frameworks, sondern ergänzt sie im Bereich der strukturellen Komposition. Für logische Aufgaben bietet es eine minimalistische API, die sich auf das konzentriert, was andere Lösungen über Drittanbieter-Bibliotheken umsetzen (State Management, Routing). Das reduziert die kognitive Belastung beim Design von Benutzeroberflächen, insbesondere in Teams mit vielfältigen Mitgliedern.
— Editorial Team
Noch keine Kommentare.