Cómo usar módulos ECMAScript con Node.js – CloudSavvy IT

ECMAScript no tenía una forma estandarizada de empaquetar el código como módulos reutilizables durante la mayor parte de su historia. A falta de una solución integrada, el CommonJS (CJS) El enfoque se convirtió en el estándar de facto para el desarrollo de Node.js. Esto usa require y module.exports para consumir y proporcionar fragmentos de código:

// Import a module
const fs = require("fs");
 
// Provide an export
module.exports = () => "Hello World";

ES2015, también conocido como ES6, finalmente introdujo su propio sistema de módulo incorporado. ECMAScript o Módulos ES (ESM) confiar en el import y export sintaxis:

// Import a default export
import fs from "fs";
 
// Provide a default export
export default () => "Hello World";
 
// Import a named export
import {helloWorld} from "./hello-world.js";
 
// Provide a named export
export const helloWorld = () => "Hello World";

Node.js ha ofrecido soporte predeterminado para ESM desde v16. En versiones anteriores era necesario utilizar el --experimental-modules bandera para activar la capacidad. Si bien los módulos ES ahora están marcados como estables y listos para uso general, la presencia de dos mecanismos de carga de módulos diferentes significa que es un desafío consumir ambos tipos de código en un solo proyecto.

En este artículo, veremos cómo usar ES Modules con Node y qué puede hacer para maximizar la interoperabilidad con los paquetes CommonJS.

Los basicos

Las cosas son relativamente sencillas si está comenzando un nuevo proyecto y quiere confiar en ESM. Como Node.js ahora ofrece soporte completo, puede dividir su código en archivos separados y usar import y export declaraciones para acceder a sus módulos.

Desafortunadamente, debe tomar algunas decisiones conscientes desde el principio. Por defecto, Node no es compatible import y export dentro de los archivos que terminan con el .js extensión. Puede añadir el sufijo a sus archivos como .mjsdonde ESM siempre está disponible, o modificar su package.json archivo a incluir "type": "module".

{
    "name": "example-package",
    "type": "module",
    "dependencies": {
        "..."
    }
}

Elegir la última ruta suele ser más conveniente para proyectos que utilizarán exclusivamente ESM. El nodo utiliza el type campo para determinar el sistema de módulo predeterminado para su proyecto. Ese sistema de módulos siempre se usa para manejar .js archivos Cuando no se configura manualmente, CJS es el sistema de módulo predeterminado para maximizar la compatibilidad con el ecosistema existente de código Node. Archivos con cualquiera .cjs o .mjs las extensiones siempre serán tratadas como fuente en formato CJS y ESM respectivamente.

Importación de un módulo CommonJS desde ESM

Puede importar módulos CJS dentro de archivos ESM utilizando un import declaración:

// cjs-module.cjs
module.exports.helloWorld = () => console.log("Hello World");
 
// esm-module.mjs
import component from "./cjs-module.cjs";
component.helloWorld();

component se resolverá al valor del módulo CJS module.exports. El ejemplo anterior muestra cómo puede acceder a las exportaciones con nombre como propiedades de objeto en el nombre de su importación. También puede acceder a exportaciones específicas utilizando la sintaxis de importación denominada ESM:

import {helloWorld} from "./cjs-module.cjs";
helloWorld();

Esto funciona mediante un sistema de análisis estático que escanea archivos CJS para calcular las exportaciones que proporcionan. Este enfoque es necesario porque CJS no comprende el concepto de «exportación con nombre». Todos los módulos CJS tienen una exportación: las exportaciones «nombradas» son realmente un objeto con múltiples pares de propiedades y valores.

Debido a la naturaleza del proceso de análisis estático, es posible que algunos patrones de sintaxis raros no se detecten correctamente. Si esto sucede, tendrá que acceder a las propiedades que necesita a través del objeto de exportación predeterminado.

Importación de un módulo ESM desde CJS

Las cosas se complican cuando desea utilizar un nuevo módulo ESM dentro del código CJS existente. no puedes escribir el import declaración dentro de los archivos CJS. Sin embargo, el dinámica import() sintaxis funciona y se puede combinar con await para acceder a los módulos de manera relativamente conveniente:

// esm-module.mjs
const helloWorld = () => console.log("Hello World");
export {helloWorld};
 
// esm-module-2.mjs
export default = () => console.log("Hello World");
 
// cjs-module.cjs
const loadHelloWorld = async () => {
    const {helloWorld} = await import("./esm-module.mjs");
    return helloWorld;
};
const helloWorld = await loadHelloWorld();
helloWorld();
 
const loadHelloWorld2 = async() => {
    const helloWorld2 = await import("./esm-module-2.mjs");
    return helloWorld2;
};
const helloWorld2 = await loadHelloWorld2();
helloWorld2();

Esta estructura se puede utilizar para acceder de forma asíncrona a las exportaciones predeterminadas y con nombre de sus módulos ESM.

Recuperación de la ruta del módulo actual con módulos ES

