Symbiote.js: Composición avanzada de componentes web sin Virtual DOM
Symbiote.js es una biblioteca ligera (~6 KB Brotli) para crear componentes web, centrada en la composición avanzada mediante marcado HTML. Resuelve el problema de la organización estructural de las interfaces al eliminar la dependencia del Virtual DOM y ofrecer mecanismos flexibles para trabajar con contextos de datos. A diferencia de los frameworks tradicionales, Symbiote.js trabaja directamente con la DOM API, minimizando el código boilerplate y permitiendo combinar componentes a través de marcado declarativo.
Conceptos básicos de la composición estructural
El desarrollo de interfaces consta de dos aspectos: el modelo lógico (componentes, datos) y la composición estructural (conexiones entre componentes, flujo de datos). Symbiote.js ofrece un enfoque único para este último, utilizando HTML como herramienta de primera clase para definir dependencias. Principios clave:
- Las plantillas de componentes son cadenas HTML independientes
- El marcado externo define las conexiones estructurales
- Sin vinculación al runtime de JS
- Soporte para enfoques híbridos (renderizado en servidor/cliente)
Esto permite dar vida a marcado pregenerado, crear componentes «tontos» sin lógica propia o usar proveedores de contexto. Un ejemplo es implementar pestañas usando dos componentes enlazados:
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');
El segundo componente se encarga de mostrar el contenido:
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');
Características técnicas clave
El ejemplo demuestra cinco mecanismos críticos:
- rootStyles — los estilos se aplican correctamente independientemente de la presencia de Shadow DOM en elementos padres. Esto garantiza compatibilidad con Tailwind, Bootstrap y estilos globales.
- renderCallback — ciclo de vida con acceso a nodos hijos (a diferencia del connectedCallback estándar).
- init$ — inicialización de propiedades reactivas con valores predeterminados.
- sub() — suscripción a cambios de propiedades mediante Pub/Sub sin Virtual DOM.
- *Propiedades mediante (asterisco)** — declaración de contexto compartido (similar al atributo name en input[type=radio]).
La característica arquitectónica es el trabajo directo con la DOM API sin abstracciones. No se trata de un antipatrón, sino de una elección deliberada que elimina el sobrecoste de la representación virtual. Al mismo tiempo, la biblioteca soporta tipado, reactividad y depuración en runtime.
Paradigma low-code en acción
La ventaja de Symbiote.js brilla en escenarios donde las interfaces se ensamblan mediante atributos HTML sin JavaScript. Considera un componente para mostrar la pestaña actual:
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');
Uso:
<super-current ctx="section-select"></super-current>
Este enfoque abre posibilidades para:
- Diseñadores: prototipar interfaces directamente en HTML
- Gestores de contenido: personalizar la visualización mediante atributos sin código
- Soluciones embebidas: configurar widgets a través de marcado
- Sistemas de IA: generar interfaces con marcado y conexiones de comportamiento predecibles
Cuanto más componentes haya en la biblioteca, más fácil será ensamblar interfaces complejas mediante HTML. Cada grupo de componentes se aísla mediante el atributo ctx, permitiendo múltiples instancias independientes en la página.
Lo que importa
- Cero dependencia de bundlers: los componentes funcionan sin Webpack/Vite
- Contexto mediante DOM: las conexiones entre componentes se definen por la estructura del marcado
- Tamaño de 6 KB: impacto mínimo en el rendimiento
- Compatibilidad con cualquier stack: integración en proyectos existentes sin migración
- Tipos de fábrica: soporte para TypeScript sin configuración extra
Symbiote.js no reemplaza frameworks, sino que los complementa en el ámbito de la composición estructural. Para tareas lógicas, proporciona una API minimalista, centrada en lo que otras soluciones implementan mediante bibliotecas de terceros (gestión de estado, enrutamiento). Esto reduce la carga cognitiva al diseñar interfaces, especialmente en equipos diversos.
— Editorial Team
Aún no hay comentarios.