Todo lo que debe saber sobre 'módulo' y 'requerir' en Node.js

Módulos

Node.js trata cada archivo JavaScript como un módulo independiente.

Por ejemplo, si tiene un archivo que contiene algún código y este archivo tiene un nombre xyz.js, este archivo se trata como un módulo en Node y puede decir que ha creado un módulo llamado xyz.

Tomemos un ejemplo para entender esto mejor.

Tiene un archivo llamado circle.jsque consiste en la lógica para calcular el área y la circunferencia de un círculo de un radio determinado, como se indica a continuación:

circle.js

Puede llamar al circle.jsarchivo un módulo llamado circle.

Quizás se pregunte por qué es necesario tener varios módulos. Podría haber escrito todo el código en un solo módulo. Bueno, es muy importante escribir código modular. Por modular, quiero decir que su código debe ser independiente y debe estar débilmente acoplado. Imagina que hay una aplicación grande y tienes todo tu código escrito en un solo lugar, solo un archivo. Demasiado desordenado, ¿verdad?

¿Cómo se ejecuta el código escrito dentro de un módulo?

Antes de ejecutar el código escrito dentro de un módulo, Node toma todo el código y lo encierra en un contenedor de funciones. La sintaxis de este contenedor de funciones es:

El contenedor de funciones para el circlemódulo se verá como el que se muestra a continuación:

Puede ver que hay un contenedor de funciones en el nivel raíz que abarca todo el código escrito dentro del circlemódulo.

Todo el código escrito dentro de un módulo es privado para el módulo, a menos que se indique explícitamente (exportado) lo contrario.

Esta es la ventaja más significativa de tener módulos en Node.js. Incluso si define una variable global en un módulo usando var, leto constpalabras clave, las variables tienen un alcance local para el módulo en lugar de un alcance global. Esto sucede porque cada módulo tiene un envoltorio de función propio y el código escrito dentro de una función es local a esa función y no se puede acceder fuera de esta función.

Imaginemos que hay dos módulos - A y B . El código escrito en el interior del módulo A está encerrado dentro de la función de contenedor que corresponde al módulo A . Algo similar ocurre con el código escrito dentro del módulo B . Debido a que el código perteneciente a ambos módulos está incluido dentro de diferentes funciones, estas funciones no podrán acceder al código de la otra. (¿Recuerda que cada función en JavaScript tiene su propio alcance local?) Esta es la razón por la que el módulo A no puede acceder al código escrito dentro del módulo B y viceversa.

Los cinco parámetros - exports, require, module, __filename, __dirnameestán disponibles dentro de cada módulo en el Nodo. Aunque estos parámetros son globales para el código dentro de un módulo, son locales para el módulo (debido al envoltorio de funciones como se explicó anteriormente). Estos parámetros proporcionan información valiosa relacionada con un módulo.

Repasemos el circlemódulo, que viste antes. Hay tres construcciones definidas en este módulo: una variable constante PI, una función nombrada calculateAreay otra función nombrada calculateCircumference. Un punto importante a tener en cuenta es que todas estas construcciones son privadas para el circlemódulo de forma predeterminada. Significa que no puede usar estas construcciones en ningún otro módulo a menos que se especifique explícitamente.

Entonces, la pregunta que surge ahora es ¿cómo se especifica algo en un módulo que pueda ser utilizado por algún otro módulo? Aquí es cuando los parámetros module& requiredel contenedor de funciones son útiles. Analicemos estos dos parámetros en este artículo.

module

El moduleparámetro (más bien una palabra clave en un módulo en Node) se refiere al objeto que representa el módulo actual . exportses una clave del moduleobjeto, cuyo valor correspondiente es un objeto. El valor predeterminado de module.exportsobjeto es {}(objeto vacío). Puede verificar esto registrando el valor de la modulepalabra clave dentro de cualquier módulo. Comprobemos cuál es el valor del moduleparámetro dentro del circlemódulo.

circle.js

Tenga en cuenta que hay una console.log(module);declaración al final del código en el archivo anterior. Cuando vea la salida, registrará el moduleobjeto, que tiene una clave nombrada exportsy el valor correspondiente a esta clave es {}(un objeto vacío).