Los módulos ES no tienen acceso a todas las variables globales conocidas de Node.js disponibles en contextos CJS. además require() y module.exportsno podrá acceder a la __dirname o __filename constantes tampoco. Estos son comúnmente utilizados por los módulos CJS que necesitan conocer la ruta a su propio archivo.

Los archivos ESM pueden leer import.meta.url para obtener esta información:

console.log(import.meta.url);
// file:///home/demo/module.mjs

La URL devuelta da la ruta absoluta al archivo actual.

¿Por qué todas las incompatibilidades?

Las diferencias entre CJS y ESM son mucho más profundas que simples cambios sintácticos. CJS es un sistema síncrono; Cuando usted require() un módulo, Node lo carga directamente desde el disco y ejecuta su contenido. ESM es asincrónico y divide las importaciones de scripts en varias fases distintas. Las importaciones se analizan, se cargan de forma asíncrona desde su ubicación de almacenamiento y luego se ejecutan una vez que todas sus propias importaciones se han recuperado de la misma manera.

ESM está diseñado como una solución moderna de carga de módulos con amplias aplicaciones. Esta es la razón por la que los módulos ESM son adecuados para su uso en navegadores web: son asíncronos por diseño, por lo que las redes lentas no son un problema.

La naturaleza asíncrona de ESM también es responsable de las limitaciones en torno a su uso en el código CJS. Los archivos CJS no son compatibles con el nivel superior await entonces no puedes usar import por sí mismo:

// this...
import component from "component.mjs";
 
// ...can be seen as equivalent to this...
const component = await import("component.mjs");
 
// ...but top-level "await" isn't available in CJS

Por lo tanto, tienes que usar la dinámica. import() estructura dentro de un async función.

¿Debería cambiar a módulos ES?

La respuesta simple es sí. Los módulos ES son la forma estandarizada de importar y exportar código JavaScript. CJS le dio a Node un sistema de módulos cuando el lenguaje carecía de uno propio. Ahora que está disponible, lo mejor para la salud a largo plazo de la comunidad es adoptar el enfoque descrito por el estándar ECMAScript.

ESM es también el sistema más potente. Debido a que es asíncrono, obtiene importaciones dinámicas, remotas importaciones desde URLy mejora el rendimiento en algunas situaciones. También puede reutilizar sus módulos con otros tiempos de ejecución de JavaScript, como el código que se entrega directamente a los navegadores web a través de <script type="module"> Etiquetas HTML.

No obstante, la migración sigue siendo un desafío para muchos proyectos existentes de Node.js. CJS no va a desaparecer pronto. Mientras tanto, puede facilitar la transición ofreciendo exportaciones CJS y ESM para sus propias bibliotecas. Esto se logra mejor escribiendo un contenedor ESM delgado alrededor de cualquier exportación CJS existente:

import demoComponent from "../cjs-component-demo.js";
import exampleComponent from "../cjs-component-example.js";
export {demoComponent, exampleComponent};

Coloque este archivo dentro de un nuevo esm subdirectorio en su proyecto. Agrega un package.json junto a él que contiene el {"type": "module"} campo.

A continuación, actualice el package.json en la raíz de su proyecto para incluir el siguiente contenido:

{
    "exports": {
        "require": "./cjs-index.js",
        "import": "./esm/esm-index.js"
    }
}

Esto le dice a Node que proporcione su punto de entrada CJS cuando los usuarios de su paquete require() una exportación Cuándo import se utiliza, en su lugar se ofrecerá la secuencia de comandos contenedora de ESM. Cuidado con esto importar mapa La funcionalidad también trae consigo otro efecto secundario: los usuarios estarán limitados solo a las exportaciones proporcionadas por su archivo de punto de entrada. Cargar archivos específicos a través de rutas dentro del paquete (import demo from "example-package/path/to/file.js) está deshabilitado cuando hay un mapa de importación presente.

Resumen

El panorama del módulo Node.js puede ser doloroso, especialmente cuando necesita compatibilidad con CJS y ESM. Aunque las cosas han mejorado con la estabilización de la compatibilidad con ESM, aún debe decidir por adelantado qué sistema de módulos debe ser el «predeterminado» para su proyecto. Cargar código desde el «otro» sistema es posible pero a menudo incómodo, particularmente cuando CJS depende de ESM.

Las cosas mejorarán gradualmente a medida que más proyectos importantes cambien a la adopción de ESM como su enfoque preferido. Sin embargo, con tanto código CJS en existencia, la migración masiva no es realista y es probable que ambos mecanismos se utilicen en paralelo, con todas las ventajas y desventajas que conlleva, durante varios años más.

Si está iniciando una nueva biblioteca, asegúrese de distribuir código compatible con ESM e intente utilizarlo como su sistema predeterminado siempre que sea posible. Esto debería ayudar al ecosistema Node a converger en ESM, realineándolo con los estándares ES.

Deja un comentario

En esta web usamos cookies para personalizar tu experiencia de usuario.    Política de cookies
Privacidad