Ciclo de vida

Cada instancia de Plugster se mueve por la misma secuencia fija: construcción, inicialización, registro y — opcionalmente — desmontaje. El framework garantiza un contrato exacto en cada paso; respetarlo es lo que mantiene el cableado de outlets determinista y previene los bugs de handlers fantasma que surgen cuando se omite una fase.

La secuencia #

        
            1. new MyPlugster(outlets, ...deps)
               → constructor: guarda dependencias, configura locales, declara firmas de eventos.
                 El DOM aún NO ha sido tocado.

            2. await instance.init()
               → el framework enlaza outlets, descarga y compila cualquier plantilla hija declarada,
                 luego invoca afterInit() una vez que todo está listo.

            3. Plugster.plug(instance)
               → agrega la instancia al registro global, cablea las suscripciones declaradas en HTML
                 (los atributos data-on-*), y vacía cualquier evento que haya sido despachado antes
                 de que la instancia existiera.

               ... el Plugster ahora está activo y manejando eventos ...

            4. Plugster.unplug(instance, options?)         (opcional, ver más abajo)
               → revierte el paso 3: remueve handlers, descarta eventos encolados dirigidos a la
                 instancia, borra entradas del registro. Si options.destroy es true, también llama
                 a instance.destroy().
        
    

El archivo de arranque de Conceptos básicos es exactamente esta secuencia, condensada en una IIFE async anónima:

        
            import {Plugster} from "https://cdn.jsdelivr.net/gh/paranoid-software/plugster@1.0.14/dist/plugster.min.js";
            import {ExchangeRatesPlugster} from "./exchange-rates-plugster.js";

            (async function () {
                let exchangeRatesPlugster = await new ExchangeRatesPlugster({
                    selectedCurrencyLabel: {},
                    ratesList: {}
                }).init();

                Plugster.plug(exchangeRatesPlugster);
            }());
        
    

Fase 1 — el constructor #

El constructor recibe el descriptor de outlets como su primer argumento; cualquier dependencia externa que tu Plugster necesite va después. El contrato para esta fase es estrecho: guardar referencias, declarar locales, declarar firmas de eventos y reenviar outlets a super. Nada más.

        
            constructor(outlets, exchangeRatesSvcs) {
                super(outlets);
                this.exchangeRatesSvcs = exchangeRatesSvcs;
                this.setLocales({ 'es': { 'Loading...': 'Cargando...' } });
            }
        
    

En este punto this._ existe pero sus outlets son stubs vacíos — el framework aún no ha recorrido el DOM. Tocar outlets aquí funciona en el sentido de que JavaScript no lanzará una excepción, pero los handlers que adjuntes quedarán atados a nada útil. Lo mismo aplica a leer this._.root.data('lang') o cualquier otro atributo de la vista: es demasiado temprano.

Fase 2 — init() y afterInit() #

init() es el método del framework que llamas desde el bloque de arranque. Devuelve una promesa porque puede necesitar descargar archivos HTML (las plantillas hijas declaradas vía data-child-templates). Internamente ejecuta tres pasos:

  1. Recorre el DOM de la vista y enlaza cada outlet declarado a this._.<outletName>.
  2. Descarga y compila cada plantilla hija, poblando la API de outlet de lista en los outlets relevantes.
  3. Llama a afterInit() en la instancia.

afterInit() es el ÚNICO lugar seguro para cablear outlets. Para cuando el framework lo llama, los outlets existen y están enlazados; antes de eso, no. La gran mayoría de bugs sutiles en Plugsters se rastrean a violar esta regla.

        
            afterInit() {
                let self = this;
                self._.emailInput.on('keyup', () => self._validate());
                self._.submitButton.on('click', () => self._handleSubmit());
                self._.statusLabel.text(self.translateTo(self.lang, 'Loading...'));
            }
        
    

Nota que afterInit() es plano — sin async, sin await. Si tu Plugster necesita hacer trabajo asincrónico como parte de la inicialización (una primera consulta de datos, por ejemplo), lánzalo dentro de afterInit() y deja que el resto del ciclo de vida continúe.

Fase 3 — Plugster.plug() #