Ahora, ¿qué hace el module.exportsobjeto? Bueno, se usa para definir cosas que un módulo puede exportar. Todo lo que se exporta desde un módulo puede, a su vez, estar disponible para otros módulos. Exportar algo es bastante sencillo. Solo necesita agregarlo al module.exportsobjeto. Hay tres formas de agregar algo al module.exportsobjeto que se va a exportar. Analicemos estos métodos uno por uno.

Método 1:

(Definir construcciones y luego usar múltiples module.exportsdeclaraciones para agregar propiedades)

En el primer método, primero define las construcciones y luego usa múltiples declaraciones module.exports donde cada declaración se usa para exportar algo de un módulo. Veamos este método en acción y veamos cómo puede exportar las dos funciones definidas en el circlemódulo.

circle.js

Como les dije antes, modulees un objeto que tiene la clave nombrada exportsy esta clave ( module.exports), a su vez, consiste en otro objeto. Ahora, si observa el código proporcionado anteriormente, todo lo que está haciendo es agregar nuevas propiedades (pares clave-valor) al module.exportsobjeto.

La primera propiedad tiene la clave calculateArea(definido en la línea 19)y el valor escrito en el lado derecho del operador de asignación es la función definida con el nombre calculateArea(en la línea 9).

La segunda propiedad (definida en la línea 20) tiene la clave calculateCircumferencey el valor es la función definida con el nombre calculateCircumference(en la línea 16).

Por lo tanto, ha asignado dos propiedades (pares clave-valor) al module.exportsobjeto.

Además, no olvidemos que ha utilizado la notación de puntos aquí. Alternativamente, puede usar la notación entre corchetes para asignar las propiedades al module.exportsobjeto y agregar las funciones, calculateAreay calculateCircumferenceespecificando las teclas que siguen a la notación entre corchetes. Por lo tanto, puede escribir las siguientes dos líneas para agregar propiedades al module.exportsobjeto usando notación entre corchetes mientras reemplaza las dos últimas líneas (usando notación de puntos) en el código dado arriba:

// exporting stuff by adding to module.exports object using the bracket notation
module.exports['calculateArea'] = calculateArea;module.exports['calculateCircumference'] = calculateCircumference; 

Intentemos ahora registrar el valor del module.exportsobjeto después de agregar las propiedades. Tenga en cuenta que la siguiente declaración se agrega al final del código en el archivo que se muestra a continuación:

// logging the contents of module.exports object after adding properties to it
console.log(module.exports);

circle.js

Revisemos el resultado de este código y veamos si todo funciona bien. Para hacer esto, guarde su código y ejecute el siguiente comando en su Terminal :

node circle

Salida:

{ calculateArea: [Function: calculateArea], calculateCircumference: [Function: calculateCircumference] }

Se registran las construcciones calculateAreay calculateCircumference, agregadas al module.exports, objeto. Por lo tanto, agregó con éxito las dos propiedades en el module.exportsobjeto para que las funciones calculateAreay calculateCircumferencepuedan exportarse desde el circlemódulo a algún otro módulo.

En este método, primero definió todas las construcciones y luego usó múltiples declaraciones module.exports donde cada declaración se usa para agregar una propiedad al module.exportsobjeto.

Método 2:

(Definir construcciones y luego usar una sola module.exportsdeclaración para agregar propiedades)

Otra forma es definir todas las construcciones primero (como lo hizo en el método anterior) pero use una sola module.exportsdeclaración para exportarlas todas. Este método es similar a la sintaxis de la notación literal de objeto en la que agrega todas las propiedades a un objeto a la vez.

Aquí, usó la notación literal del objeto y agregó ambas funciones, calculateArea y calculateCircumference(todas a la vez) al module.exportsobjeto escribiendo una sola declaración module.exports .

Si verifica la salida de este código, obtendrá el mismo resultado que obtuvo anteriormente al usar el método 1.

Método 3:

(Agregar propiedades al module.exportsobjeto mientras se definen construcciones)

En este método, puede agregar las construcciones al module.exportsobjeto mientras las define. Veamos cómo se puede adoptar este método en nuestro circlemódulo.

En el código dado arriba, puede ver que las funciones en el módulo se agregan al module.exportsobjeto cuando se definen. Veamos cómo funciona esto. Está agregando una clave calculateAreaal module.exportsobjeto y el valor correspondiente a esta clave es la definición de función.

