Anti-patrones

Estos son errores que parecen razonables, compilan bien, y causan bugs reales difíciles de diagnosticar después. Lee esta página antes de poner en producción un Plugster que pretendas mantener — la mayoría de estos tiene al menos una entrada en la historia de algún codebase real, y cada uno tiende a aparecer como "el framework está roto" cuando la causa es un contrato violado.

Cada entrada sigue la misma forma: el error, por qué muerde, y la forma correcta de hacerlo. Las secciones se agrupan por el área que tocan — outlets, ciclo de vida, eventos, outlets de lista, localización, frontera del framework.

Acceso a outlets #

Llamar a $() o document.querySelector dentro de un método de Plugster

Salta el enlazado de outlets, acopla el controlador a selectores CSS, y rompe tests que renderizan HTML mínimo. El contrato del framework es que todo el acceso al DOM va por this._.<outletName>. Si una pieza de HTML le importa a un controlador, dale un data-outlet-id y alcánzalo vía this._; si no le importa, el controlador no debería estar alcanzándola.

Usar APIs de mutación cruda del DOM (innerHTML, createElement, appendChild)

Escribir directamente element.innerHTML = ... o agregar nodos salta el enlazado de outlets por completo. El contenido inyectado así es invisible al sistema de outlets y no puede ser gestionado por la API de outlet de lista. Para ítems repetibles usa buildListItem; para regiones de un solo slot, asigna fragmentos renderizados por jQuery a través del handle de outlet existente.

Hardcodear nombres de outlets como selectores DOM en callbacks de plantillas hijas

Dentro de callbacks de buildListItem, usar $('[data-child-outlet-id="chipText"]') en lugar del handle de outlets scopeado devuelto por buildListItem. El handle scopeado (outlets.chipText) direcciona solo el elemento de la fila actual; el enfoque del selector direcciona cada elemento coincidente en todo el documento y los muta a todos silenciosamente.

Ciclo de vida #

Cablear eventos de outlets fuera de afterInit()

Los outlets no están enlazados hasta que init() completa. Cablear en el constructor adjunta silenciosamente handlers a stubs vacíos — nada lanza excepción, pero nada dispara tampoco. afterInit() es el único hook seguro.

Llamar a Plugster.plug() antes de await instance.init()

Registrar un Plugster no inicializado significa que los outlets no están enlazados cuando las suscripciones HTML de otros Plugsters tratan de cablear contra este. Los eventos que disparen inmediatamente manipularán outlets no enlazados. El patrón de arranque existe específicamente para forzar el ordenamiento: await new MyPlugster(...).init(), luego Plugster.plug(...).

Olvidar Plugster.plug(instance) después de init()

La instancia nunca se registra. Los eventos encolados que la apuntan nunca se vacían; las suscripciones declaradas en HTML donde es publicador o suscriptor nunca se cablean. El Plugster existe efectivamente en aislamiento — sus propios outlets funcionan, pero nada más en la página puede hablarle.

Omitir Plugster.unplug(instance) al remover un Plugster de la página

Las suscripciones sobreviven a la remoción del DOM. Los handlers de la instancia muerta siguen disparando durante interacciones subsiguientes en la página, produciendo comportamiento fantasma. Para Plugsters que viven toda la página esto es irrelevante; para cualquier cosa montada dinámicamente (cambios de ruta, salida de fábrica, ciclos de modal) es obligatorio.

Llamar a unplug(instance) sin { destroy: true } cuando la clase define destroy()

Las suscripciones se desadjuntan limpiamente pero los recursos que destroy() liberaría (timers, observers, disposers de MobX, sockets abiertos) siguen corriendo. El framework deliberadamente nunca llama a destroy() automáticamente — agregar un método destroy no debería cambiar silenciosamente el comportamiento de desmontaje para llamadores que no saben que existe. Si publicas un Plugster con un método destroy, documenta que los consumidores deben pasar { destroy: true } al hacer unplug.

Eventos #

Llamar métodos directamente entre instancias de Plugster

Escribir headerPlugster.save() desde otro controlador acopla los dos componentes y rompe el modelo de comunicación del framework. Toda la comunicación entre Plugsters debe ir por dispatchEvent / suscripciones — ese es el único contrato que Plugster ofrece entre instancias. Si dos Plugsters necesitan coordinarse, declara un evento en el publicador, suscríbete en el listener.

