Cómo funciona el sistema de módulos, CommonJS y require

En este capítulo de la web escuelajavascript.com estas a punto de aprender cómo funciona el sistema de módulos Node.js y CommonJS y qué es lo que hace la función require.

CommonJS al rescate

El lenguaje JavaScript no tenía una forma nativa de organizar el código antes del estándar ES2015. Node.js llenó este vacío con el formato del módulo CommonJS. En este artículo, aprenderemos cómo funciona el sistema de módulos Node.js, cómo puedes organizar tus módulos y qué significa el nuevo estándar de ES para el futuro de Node.js.

¿Qué es el sistema de módulos?

Los módulos son los bloques de construcción fundamentales de la estructura del código. El sistema de módulos te permite organizar tu código, ocultar información y solo exponer la interfaz pública de un componente usando module.exports. Cada vez que usas la llamada require; por lo tanto, estás cargando otro módulo.

El ejemplo más simple puede ser el siguiente usando CommonJS:


// add.js
function add (a, b) {
return a + b
}

module.exports = add

Para utilizar el módulo add que acabamos de crear, debemos pedirlo.


// index.js
const add = require('./add')

console.log(add(4, 5))
//9

Aunque no se note, add.js está envuelto por Node.js de esta manera:


(function (exports, require, module, __filename, __dirname) {
function add (a, b) {
return a + b
}

module.exports = add
})


Es por esto que puedes acceder a las variables de tipo global como require y module. También asegura que tus variables estén dentro del alcance de tu módulo en lugar del objeto global.

¿Cómo funciona require?

El mecanismo de carga del módulo en Node.js almacena en caché los módulos en la primera llamada a require. Significa que cada vez que uses: require(‘awesome-module’), obtendrás la misma instancia de awesome-module, lo que garantiza que los módulos sean de tipo singleton y tengan el mismo estado en toda tu aplicación.

Puedes cargar módulos nativos y referencias de ruta desde tu sistema de archivos o módulos instalados. Si el identificador pasado a la función require no es un módulo nativo, o una referencia de archivo (que comienza con /, ../, ./ o similar), entonces Node.js buscará los módulos instalados. Recorrerá tu sistema de archivos buscando el módulo referenciado en la carpeta node_modules. Comienza desde el directorio principal de tu módulo actual y luego se mueve al directorio principal hasta que encuentra el módulo correcto, o hasta que se alcanza la raíz del sistema de archivos.

Require bajo el capó – module.js

El módulo que se ocupa de la carga de módulos en el núcleo de Node se llama module.js, y se puede encontrar en lib / module.js en el repositorio de Node.js.

Las funciones más importantes para comprobar aquí son las funciones _loady _compile.

Modulo _load

Esta función comprueba si el módulo ya está en el caché; si es así, devuelve el objeto de exportación.

Si el módulo es nativo, llama al NativeModule.require() con el nombre de archivo y devuelve el resultado.

De lo contrario, crea un nuevo módulo para el archivo y lo guarda en el caché. Luego carga el contenido del archivo antes de devolver su objeto de exportación.

Modulo _compile

La función de compilación ejecuta el contenido del archivo en el ámbito correcto o en el recinto de seguridad, así como expone las variables auxiliares como require, module o exports al archivo.

¿Cómo organizar el código?

En nuestras aplicaciones, necesitamos encontrar el equilibrio correcto de cohesión y acoplamiento al crear módulos. El escenario deseable es lograr una alta cohesión y un acoplamiento suelto de los módulos.

Un módulo debe enfocarse solo en una parte de la funcionalidad para tener una alta cohesión. El acoplamiento flojo significa que los módulos no deben tener un estado global o compartido. Solo deben comunicarse pasando los parámetros, y son fácilmente reemplazables sin tocar su base de código más amplia.

Usualmente exportamos funciones nombradas o constantes de la siguiente manera:


'use strict'

const CONNECTION_LIMIT = 0

function connect () { /* ... */ }

module.exports = {
CONNECTION_LIMIT,
connect
}

¿Qué hay en tus node_modules?

La carpeta node_modules es el lugar donde Node.js busca módulos. npm v2 y npm v3 instalan sus dependencias de manera diferente. Puedes averiguar qué versión de npm está utilizando ejecutando:


npm --version

npm v2

npm v2 instala todas las dependencias de forma anidada, donde las dependencias del paquete principal se encuentran en tu carpeta node_modules.

npm v3

npm v3 intenta aplanar estas dependencias secundarias e instalarlas en la carpeta raíz node_modules. Esto significa que no puedes saber al buscar en node_modules qué paquetes son sus dependencias explícitas o implícitas. También es posible que el orden de instalación cambie la estructura de tu carpeta porque npm v3 no es determinista de esta manera.

Puedes asegurarte de que su directorio node_modules sea siempre el mismo instalando paquetes solo desde un package.json. En este caso, instala tus dependencias en orden alfabético, lo que también significa que obtendrá el mismo árbol de carpetas. Esto es importante porque los módulos se almacenan en caché utilizando tu ruta como clave de búsqueda. Cada paquete puede tener su propia carpeta secundaria node_modules, lo que puede resultar en múltiples instancias del mismo paquete y del mismo módulo.

¿Cómo manejar tus módulos?

Hay dos formas principales para cablear módulos. Uno de ellos es usando dependencias codificadas, cargando explícitamente un módulo en otro usando una llamada a la función require. El otro método es usar un patrón de inyección de dependencia, donde pasamos los componentes como un parámetro, o tenemos un contenedor global (conocido como IoC, o contenedor de Inversión de Control) , que centraliza la administración de los módulos.

Podemos permitir que Node.js administre el ciclo de vida de los módulos mediante la carga de módulos codificados. Organiza tus paquetes de forma intuitiva, lo que facilita la comprensión y la depuración.

La inyección de dependencia rara vez se utiliza en un entorno Node.js, aunque es un concepto útil. El patrón DI puede dar como resultado un desacoplamiento mejorado de los módulos. En lugar de definir explícitamente las dependencias para un módulo, se reciben desde el exterior. Por lo tanto, se pueden reemplazar fácilmente con módulos que tengan las mismas interfaces.

Veamos un ejemplo para módulos DI usando el patrón de fábrica:


class Carro {
constructor (options) {
this.engine = options.engine
}

start () {
this.engine.start()
}
}

function create (options) {
return new Carro(options)
}

module.exports = create

El sistema de módulos ES2015

Como vimos anteriormente, el sistema de módulos CommonJS utiliza una evaluación de tiempo de ejecución de los módulos, envolviéndolos en una función antes de la ejecución. No es necesario envolver los módulos ES2015 ya que los enlaces import / export se crean antes de evaluar el módulo. Esta incompatibilidad es la razón por la que actualmente no hay tiempo de ejecución de JavaScript que admita los módulos ES. Hubo mucha discusión sobre el tema y una propuesta está en estado DRAFT, por lo que esperamos tener soporte en futuras versiones de Node.