Adapter de Vanilla JS
El adapter de Vanilla es para JavaScript plano: sin framework, sin necesidad de
paso de build, sin runtime que cargar. Además es el único adapter construido
sobre el contrato compartido createWuAdapter(), lo que lo convierte en el
mejor ejemplo práctico si vas a escribir un adapter propio.
Pese al nombre, tiene la mayor superficie de helpers de los adapters ligeros y —a diferencia de Alpine, HTMX y Stimulus— soporta props vivas.
npm install wu-frameworkregister(appName, config, options)
Sección titulada «register(appName, config, options)»El punto de entrada principal. config describe la app; options es la bolsa
de opciones habitual de Wu.
import { wuVanilla } from 'wu-framework/adapters/vanilla';
wuVanilla.register('counter', { state: { count: 0 }, init: (container, state) => { console.log('Inicializando…', state); }, render: (container, state) => { container.innerHTML = ` <div> <h1>Cuenta: ${state.count}</h1> <button id="increment">+</button> </div> `; container.querySelector('#increment').onclick = () => { state.count++; // vuelve a renderizar como prefieras — o deja que wu.update() lo dirija }; }, destroy: (container, state) => { container.innerHTML = ''; },});Clave de config |
Tipo | Obligatoria | Firma |
|---|---|---|---|
render |
function |
sí | (container, state) => void |
init |
function |
no | (container, state) => void, antes del primer render |
destroy |
function |
no | (container, state) => void; por defecto vacía el contenedor |
state |
object |
no | Estado inicial, fusionado debajo de las props |
Opciones: las estándar props, standalone, standaloneContainer (por defecto
#app), onMount y onUnmount.
render es obligatorio; omitirlo lanza:
[WuVanilla] render function is required for <appName>El estado es config.state fusionado con las props
Sección titulada «El estado es config.state fusionado con las props»Al montar, el adapter calcula { ...config.state, ...props }. Las props de
registro y las que envía el shell aterrizan en el mismo objeto que recibe tu
render. Eso es lo que hace que las props vivas funcionen sin un segundo
concepto.
Props vivas
Sección titulada «Props vivas»await wu.update('counter', { count: 42 });El slot update fusiona las props nuevas en el estado guardado y vuelve a
llamar a render(container, state) sobre el mismo contenedor: en el sitio, sin
desmontar. Como render controla por completo el contenido del contenedor, esto
es lo más cerca de un re-render que llega el JavaScript plano.
registerClass(appName, AppClass, options)
Sección titulada «registerClass(appName, AppClass, options)»Para apps organizadas como una clase con constructor(container) y métodos
render() / destroy():
class TodoApp { constructor(container) { this.container = container; this.todos = []; }
render() { this.container.innerHTML = ` <ul>${this.todos.map((t) => `<li>${t}</li>`).join('')}</ul> `; }
addTodo(text) { this.todos.push(text); this.render(); }
destroy() { this.container.innerHTML = ''; this.todos = []; }}
wuVanilla.registerClass('todo-app', TodoApp);El adapter construye una instancia por montaje y llama a render(); al
desmontar llama a destroy() si existe y luego vacía el contenedor.
registerTemplate(appName, template, options)
Sección titulada «registerTemplate(appName, template, options)»Para HTML estático o dirigido por datos sin comportamiento propio:
// EstáticowuVanilla.registerTemplate('header', '<header><h1>Mi cabecera</h1></header>');
// DinámicowuVanilla.registerTemplate('greeting', (data) => `<h1>¡Hola ${data.name}!</h1>`, { data: { name: 'Mundo' }, styles: ['h1 { color: rebeccapurple; }'], scripts: [],});template es un string de HTML, o una función que recibe data.
| Opción | Tipo | Por defecto | Notas |
|---|---|---|---|
data |
object |
{} |
Se pasa a template cuando es una función. |
styles |
string[] |
[] |
Se unen en un único <style data-wu-app="<appName>"> antepuesto al contenedor. |
scripts |
string[] |
[] |
Fuentes de script inyectadas tras el markup. |
standalone |
boolean |
true |
Plan B standalone. |
standaloneContainer |
string |
'#app' |
Selector usado en modo standalone. |
onMount / onUnmount |
function |
null |
Callbacks de ciclo de vida. |
Como los estilos van dentro del contenedor, aterrizan dentro del Shadow root cuando Wu está aislando la app, que es exactamente donde los quieres.
createComponent(config)
Sección titulada «createComponent(config)»Un componente en miniatura de estado y acciones, pensado para pasárselo
directamente a register():
import { wuVanilla, createComponent } from 'wu-framework/adapters/vanilla';
const Counter = createComponent({ state: { count: 0 }, template: (state) => ` <div> <h1>${state.count}</h1> <button data-action="increment">+</button> <button data-action="decrement">−</button> </div> `, actions: { increment: (state) => ({ count: state.count + 1 }), decrement: (state) => ({ count: state.count - 1 }), }, onInit: (container, state) => console.log('init', state), onDestroy: (container, state) => console.log('adiós'),});
wuVanilla.register('counter', Counter);El objeto devuelto ya tiene la forma { state, init, render, destroy } que
espera register(), más setState(partial) y getState() para uso imperativo.
Cómo funciona:
template(state)produce el HTML;renderlo escribe y después enlaza un listener declicka cada elemento[data-action].- Una acción recibe
(state, element). Devolver un objeto llama asetState()con él; no devolver nada significa “sin cambio de estado” y se salta el re-render. - El foco se conserva entre re-renders cuando el elemento enfocado tiene un
id: el adapter guardadocument.activeElement.idy vuelve a enfocar el elemento correspondiente tras escribir el HTML nuevo. Sin esto, escribir en un input dentro de un componente condata-actionperdería el cursor en cada pulsación.
createWuSlot(target, props)
Sección titulada «createWuSlot(target, props)»Lado del shell, imperativo: sin JSX, sin compilador de templates.
import { createWuSlot } from 'wu-framework/adapters/vanilla';
const slot = createWuSlot(document.querySelector('#main'), { name: 'catalog', url: 'http://localhost:3001', fallbackText: 'Cargando catálogo…', onLoad: ({ name }) => console.log(name, 'listo'), onError: (err) => console.error(err),});Crea un div.wu-slot con data-wu-app / data-wu-url, lo añade a target,
pinta un estado de carga y monta la micro-app en un contenedor interno con id
único.
El texto del fallback pasa por escapeHtml() antes de llegar a innerHTML:
este slot renderiza los mensajes de error de la misma forma, y esos mensajes
pueden originarse en un módulo remoto. Consulta
Escribir un adapter.
useWuEvents() y useWuStore(namespace)
Sección titulada «useWuEvents() y useWuStore(namespace)»Funciones simples, sin ciclo de vida de framework:
import { useWuEvents, useWuStore } from 'wu-framework/adapters/vanilla';
const { emit, on, once, off, cleanup } = useWuEvents();on('user:login', (e) => console.log(e.data));
const { state, setState, getState, cleanup: storeCleanup } = useWuStore('user');setState('name', 'Ada'); // escribe en user.nameuseWuAI(options)
Sección titulada «useWuAI(options)»import { useWuAI } from 'wu-framework/adapters/vanilla';
const ai = useWuAI({ namespace: 'support' });
await ai.send('Muéstrame los pedidos pendientes');
console.log(ai.getMessages()); // copia del arrayconsole.log(ai.isStreaming()); // booleanconsole.log(ai.getError()); // string | nullai.clear();Sin streaming: envuelve wu.ai.send(). Los getters son métodos, no
propiedades, porque no hay sistema de reactividad que haga significativos los
valores vivos. Consulta Visión general de IA.
Construido sobre createWuAdapter()
Sección titulada «Construido sobre createWuAdapter()»register() es el único punto de entrada de adapter en el código base que
delega en el contrato compartido:
const registerFn = createWuAdapter({ name: 'WuVanilla', mount(container, { props }) { /* … */ return { container, state }; }, unmount(container) { /* … */ }, update(container, { props }) { /* … */ },});
return registerFn(appName, config, { standaloneContainer: '#app', ...rest });Todo lo demás —waitForWu, wu.define, la contabilidad por app, el plan B
standalone, el slot condicional update— viene de createWuAdapter(). Si vas a
escribir un adapter para un framework que Wu no incluye, lee este archivo junto
con Escribir un adapter.
Exports
Sección titulada «Exports»import { wuVanilla, // export por defecto register, registerClass, registerTemplate, createComponent, createWuSlot, useWuEvents, useWuStore, useWuAI, getWuInstance, waitForWu,} from 'wu-framework/adapters/vanilla';Desde el barrel: registerVanilla, createVanillaWuSlot,
useVanillaWuEvents, useVanillaWuStore, useVanillaWuAI.
