Adapters de Lit y Stencil
Ambos adapters acaban en el mismo sitio: un custom element dentro del
contenedor. Difieren en quién llama a customElements.define.
- Lit toma una clase. El adapter define la etiqueta (generando un nombre si
no le das uno), crea el elemento y le asigna propiedades. Anuncia
update, así que las props vivas funcionan. - Stencil toma un string con el nombre de la etiqueta. El compilador de Stencil ya definió el elemento; el adapter solo lo crea, le pasa propiedades/atributos y deja que el ciclo de vida nativo del navegador haga el resto.
| Lit | Stencil | |
|---|---|---|
| Subpath | wu-framework/adapters/lit |
wu-framework/adapters/stencil |
| 2.º argumento | ElementClass |
tagName (string) |
| Props vivas | sí | no |
| Hidratación SSR | no | no |
| Contenedor standalone | #root |
#app |
npm install wu-framework litimport { LitElement, html } from 'lit';import { wuLit } from 'wu-framework/adapters/lit';
class HeaderApp extends LitElement { render() { return html`<header><h1>Mi cabecera</h1></header>`; }}
wuLit.register('header', HeaderApp);register(appName, ElementClass, options)
Sección titulada «register(appName, ElementClass, options)»| Opción | Tipo | Por defecto | Notas |
|---|---|---|---|
tagName |
string |
autogenerado | Etiqueta del custom element. Ver la regla de nombres abajo. |
properties |
object |
{} |
Propiedades asignadas a la instancia del elemento tras crearlo (valores ricos, no atributos). |
standalone |
boolean |
true |
Plan B standalone. |
standaloneContainer |
string |
'#root' |
Selector usado en modo standalone. |
onMount |
function |
null |
(container, element) => void. |
onUnmount |
function |
null |
(container, element) => void, antes de eliminar el elemento. |
Devuelve Promise<boolean>.
wuLit.register('user-card', UserCard, { tagName: 'acme-user-card', properties: { userId: 42, theme: 'dark' }, onMount: (container, el) => console.log('montado', el.tagName),});Nombres de etiqueta
Sección titulada «Nombres de etiqueta»Los nombres de custom element deben contener un guion. Si no pasas tagName, el
adapter genera uno:
- el nombre de la app ya contiene
-→wu-<appName>(p. ej.user-card→wu-user-card) - si no →
wu-app-<appName>(p. ej.header→wu-app-header)
La etiqueta se define una sola vez; un segundo register() con la misma
etiqueta se salta customElements.define, porque redefinir lanza un error.
Propiedades inyectadas
Sección titulada «Propiedades inyectadas»Todo elemento montado recibe dos propiedades antes de ser añadido al DOM:
element.wuAppName = 'header';element.wuInstance = getWuInstance();Decláralas como propiedades de Lit si quieres que sean reactivas.
Props vivas
Sección titulada «Props vivas»await wu.update('user-card', { theme: 'light' });El adapter asigna los valores nuevos sobre el elemento vivo. El sistema de propiedades reactivas de Lit programa una actualización: sin remontar, sin perder el estado del elemento.
registerWebComponent(appName, ElementClass, options)
Sección titulada «registerWebComponent(appName, ElementClass, options)»Un alias de register(), para subclases simples de HTMLElement que no
extienden LitElement:
class MyWebComponent extends HTMLElement { connectedCallback() { this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = '<h1>¡Hola!</h1>'; }}
wuLit.registerWebComponent('my-component', MyWebComponent);Mismas opciones, mismo comportamiento. El nombre aparte existe para documentar la intención.
WuMixin(Base)
Sección titulada «WuMixin(Base)»Añade los métodos del bus de eventos y del store de Wu a cualquier clase de elemento, con seguimiento de suscripciones:
import { LitElement, html } from 'lit';import { WuMixin } from 'wu-framework/adapters/lit';
class Cart extends WuMixin(LitElement) { connectedCallback() { super.connectedCallback(); this.wuOn('user:login', (e) => { this.user = e.data; }); }
handleClick() { this.wuEmit('button:clicked', { id: this.id }); }
render() { return html`<button @click=${this.handleClick}>Añadir</button>`; }}Provee this.wu (la instancia), wuEmit, wuOn, wuOnce y helpers del store.
Las suscripciones hechas con wuOn se recogen en this._wuSubscriptions para
poder cerrarlas.
WuAIMixin(Base)
Sección titulada «WuAIMixin(Base)»class Assistant extends WuAIMixin(LitElement) { connectedCallback() { super.connectedCallback(); this.wuAISetNamespace('support'); }
async ask(text) { const res = await this.wuAISend(text); this.answer = res?.content; }}Provee this.wuAI (el objeto wu.ai o null), wuAISend(text, options) y
wuAISetNamespace(ns). Los mixins se componen: WuAIMixin(WuMixin(LitElement)).
wuProperty(storePath)
Sección titulada «wuProperty(storePath)»Un decorador de propiedad que enlaza un campo con una ruta del store de Wu en ambas direcciones:
import { LitElement, html } from 'lit';import { wuProperty } from 'wu-framework/adapters/lit';
class Greeting extends LitElement { @wuProperty('user.name') userName;
render() { return html`<p>Hola, ${this.userName}</p>`; }}Leer devuelve el valor cacheado. Escribir escribe en el store de Wu, no en
el campo; el campo se actualiza cuando el store notifica de vuelta. El decorador
engancha connectedCallback para leer el valor inicial y suscribirse (llamando
a requestUpdate() al cambiar) y disconnectedCallback para desuscribirse.
createWuSlotElement(LitElement, html, css)
Sección titulada «createWuSlotElement(LitElement, html, css)»Lado del shell. Le pasas los propios exports de Lit para que el adapter nunca resuelva una segunda copia de Lit:
import { LitElement, html, css } from 'lit';import { createWuSlotElement } from 'wu-framework/adapters/lit';
const WuSlotElement = createWuSlotElement(LitElement, html, css);customElements.define('wu-slot', WuSlotElement);render() { return html`<wu-slot name="header" url="http://localhost:3001"></wu-slot>`;}Propiedades: name, url, appName (atributo app-name), fallbackText
(atributo fallback-text), más el estado interno loading / error. css es
opcional: si lo omites, el elemento sale sin static styles.
createSimpleElement(config)
Sección titulada «createSimpleElement(config)»Un helper sin Lit para componentes triviales:
import { createSimpleElement } from 'wu-framework/adapters/lit';
const MyComponent = createSimpleElement({ name: 'my-component', template: '<h1>¡Hola!</h1>', // string o (element) => string styles: ':host { display: block; color: blue; }', shadow: true, // por defecto connectedCallback() { console.log('¡Conectado!'); },});Devuelve una subclase de HTMLElement. Cualquier clave extra de config se
trata como un callback de ciclo de vida y se adjunta a la clase.
Exports de Lit
Sección titulada «Exports de Lit»import { wuLit, // export por defecto register, registerWebComponent, createWuSlotElement, WuMixin, WuAIMixin, wuProperty, createSimpleElement, getWuInstance, waitForWu,} from 'wu-framework/adapters/lit';Desde el barrel: registerLit.
Stencil
Sección titulada «Stencil»npm install wu-frameworkStencil en sí es un compilador de tiempo de build; el adapter no tiene ninguna dependencia de él en tiempo de ejecución. Carga tu bundle de componentes compilados como prescriba el output target de Stencil y luego registra la etiqueta:
import { wuStencil } from 'wu-framework/adapters/stencil';import { defineCustomElements } from 'my-design-system/loader';
await defineCustomElements();
wuStencil.register('my-component', 'my-stencil-component');register(appName, tagName, options)
Sección titulada «register(appName, tagName, options)»| Opción | Tipo | Por defecto | Notas |
|---|---|---|---|
props |
object |
{} |
Se asignan como propiedades del elemento: objetos, arrays y funciones sobreviven. |
attributes |
object |
{} |
Se asignan con setAttribute() — convertidos a string. |
standalone |
boolean |
true |
Plan B standalone. |
standaloneContainer |
string |
'#app' |
Selector usado en modo standalone. |
onMount |
function |
null |
(container) => void. |
onUnmount |
function |
null |
(container) => void. |
wuStencil.register('user-profile', 'user-profile-card', { props: { user: { id: 42, name: 'Ada' }, onSelect: (id) => console.log(id) }, attributes: { theme: 'dark', 'aria-label': 'Perfil de usuario' }, onMount: (container) => console.log('componente Stencil conectado'),});tagName es obligatorio y debe ser un string; cualquier otra cosa lanza de
forma síncrona:
[WuStencil] tagName (custom element tag) is required for <appName>Sin props vivas
Sección titulada «Sin props vivas»El adapter de Stencil registra solo mount y unmount, así que wu.update()
no hace nada. Para actualizar un componente Stencil en ejecución, guarda una
referencia desde onMount y asigna propiedades directamente, o gobiérnalo desde
el store de Wu.
Al desmontar se elimina el elemento del DOM, lo que dispara el
disconnectedCallback de Stencil: la limpieza propia del componente se ejecuta
de forma nativa.
Exports de Stencil
Sección titulada «Exports de Stencil»import { wuStencil, // export por defecto register, getWuInstance, waitForWu,} from 'wu-framework/adapters/stencil';Desde el barrel: registerStencil.