Tenga en cuenta que la función ya no tiene ningún nombre y es una función anónima que solo se trata como un valor para una clave de un objeto. Por lo tanto, no se puede hacer referencia a esta función en el circlemódulo y no se puede invocar esta función dentro de este módulo escribiendo la siguiente declaración:

calculateArea(8);

Si intenta ejecutar la declaración anterior, obtendrá una ReferenceErrordeclaración calculateArea is not defined.

Ahora que ha aprendido cómo puede especificar qué se debe exportar de un módulo, ¿cómo cree que el otro módulo podrá utilizar el material exportado? Debe importar el módulo a algún otro módulo para poder utilizar el material exportado del primero en el segundo. Aquí es cuando necesitamos discutir otro parámetro llamado require.

exigir

requirepalabra clave se refiere a una función que se usa para importar todas las construcciones exportadas usando el module.exportsobjeto de otro módulo. Si tiene un módulo x en el que está exportando algunas construcciones usando el module.exportsobjeto y desea importar estas construcciones exportadas en el módulo y , entonces necesita requerir el módulo x en el módulo y usando la requirefunción. El valor devuelto por la requirefunción en el módulo y es igual al module.exportsobjeto en el módulo x .

Entendamos esto usando el ejemplo que discutimos anteriormente. Ya tiene el circlemódulo desde el que está exportando las funciones calculateAreay calculateCircumference. Ahora, veamos cómo puede usar la requirefunción para importar el material exportado en otro módulo.

Primero creemos un nuevo archivo en el que utilizará el código exportado del circlemódulo. Vamos a nombrar este archivo app.jsy puede llamarlo appmódulo.

El objetivo es importar al appmódulo todo el código exportado desde el circlemódulo. Entonces, ¿cómo puede incluir su código escrito en un módulo dentro de otro módulo?

Considere la sintaxis de la requirefunción dada a continuación:

const variableToHoldExportedStuff = require('idOrPathOfModule');

La requirefunción toma un argumento que puede ser un ID o una ruta. La identificación se refiere a la identificación (o nombre) del módulo necesario. Debe proporcionar la ID como argumento cuando utilice los módulos de terceros o los módulos principales proporcionados por Node Package Manager. Por otro lado, cuando tiene módulos personalizados definidos por usted, debe proporcionar la ruta del módulo como argumento. Puede leer más sobre la función require en este enlace.

Debido a que ya ha definido un módulo personalizado con nombre circle, proporcionará la ruta como argumento para la requirefunción.

app.js

Si lo nota claramente, el punto al comienzo de la ruta significa que es una ruta relativa y que los módulos appy circleestán almacenados en la misma ruta.

Iniciemos sesión en la consola con la circlevariable, que contiene el resultado devuelto por la requirefunción. Veamos qué contiene esta variable.

app.js

Verifique el resultado guardando todo su código y ejecutando el siguiente comando en su Terminal (este último no es necesario si tiene el nodemonpaquete instalado):

node app

Salida:

{ calculateArea: [Function: calculateArea],calculateCircumference: [Function: calculateCircumference] }

Como puede ver, la requirefunción devuelve un objeto, cuyas claves son los nombres de las variables / funciones que se han exportado desde el módulo requerido ( circle). En resumen, la requirefunción devuelve el module.exportsobjeto.

Accedamos ahora a las funciones importadas del circlemódulo.

app.js

Salida:

Area = 200.96, Circumference = 50.24

¿Qué crees que pasará si intento acceder a la variable nombrada PIdefinida en el circlemódulo dentro del appmódulo?

app.js

Salida:

Area = 200.96, Circumference = 50.24pi = undefined

¿Puedes averiguar por qué pies undefined? Bueno, esto se debe a que la variable PIno se exporta desde el circlemódulo. ¿Recuerda el punto en el que le dije que no puede acceder al código escrito dentro de un módulo en otro módulo porque todo el código escrito dentro de un módulo es privado a menos que se exporte? Aquí, está intentando acceder a algo que no se ha exportado desde el circlemódulo y es privado para él.

Por lo tanto, es posible que se pregunte por qué no obtuvo un ReferenceError. Esto se debe a que está intentando acceder a una clave nombrada PIdentro del module.exportsobjeto devuelto por la requirefunción. También sabe que la clave nombrada PIno existe en el module.exportsobjeto.