Plugster.plug() es la llamada del framework de "registra esta instancia". Hace tres cosas:

  • Agrega la instancia al Plugster.registry bajo su nombre en minúsculas, y la duplica en window.plugsters.
  • Escanea la vista de cada otro Plugster registrado en busca de atributos data-on-{thisinstance}-* y cablea el método correspondiente en el publicador como un listener de eventos personalizados de jQuery. Este es el motor detrás de las suscripciones declaradas en HTML (ver Eventos).
  • Vacía cualquier evento encolado. Si otro Plugster despachó un evento antes de que este se registrara, el evento fue aparcado en Plugster.eventQueue; plug() lo entrega ahora.

El orden importa: await init() debe completarse antes de llamar a plug(). Registrar una instancia no inicializada es uno de los anti-patrones documentados del framework — el framework registrará un objeto cuyos outlets aún no están enlazados, y cualquier suscripción HTML que dependa de él disparará contra handles inexistentes.

Fase 4 — Plugster.unplug() (opcional) #

La mayoría de Plugsters viven tanto como la página, así que nunca llamas a unplug explícitamente. La fase existe para dos situaciones específicas: aplicaciones single-page de larga vida donde un Plugster se intercambia mientras el usuario navega, y Plugsters creados dinámicamente que se montan y desmontan en respuesta a eventos en otra parte de la página.

La firma acepta o la instancia o su nombre (insensible a mayúsculas):

        
            Plugster.unplug(myEditor, { reason: 'route-change' });
            Plugster.unplug('MyEditor');
        
    

Lo que hace unplug en orden:

  • Remueve cada handler de jQuery que el framework adjuntó a la instancia vía registerEventSignature.
  • Descarta cada evento encolado dirigido a la instancia antes de ser registrada.
  • Remueve cada suscripción explícita (configurada vía listenTo) donde la instancia es publicador o listener.
  • Remueve cada suscripción declarada en HTML donde la instancia es publicador o listener.
  • Borra la instancia de Plugster.registry y del espejo en window.plugsters.
  • Si options.destroy === true, llama a instance.destroy() al final (ver la siguiente sección).

unplug devuelve true en caso de éxito y false si el argumento no se puede resolver a un Plugster conocido. La opción reason es puramente informativa — termina en la entrada de log que unplug escribe, lo que hace más legibles los rastros de desmontaje en devtools.

Fase 5 — destroy() (limpieza opt-in) #

unplug solo sabe desmontar las cosas que el framework mismo creó — handlers de outlets, suscripciones, entradas del registro. Si tu Plugster envuelve recursos propios (un timer setInterval, un MutationObserver, un disposer de autorun de MobX, un WebSocket abierto), necesitas un hook para liberarlos también. Para eso existe destroy().

        
            class MyEditor extends Plugster {
                afterInit() {
                    this.timer = setInterval(() => this.tick(), 1000);
                    this.observer = new MutationObserver(() => this._reflow());
                    this.observer.observe(this._.root[0], { childList: true });
                }
                destroy() {
                    if (this.timer) clearInterval(this.timer);
                    if (this.observer) this.observer.disconnect();
                }
            }

            Plugster.unplug(myEditor, { destroy: true, reason: 'navigation' });
        
    

El framework nunca llama a destroy() automáticamente. Debes pasar { destroy: true } a unplug para que se dispare. La asimetría es deliberada: definir 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.

Errores comunes #

  • Cablear eventos en el constructor. Los outlets son stubs vacíos en ese punto; el cableado queda atado a nada. Muévelo a afterInit().
  • Llamar a Plugster.plug() antes de que await init() resuelva. El framework registra una instancia a medio inicializar. Siempre haz await a la promesa de init primero.
  • Olvidar Plugster.plug() por completo. Los outlets del Plugster funcionan en aislamiento, pero las suscripciones declaradas en HTML nunca se cablean — el framework nunca ve la instancia.
  • Remover un Plugster del DOM sin llamar a unplug. Las suscripciones y eventos encolados sobreviven al Plugster, causando que handlers de la instancia muerta disparen en interacciones posteriores.
  • Llamar a unplug(instance) sin { destroy: true } cuando la clase define un destroy(). Las suscripciones se desadjuntan limpiamente, pero timers, observers y otros recursos envueltos siguen corriendo.

Cada uno de estos tiene una entrada correspondiente en Anti-patrones con un tratamiento más largo.

Content