Ir al contenido

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 no
Hidratación SSR no no
Contenedor standalone #root #app

Ventana de terminal
npm install wu-framework lit
src/main.js
import { 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);
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),
});

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-cardwu-user-card)
  • si no → wu-app-<appName> (p. ej. headerwu-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.

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.

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.

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.

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)).

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.

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.

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.

import {
wuLit, // export por defecto
register,
registerWebComponent,
createWuSlotElement,
WuMixin,
WuAIMixin,
wuProperty,
createSimpleElement,
getWuInstance,
waitForWu,
} from 'wu-framework/adapters/lit';

Desde el barrel: registerLit.


Ventana de terminal
npm install wu-framework

Stencil 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');
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>

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.

import {
wuStencil, // export por defecto
register,
getWuInstance,
waitForWu,
} from 'wu-framework/adapters/stencil';

Desde el barrel: registerStencil.