Closures en JavaScript: construyendo un sistema de plugins sencillo

Los closures son una de esas características de JavaScript que, bien aprovechadas, nos permiten diseñar APIs limpias y extensibles. En este artículo vamos a ver cómo utilizarlos para montar un sistema de plugins ligero que cualquier proyecto frontend puede integrar sin depender de librerías externas.
¿Por qué un motor de plugins?
Un plugin es simplemente una pieza de código que se conecta a una aplicación para añadir o modificar funcionalidades. Cuando la arquitectura está bien pensada, los plugins pueden:
- Encapsular su propio estado.
- Exponer una interfaz clara para la aplicación anfitriona.
- Ser añadidos o eliminados en tiempo de ejecución.
Todo esto es posible gracias a los closures: cada plugin puede cerrar sobre sus variables internas y, al mismo tiempo, devolver una función que la aplicación pueda invocar.
La base: una fábrica de plugins
Empezamos definiendo una createPlugin que recibe una configuración y devuelve una función que, cuando se registra, recibe el core de la aplicación.
function createPlugin(options) {
// Variables privadas del plugin
const name = options.name || 'plugin';
let counter = 0;
// La función que la app llamará para registrar el plugin
return function register(core) {
// El closure permite que "name" y "counter" persistan
core.register(name, () => {
counter++;
console.log(`[${name}] llamada ${counter} veces`);
});
};
}
Observa que name y counter nunca salen del plugin; sólo la función register las captura. La aplicación no necesita saber nada de su implementación interna.
El núcleo de la aplicación
Para que el ejemplo sea completo, implementemos un core muy sencillo que gestiona los plugins registrados:
function createCore() {
const registry = {};
return {
register(pluginName, fn) {
if (registry[pluginName]) {
console.warn(`Plugin "${pluginName}" ya está registrado`);
return;
}
registry[pluginName] = fn;
console.log(`Plugin "${pluginName}" registrado`);
},
run(pluginName) {
const fn = registry[pluginName];
if (!fn) {
console.error(`Plugin "${pluginName}" no encontrado`);
return;
}
fn();
},
list() {
return Object.keys(registry);
}
};
}
El core expone tres métodos: register para añadir plugins, run para ejecutarlos y list para inspeccionar qué está disponible.
Uso práctico
Con la fábrica y el núcleo listos, veamos cómo montar todo:
// Creamos el core de la app
const app = createCore();
// Definimos dos plugins con distinta lógica
const loggerPlugin = createPlugin({ name: 'logger' });
const analyticsPlugin = createPlugin({ name: 'analytics' });
// Registramos los plugins
loggerPlugin(app);
analyticsPlugin(app);
// Probamos su ejecución
app.run('logger'); // [logger] llamada 1 veces
app.run('logger'); // [logger] llamada 2 veces
app.run('analytics'); // [analytics] llamada 1 veces
console.log('Plugins activos:', app.list()); // Plugins activos: [ 'logger', 'analytics' ]
Gracias al closure, cada plugin mantiene su propio contador counter sin interferir con los demás. Si quisieras añadir configuración extra (por ejemplo, una URL de endpoint), simplemente la pasas a createPlugin y la guardas en una variable privada.
Ventajas de este enfoque
- Encapsulamiento total: el estado interno está protegido contra accesos externos.
- Flexibilidad: puedes registrar o retirar plugins en cualquier momento.
- Reusabilidad: la fábrica
createPluginpuede reutilizarse para cientos de casos diferentes. - Sin dependencias: todo el código es puro JavaScript, ideal para proyectos que buscan ligereza.
Extensiones habituales
Una vez tienes la base, puedes añadir funcionalidades como:
- Hooks de ciclo de vida: permitir que el plugin tenga
initydestroy. - Prioridad de ejecución: ordenar los plugins según un número de orden.
- Comunicación entre plugins: exponer un
eventBusdentro delcorepara que los plugins se suscriban a eventos.
En todos esos casos, el patrón sigue siendo el mismo: la función devuelta cierra sobre sus variables y se comunica con el núcleo mediante una API pública y bien definida.
Conclusión
Los closures no son sólo una curiosidad teórica; son la herramienta que nos permite crear módulos de código aislados, con estado propio y una interfaz mínima. Con la pequeña fábrica que hemos visto, puedes montar rápidamente un motor de plugins que escale desde una simple página estática hasta una SPA compleja.
La próxima vez que necesites añadir funcionalidades extensibles a tu proyecto, piensa en los closures como la base de tu arquitectura. ¡A probarlo y a compartir tus propios plugins!