Todo lo que necesita saber sobre los módulos de Node.js
Los Módulos de Node.js son pequeños fragmentos de código o podemos decir: “El módulo divide la base de código en pequeñas unidades, que se pueden usar en cualquier parte de la aplicación”. Otros lenguajes de programación también tienen su propio sistema de módulos como Java, Go y PHP; se llama Paquete; en Ruby es Unidad.
Node.js actualmente viene con 2 sistemas de módulos diferentes
- Commonjs (CJS)
- Módulos ECMAScript (módulos ESM o ES)
La necesidad de módulos:
- Tener una forma de dividir el código base en varios archivos.
- Permitir la reutilización de código en diferentes proyectos.
- Encapsulación (u ocultación de información). Por lo general, es una buena idea ocultar la complejidad de la implementación y solo exponer interfaces simples con responsabilidades claras.
- Gestión de dependencias. Un buen sistema de módulos debería facilitar a los desarrolladores de módulos la creación de módulos existentes, incluidos los de terceros.
Podemos analizar más los 2 tipos de módulos en Node.js, pero por ahora, veamos cómo podemos usar el sistema de módulos.
Hay 2 conceptos principales del sistema de módulos:
- “requerir” es una función que le permite importar un módulo desde el sistema de archivos local
- “exportaciones” y “módulo.exportaciones” son variables especiales que se pueden usar para exportar la funcionalidad pública del módulo actual
Nota: en este punto, no estamos comparando los 2 módulos. Solo tratamos de entender las cosas básicas.
Hay algunos patrones populares para definir los módulos y exportarlos. Al exportar un módulo, está disponible en toda la aplicación.
Echemos un vistazo a estos patrones:
Exportaciones con nombre
El concepto más básico es exponer una API pública mediante la exportación de nombres. Echemos un vistazo al siguiente código.
exports.info = (message) => { console.log(`info: ${message}`) } exports.verbose = (message) => { console.log(`verbose: ${message}`) }
Aquí podemos ver que estamos exportando una función dándole un nombre como info y verbose. La función exportada ya está disponible. Podemos usarlos como:
const logger = require(‘./logger’) logger.info(‘This is an informational message’) logger.verbose(‘This is a verbose message’)
Este método nos permite exportar todo desde el módulo. Hace que la API sea pública para que todos puedan usar todos los métodos. Entonces podemos concluir, debe usarse cuando queremos que todos los métodos sean públicos. Ahora pasemos al siguiente patrón.
Exportando una función
Uno de los patrones de definición de módulos más populares consiste en reasignar toda la variable module.exports a una función. La principal fortaleza de este patrón es el hecho de que le permite exponer solo una funcionalidad, lo que proporciona un punto de entrada claro para el módulo, lo que lo hace más fácil de entender y usar; también respeta muy bien el principio de una pequeña superficie. Esta forma de definir módulos también se conoce en la comunidad como patrón de subpila.
module.exports = (message) => { console.log(`info: ${message}`) }
Esta es una combinación muy poderosa porque todavía le da al módulo la claridad de un único punto de entrada (la principal función exportada) y al mismo tiempo nos permite exponer otras funcionalidades que tienen casos de uso secundarios o más avanzados.
module.exports.verbose = (message) => { console.log(`verbose: ${message}`) }
Este código demuestra cómo usar el módulo que acabamos de definir:
const logger = require('./logger') logger('This is an informational message') logger.verbose('This is a verbose message')
Exportando una Clase
Un módulo que exporta una clase es una especialización de un módulo que exporta una función. Un módulo que exporta una clase es una especialización de un módulo que exporta una función.
class Logger {
constructor (name) {
this.name = name
}
log (message) {
console.log(`[${this.name}] ${message}`)
}
info (message) {
this.log(`info: ${message}`)
}
verbose (message) {
this.log(`verbose: ${message}`)
}
}
module.exports = Logger
Y podemos usar el módulo anterior de la siguiente manera:
const Logger = require('./logger')
const dbLogger = new Logger('DB')
dbLogger.info('This is an informational message')
const accessLogger = new Logger('ACCESS')
accessLogger.verbose('This is a verbose message')
La exportación de una clase aún proporciona un punto de entrada único para el módulo, pero en comparación con el patrón de subpila, expone muchas más partes internas del módulo. Por otro lado, permite mucha más potencia a la hora de ampliar su funcionalidad.
Exportando una instancia
En lugar de exportar una nueva clase, también podemos exponer su instancia.
class Logger {
constructor (name) {
this.count = 0
this.name = name
}
log (message) {
this.count++
console.log('[' + this.name + '] ' + message)
}
}
module.exports = new Logger('DEFAULT')
Este módulo recién definido se puede utilizar de la siguiente manera:
const logger = require('./logger')
logger.log('This is an informational message')
Un detalle interesante de este patrón es que no excluye la oportunidad de crear nuevas instancias, incluso si no estamos exportando la clase explícitamente. De hecho, podemos confiar en la propiedad de constructor
de la instancia exportada para construir una nueva instancia del mismo tipo:
const customLogger = new logger.constructor('CUSTOM') customLogger.log('This is an informational message')
Como puede ver, al usar logger.constructor()
, podemos instanciar nuevos objetos Logger
. Tenga en cuenta que esta técnica debe usarse con precaución o evitarse por completo. Considere que, si el autor del módulo decidió no exportar la clase explícitamente, probablemente quisiera mantener esta clase privada.
Conclusión
Hemos cubierto todos los tipos de patrones de exportación proporcionados por Node.js. El mejor que podemos usar depende de nuestros requisitos y la arquitectura de la aplicación.
Espero que encuentre útil este artículo. ¡Dale algunos aplausos para que otros también lo encuentren!