Tenga en cuenta que cuando intenta acceder a una clave que no existe en un objeto, obtiene el resultado como undefined. Esta es la razón por la que obtiene PIcomo en undefinedlugar de obtener un ReferenceError.

Ahora, exportemos la variable PIdel circlemódulo y veamos si cambia la respuesta.

circle.js

Observe que aquí no está utilizando el nombre de la variable PIcomo clave de la propiedad agregada al module.exportsobjeto. En cambio, estás usando otro nombre, que es lifeOfPi.

Esto es algo interesante de notar. Cuando está exportando alguna construcción de codificación, puede dar cualquier nombre a la clave al agregar una propiedad agregada al module.exportsobjeto. No es obligatorio utilizar el mismo nombre que utilizó al definir la construcción. Esto se debe a que puede utilizar cualquier identificador válido como clave en un objeto JavaScript. Por lo tanto, en el lado izquierdo del operador de asignación, puede usar cualquier identificador válido, pero en el lado derecho del operador de asignación, debe proporcionar un valor que se define como una construcción en el alcance del módulo actual (como usted he definido las variables y funciones en el módulo 'círculo').

Un punto importante a tener en cuenta es que al importar algo de otro módulo en el módulo actual, debe utilizar la misma clave que utilizó al exportarlo.

app.js

Debido a que usó la clave lifeOfPi, debe usar la misma clave para acceder a la variable PIdefinida en el circlemódulo, como se hace en el código dado arriba.

Salida:

Area = 200.96, Circumference = 50.24pi = 3.14

¿Qué crees que pasará si usas el nombre de la variable en lugar de usar la clave que se usó durante la exportación? En resumen, intentemos acceder a PI(nombre de la variable) en lugar de lifeOfPi(clave utilizada durante la exportación PI).

app.js

Salida:

Area = 200.96, Circumference = 50.24pi = undefined

Esto sucede porque el module.exportsobjeto ya no conoce la variable PI. Solo conoce las claves que se le agregaron. Debido a que la clave utilizada para exportar la variable PIes lifeOfPi, la última solo se puede usar para acceder a la primera.

TL; DR

  • Cada archivo de Node.js se denomina módulo .
  • Antes de ejecutar el código escrito en un módulo, Node.js toma todo el código escrito dentro del módulo y lo convierte en un contenedor de funciones, que tiene la siguiente sintaxis:
(function(exports, require, module, __filename, __dirname) { // entire module code lives in here});
  • El contenedor de funciones asegura que todo el código escrito dentro de un módulo sea privado a menos que se indique explícitamente lo contrario (exportado). Los parámetros exports, require, module, __filename, y __dirnameactúan como las variables globales para todo el código en un módulo. Dado que cada módulo tiene un contenedor de función propio, el código escrito dentro de un contenedor de función se vuelve local para ese contenedor de función (módulo de lectura) y no es accesible dentro de otro contenedor de función (módulo de lectura).
  • modulepalabra clave se refiere al objeto que representa el módulo actual. El moduleobjeto tiene una clave nombrada exports. module.exportses otro objeto que se utiliza para definir lo que puede exportar un módulo y puede estar disponible para otros módulos. En resumen, si un módulo desea exportar algo, debe agregarse al module.exportsobjeto.
  • El valor predeterminado de module.exportsobjeto es {}.
  • Hay tres métodos en los que puede exportar algo de un módulo o agregar algo al module.exportsobjeto:

    1. Defina todas las construcciones primero y luego use múltiples module.exportsdeclaraciones donde cada declaración se usa para exportar una construcción.

    2. Defina primero todas las construcciones y luego use una sola module.exportsdeclaración para exportar todas las construcciones a la vez siguiendo la notación literal del objeto.

    3. Agregue construcciones al module.exportsobjeto mientras las define.

  • requirepalabra clave se refiere a una función que se usa para importar todas las variables y funciones exportadas usando el module.exportsobjeto desde otro módulo. En resumen, si un archivo quiere importar algo, debe declararlo usando la siguiente sintaxis:
require('idOrPathOfModule');
  • Al exportar algo de un módulo, puede utilizar cualquier identificador válido. No es obligatorio que necesite dar el nombre exacto de la variable / función como clave de la propiedad agregada al module.exportsobjeto. Solo asegúrese de usar la misma clave para acceder a algo que usó al exportarlo.