1. Comprendiendo la resolución de paquetes en Node.js
En esta parte, discutiremos CommonJS, módulos ES, ESM, la estructura de package.json y el cargador de package.json en Node.js. CommonJS es la estrategia de resolución de módulos más conocida en Node.js.
Hola, hoy voy a hablar sobre la comprensión de la resolución de paquetes en Node.js. Soy Yalcin Zipli. Soy un ingeniero de software senior en Sentry. Soy miembro del Comité Técnico de Node.js y miembro del Consejo de Proyectos Cruzados de la Fundación OpenJS. Puedes contactarme a través de mi cuenta de GitHub, github.com, y en Twitter, axe.com.
En resumen, hoy hablaremos sobre CommonJS, módulos ES, ESM, la estructura de package.json y el cargador de package.json en Node.js.
Comencemos con CommonJS. CommonJS es la primera y la estrategia de resolución de módulos más conocida en Node.js. Incluye archivos con implementaciones de require, exportaciones con module.export o exports. Puede tener una extensión de .cgs o .js. Las llamadas a require no tienen que incluir la extensión del archivo y la carga de un archivo es síncrona.
2. Carga condicional y resolución de extensiones de archivos
Si deseas realizar una carga condicional donde se carga y utiliza un archivo en una función, debes usar require dentro de la función. Sin embargo, esta implementación puede bloquear la ejecución y el IOU dependiendo del tamaño del archivo. La extensión no es obligatoria y el cargador verifica las diferentes extensiones en un orden específico. Cargar un archivo sin extensión implica llamadas síncronas al sistema de archivos, lo cual puede afectar el rendimiento.
Básicamente, si deseas realizar una carga condicional, digamos que tienes una función llamada leer archivo, y justo cuando se ejecuta esta función, deseas cargar un archivo y utilizar esa implementación en tu función, entonces debes usar require dentro de esta función.
La advertencia de esta implementación es que en la primera línea de leer archivo, el módulo cargado de forma perezosa es igual a require.reader, lo cual bloqueará la ejecución de este archivo porque es una llamada síncrona. Y bloqueará el IOU dependiendo del tamaño del archivo en sí.
Como puedes ver, la extensión no es obligatoria, .reader. Esto significa que primero el cargador verificará la extensión .gs, luego .json y así sucesivamente. Y luego devolverá un valor y luego podemos ejecutar esta función.
Para cargar este archivo sin extensión, esta implementación realiza una llamada síncrona al sistema de archivos para determinar si el archivo reader.js existe. Si no existe, verifica automáticamente si existe reader.json, y así sucesivamente, y luego devolverá un error si no se encuentra.
Esto es particularmente lento porque para determinar si ese archivo existe, necesitas realizar llamadas adicionales al sistema de archivos y esto afectará tu rendimiento, ya sea pequeño o grande, lo afectará.
3. Importación y Exportación de Módulos
En CommonJS, se incluyen módulos adicionales utilizando requires y exports. Los módulos de ES utilizan las declaraciones import y export. La declaración import devuelve una promesa y se debe evitar si está dentro de una función. Los módulos se pueden exportar con export o export default. Las extensiones de archivo para los módulos de ES pueden ser .mgs o .gs.
A pesar de la implementación de CommonJS, como dije, CommonJS, para incluir módulos adicionales con CommonJS, necesitas llamarlo con requires. Dentro de la función que deseas requerir, necesitas exportar la implementación con module.express y así sucesivamente. Por lo tanto, puede tener una extensión de .cgs, que es CommonJS, y .jsimplementation. Pero en los módulos de ES, es muy diferente, la idea detrás de ello. Se introdujo en Node.js 8.5.0 con una bandera experimental. Para incluir archivos, necesitas llamarlo con import. Si esta declaración de importación está en la parte superior de la implementación, como si no estuviera dentro de la función, entonces puedes usar import blah blah. Pero si está dentro de una función, debes evitar la declaración de importación porque import devuelve una promesa. Exporta implementaciones con export y export default, y puede tener una extensión de .mgs o .gs y carga asíncrona de un archivo de módulo.
4. Tipo de Módulo y Package.json
Para cargar módulos de forma condicional, utiliza lazyLoadedModule?equal y evita la declaración de importación. Esto permite la carga asíncrona sin bloquear la E/S. Node.js determina el tipo de módulo en función de las extensiones de archivo y la presencia de un archivo package.json. El atributo type en el package.json especifica si es un módulo o un CommonJS.
Entonces, en esencia, es una estructura de carga asíncrona. Para cargar el módulo de forma condicional, al igual que en la implementación anterior, necesitas llamarlo con lazyLoadedModule?equal, evadir la importación y esto hace que esta función en particular sea una llamada asíncrona, ya sea que lazyLoadedModule.haga algo de forma asíncrona o no. Esta es la razón principal por la que, cuando evades un módulo de importación, no bloquea la E/S.
Además de eso, hablamos sobre las extensiones de archivo CommonJS y las extensiones de archivo MGS, entonces, ¿cómo sabemos realmente qué está sucediendo? Así que tenemos este archivo package.json en todos nuestros proyectos. Contiene metadatos sobre un proyecto, pero a Node.js solo le importan 5 de esos campos. Nombre, principal, imports, exports y type. Por el bien de esta presentación, me voy a centrar solo en el atributo type. El tipo puede ser un módulo o un CommonJS. Así que creo que estás entendiendo hacia dónde voy con esto. En este ejemplo, tenemos un nombre node-congress-2024, tenemos un atributo principal y tenemos un tipo, que corresponde a modules. Entonces, ¿cómo sabe Node.js si estás usando ESM o CommonJS? Primero, verifica las extensiones de archivo, Node.js verifica las extensiones de archivo. Si es MGS, entonces es ESM. Si es CGS o JS, entonces es CommonJS. Entonces, encontrando el package.json, primero verificamos las extensiones.
5. Resolución de Módulos y Consejos
Si la extensión es un archivo .js, Node.js verifica la existencia de package.json en el directorio del archivo. Si se encuentra, verifica el campo type en package.json. Si el tipo es module, utiliza los cargadores ESM; de lo contrario, es CommonJS. Si el tipo no está presente, Node.js verifica la bandera experimental detect module. Para archivos requeridos desde ESM implementado en CommonJS, Node.js sigue la resolución de profundidad de módulos. Para mejorar el tiempo de carga, utiliza la bandera experimental default type CLI para aplicaciones ESM.
Si la extensión es básicamente un archivo .js, entonces no sabemos qué es, entonces necesitamos buscar el package.json en el que estamos ejecutando este archivo. Así que necesitamos conocer el contexto. Por lo tanto, Node.js intenta encontrar el package.json más cercano en el directorio hasta la raíz. Por lo tanto, verifica app, my, project, package.json, y así sucesivamente, hasta la raíz. Si no se encuentra, entonces asumirá algo más.
Node.js lo verifica y cuando se encuentra el valor package.json, Node.js verifica el campo type en package.json. Si es module, utiliza los cargadores ESM, la implementación de cargadores en Node.js. Y si no lo es, entonces es CommonJS. Entonces, si el tipo no está presente, no pudimos encontrar el package.json, ¿qué sucede? Si el tipo no está presente, se verifica una bandera experimental, experimental detect module. Esto es bastante nuevo en Node 20 o 21. Verifica automáticamente si el archivo que estás ejecutando es un archivo CommonJS o un archivo ESM. Esto es particularmente nuevo porque es una bandera experimental y hay problemas conocidos con ella, pero estamos trabajando en ello.
Entonces, si tenemos experimental detect module, entonces detectamos si el archivo es requerido, si es un ESM o un CommonJS. Si no, entonces volvemos a CommonJS. Entonces, sabemos cómo comienza nuestra aplicación, porque sabemos que el script inicial está escrito en ESM o CommonJS. Pero el problema es, ¿qué sucede si quieres requerir un archivo desde ESM que está implementado en CommonJS? ¿O quieres llamar a una función que es CommonJS o un ESM desde un módulo CommonJS? Entonces, ¿qué pasa con la resolución de profundidad de módulos de Node.js? Como si tienes módulos de Node implementados, un paquete dentro de tu lista de dependencias, y está implementado en CommonJS o ESM. Node.js verifica el campo type en el package.json de la dependencia. Si es module, utiliza ESM, de lo contrario, CommonJS. Si el tipo no está presente, utiliza el tipo del paquete padre, que es el paquete raíz que contiene el package.json de nuestro proyecto.
Continuemos con algunos consejos para mejorar el tiempo de carga. Porque hablamos de todos estos cargadores de package.json, llamadas al sistema, hablamos de la detección, las extensiones, y así sucesivamente. Entonces, si quieres evitar todas esas cosas y si quieres iniciar Node.js lo antes posible, lo que puedes hacer es, si tienes una aplicación ESM, puedes usar la bandera experimental default type CLI, que eliminará automáticamente todas esas comprobaciones y siempre devolverá ESM. No verificará la extensión, no verificará nada más, simplemente cargará el cargador ESM. Para proyectos existentes, utiliza un campo type en package.json para especificar el tipo de módulo. Si no tienes un tipo CommonJS, entonces asumimos que es CommonJS, pero si lo tienes, te recomiendo que lo uses. Para scripts de una sola vez, digamos que quieres ejecutar Node.index.js y no tienes package.json ni nada más. Así que si estás ejecutando un script de una sola vez, asegúrate de tener en el mismo directorio o en el directorio padre, un package.json con un campo type para que sepamos si es un ESM o un CommonJS, y no tengamos que recorrer todo el sistema de archivos en una ruta de archivo para entender si es ESM o CommonJS. Si no quieres hacer eso, si no quieres tener un package.json, debes usar .mgs para ESM y .cgs para CommonJS. Por supuesto, esto nos indica si es un ESM o un CommonJS. Además, puedes usar experimental detect module si solo quieres escribir .gs, pero no quieres preocuparte por las extensiones o package.json, siempre puedes usar experimental detect module. Pero ten en cuenta que esta es una bandera experimental y tiene algunos problemas. Además, lo primero que puedes hacer es usar experimental default module para ESM si quieres ir directamente al camino ESM. Para mejorar la resolución de módulos, como dije, por defecto, verificamos .gs, .json, y así sucesivamente. Siempre usa extensiones en las llamadas de importación requeridas. Esto es extremadamente importante. En ESM, esto es requerido. En CommonJS, no lo es. Pero te recomendamos que siempre uses extensiones. Gracias por escuchar y espero que hayas aprendido algo hoy.
Comments