Despachar eventos con formas de payload ad-hoc

this.dispatchEvent('save', { x: 1 }) donde x no era parte de la firma de evento declarada. Los suscriptores reciben campos de payload en los que no pueden confiar, y el contrato deriva silenciosamente a medida que el código evoluciona. Cada campo del payload debería aparecer en una declaración registerEventSignature del lado publicador; si el payload necesita cambiar, actualiza la declaración primero.

Outlets de lista #

Usar outlets data-child-templates para contenido no-lista

La API de plantilla hija es para ítems repetibles con clave. Para paneles de un solo slot — un panel de detalle, un sidebar contextual — usa un outlet regular y asigna contenido en él vía la API estándar de jQuery. La maquinaria de listas (claves, índices, buildListItem) es overhead en ese caso.

Leer ítems vía .getItems() cuando el orden DOM importa

getItems() devuelve un diccionario plano. La iteración de diccionarios de JavaScript no garantiza coincidir con el orden en que los ítems aparecen en el DOM. Si tu código se preocupa por el orden de la lista (exportar ítems en orden, computar vecinos, comparar contra otra colección ordenada), usa getItemsAsArray() en su lugar — devuelve ítems ordenados por su index rastreado, que coincide con el DOM renderizado.

Localización #

Hardcodear strings de display en JavaScript

Escribir this._.saveButton.text('Save') en lugar de this._.saveButton.text(this.translateTo(this.lang, 'Save')). La página renderiza correctamente en inglés pero nunca se adapta cuando el idioma activo es otro. Trata cada string literal que termine en el DOM en tiempo de ejecución como candidato de localización.

Frontera del framework #

Usar un framework de componentes (React, Vue, Svelte, Angular) dentro de un Plugster

Estos frameworks tienen su propio ciclo de vida del DOM y entran en conflicto con el enlazado de outlets y el modelo pub/sub de Plugster. Los dos no pueden compartir la propiedad de renderizado de la misma región limpiamente. Si necesitas una pieza de UI que un framework de componentes hace fácil y Plugster hace difícil, escribe esa pieza fuera de cualquier Plugster — la página puede albergar ambos — pero no anides uno dentro del otro.

Usar atributos globales de auto-init de Flowbite dentro de Plugsters cargados dinámicamente

El auto-init global de Flowbite corre una vez al cargar la página y no vuelve a correr para contenido dinámico. Si un Plugster montado en tiempo de ejecución lleva atributos Flowbite, esos widgets nunca se inicializarán. O bien inicializa componentes Flowbite explícitamente dentro de afterInit() o evita por completo el patrón de auto-init global.

Gestión de estado #

Compartir estado mutable entre instancias de Plugster vía globales

Guardar datos compartidos en window.* o variables exportadas de módulo crea acoplamiento oculto y rompe la aislación de tests. El estado entre instancias debe fluir a través de eventos. Si dos Plugsters genuinamente necesitan coordinarse sobre una pieza de estado, modela ese flujo explícitamente con eventos declarados; el acoplamiento resultante es al menos visible.

Mutar outlets para contenido manejado por estado fuera de _render() (patrón MobX)

Al usar autorun de MobX para proyectar estado observable al DOM, actualiza outlets dentro de _render() únicamente. Actualizar outlets directamente desde handlers de eventos además de la proyección produce múltiples sitios de mutación para el mismo DOM, y el resultado renderizado deriva del estado observado. La integración Plugster + MobX se describe en su propia regla en coco — la mayoría de Plugsters no la necesitan.

Testing #

Omitir el reset del estado estático de Plugster entre tests

Plugster.registry, Plugster.explicitSubscriptions, Plugster.htmlDeclaredSubscriptions, Plugster.eventQueue, Plugster.childTemplateHtmlCache y Plugster.childTemplateRequestCache son todos estáticos. Sin resetearlos en beforeEach, los handlers y el HTML de plantilla cacheado se filtran entre tests, y el cableado de un test afecta el comportamiento del siguiente. Resetea los seis explícitamente.

        
            beforeEach(() => {
                Plugster.registry = undefined;
                Plugster.explicitSubscriptions = undefined;
                Plugster.htmlDeclaredSubscriptions = undefined;
                Plugster.eventQueue = undefined;
                Plugster.childTemplateHtmlCache = undefined;
                Plugster.childTemplateRequestCache = undefined;
            });
        
    
Content