Módulos: Módulos ECMAScript
[Historial]
Versión | Cambios |
---|---|
v23.1.0 | Los atributos de importación ya no son experimentales. |
v22.0.0 | Se eliminó el soporte para las aserciones de importación. |
v21.0.0, v20.10.0, v18.20.0 | Se agregó soporte experimental para los atributos de importación. |
v20.0.0, v18.19.0 | Los hooks de personalización de módulos se ejecutan fuera del hilo principal. |
v18.6.0, v16.17.0 | Se agregó soporte para encadenar hooks de personalización de módulos. |
v17.1.0, v16.14.0 | Se agregó soporte experimental para las aserciones de importación. |
v17.0.0, v16.12.0 | Se consolidaron los hooks de personalización, se eliminaron los hooks getFormat , getSource , transformSource y getGlobalPreloadCode , se agregaron los hooks load y globalPreload , se permitió devolver format desde los hooks resolve o load . |
v14.8.0 | Se eliminó la bandera de Top-Level Await. |
v15.3.0, v14.17.0, v12.22.0 | Se estabilizó la implementación de módulos. |
v14.13.0, v12.20.0 | Soporte para la detección de exportaciones nombradas de CommonJS. |
v14.0.0, v13.14.0, v12.20.0 | Se eliminó la advertencia de módulos experimentales. |
v13.2.0, v12.17.0 | La carga de módulos ECMAScript ya no requiere una bandera de línea de comandos. |
v12.0.0 | Se agregó soporte para módulos ES usando la extensión de archivo .js a través del campo "type" de package.json . |
v8.5.0 | Añadido en: v8.5.0 |
[Estable: 2 - Estable]
Estable: 2 Estabilidad: 2 - Estable
Introducción
Los módulos ECMAScript son el formato estándar oficial para empaquetar código JavaScript para su reutilización. Los módulos se definen utilizando una variedad de declaraciones import
y export
.
El siguiente ejemplo de un módulo ES exporta una función:
// addTwo.mjs
function addTwo(num) {
return num + 2
}
export { addTwo }
El siguiente ejemplo de un módulo ES importa la función de addTwo.mjs
:
// app.mjs
import { addTwo } from './addTwo.mjs'
// Imprime: 6
console.log(addTwo(4))
Node.js es totalmente compatible con los módulos ECMAScript tal y como se especifican actualmente y proporciona interoperabilidad entre ellos y su formato de módulo original, CommonJS.
Habilitación
Node.js tiene dos sistemas de módulos: módulos CommonJS y módulos ECMAScript.
Los autores pueden indicar a Node.js que interprete JavaScript como un módulo ES a través de la extensión de archivo .mjs
, el campo "type"
de package.json
con el valor "module"
o el flag --input-type
con el valor "module"
. Estos son marcadores explícitos de código que se pretende ejecutar como un módulo ES.
A la inversa, los autores pueden indicar explícitamente a Node.js que interprete JavaScript como CommonJS a través de la extensión de archivo .cjs
, el campo "type"
de package.json
con el valor "commonjs"
o el flag --input-type
con el valor "commonjs"
.
Cuando el código carece de marcadores explícitos para cualquiera de los sistemas de módulos, Node.js inspeccionará el código fuente de un módulo para buscar la sintaxis de módulos ES. Si se encuentra dicha sintaxis, Node.js ejecutará el código como un módulo ES; de lo contrario, ejecutará el módulo como CommonJS. Consulte Determinación del sistema de módulos para obtener más detalles.
Paquetes
Esta sección se ha movido a Módulos: Paquetes.
Especificadores import
Terminología
El especificador de una declaración import
es la cadena después de la palabra clave from
, por ejemplo, 'node:path'
en import { sep } from 'node:path'
. Los especificadores también se usan en las declaraciones export from
y como argumento para una expresión import()
.
Existen tres tipos de especificadores:
- Especificadores relativos como
'./startup.js'
o'../config.mjs'
. Hacen referencia a una ruta relativa a la ubicación del archivo que importa. La extensión del archivo siempre es necesaria para estos. - Especificadores sin ruta como
'some-package'
o'some-package/shuffle'
. Pueden hacer referencia al punto de entrada principal de un paquete por el nombre del paquete, o a un módulo de característica específico dentro de un paquete con el nombre del paquete como prefijo, según los ejemplos respectivamente. Incluir la extensión del archivo solo es necesario para paquetes sin un campo"exports"
. - Especificadores absolutos como
'file:///opt/nodejs/config.js'
. Hacen referencia directa y explícitamente a una ruta completa.
La resolución de especificadores sin ruta se maneja mediante el algoritmo de resolución y carga de módulos de Node.js. Todas las demás resoluciones de especificadores siempre se resuelven únicamente con la semántica de resolución de URL relativa estándar.
Al igual que en CommonJS, se puede acceder a los archivos de módulos dentro de los paquetes agregando una ruta al nombre del paquete, a menos que el package.json
del paquete contenga un campo "exports"
, en cuyo caso solo se puede acceder a los archivos dentro de los paquetes a través de las rutas definidas en "exports"
.
Para obtener detalles sobre estas reglas de resolución de paquetes que se aplican a los especificadores sin ruta en la resolución de módulos de Node.js, consulte la documentación de paquetes.
Extensiones de archivo obligatorias
Se debe proporcionar una extensión de archivo al usar la palabra clave import
para resolver especificadores relativos o absolutos. Los índices de directorio (por ejemplo, './startup/index.js'
) también deben especificarse completamente.
Este comportamiento coincide con cómo se comporta import
en entornos de navegador, asumiendo un servidor configurado de manera típica.
URLs
Los módulos ES se resuelven y almacenan en caché como URLs. Esto significa que los caracteres especiales deben estar codificados por porcentaje, como #
con %23
y ?
con %3F
.
Se admiten los esquemas de URL file:
, node:
y data:
. Un especificador como 'https://example.com/app.js'
no se admite de forma nativa en Node.js a menos que se utilice un cargador HTTPS personalizado.
URLs file:
Los módulos se cargan varias veces si el especificador import
utilizado para resolverlos tiene una consulta o fragmento diferente.
import './foo.mjs?query=1' // carga ./foo.mjs con la consulta "?query=1"
import './foo.mjs?query=2' // carga ./foo.mjs con la consulta "?query=2"
La raíz del volumen se puede referenciar a través de /
, //
o file:///
. Dadas las diferencias entre la URL y la resolución de rutas (como los detalles de la codificación por porcentaje), se recomienda usar url.pathToFileURL al importar una ruta.
Imports data:
Agregado en: v12.10.0
Las data:
URLs son compatibles para importar con los siguientes tipos MIME:
text/javascript
para módulos ESapplication/json
para JSONapplication/wasm
para Wasm
import 'data:text/javascript,console.log("¡hola!");';
import _ from 'data:application/json,"¡mundo!"' with { type: 'json' };
Las data:
URLs solo resuelven especificadores desnudos para módulos integrados y especificadores absolutos. Resolver especificadores relativos no funciona porque data:
no es un esquema especial. Por ejemplo, intentar cargar ./foo
desde data:text/javascript,import "./foo";
no se resuelve porque no existe el concepto de resolución relativa para las data:
URLs.
Imports node:
[Historial]
Versión | Cambios |
---|---|
v16.0.0, v14.18.0 | Se agregó soporte para importación node: a require(...) . |
v14.13.1, v12.20.0 | Agregado en: v14.13.1, v12.20.0 |
Las node:
URLs son compatibles como un medio alternativo para cargar módulos incorporados de Node.js. Este esquema de URL permite que los módulos incorporados se referencien mediante cadenas de URL absolutas válidas.
import fs from 'node:fs/promises'
Importar atributos
[Historial]
Versión | Cambios |
---|---|
v21.0.0, v20.10.0, v18.20.0 | Cambio de Afirmaciones de Importación a Atributos de Importación. |
v17.1.0, v16.14.0 | Agregado en: v17.1.0, v16.14.0 |
[Estable: 2 - Estable]
Estable: 2 Estabilidad: 2 - Estable
Los atributos de importación son una sintaxis en línea para las declaraciones de importación de módulos para pasar más información junto con el especificador del módulo.
import fooData from './foo.json' with { type: 'json' };
const { default: barData } =
await import('./bar.json', { with: { type: 'json' } });
Node.js solo admite el atributo type
, para el cual admite los siguientes valores:
Atributo type | Necesario para |
---|---|
'json' | Módulos JSON |
El atributo type: 'json'
es obligatorio al importar módulos JSON.
Módulos integrados
Los módulos integrados proporcionan exportaciones con nombre de su API pública. También se proporciona una exportación predeterminada que es el valor de las exportaciones de CommonJS. La exportación predeterminada se puede utilizar, entre otras cosas, para modificar las exportaciones con nombre. Las exportaciones con nombre de los módulos integrados se actualizan solo llamando a module.syncBuiltinESMExports()
.
import EventEmitter from 'node:events'
const e = new EventEmitter()
import { readFile } from 'node:fs'
readFile('./foo.txt', (err, source) => {
if (err) {
console.error(err)
} else {
console.log(source)
}
})
import fs, { readFileSync } from 'node:fs'
import { syncBuiltinESMExports } from 'node:module'
import { Buffer } from 'node:buffer'
fs.readFileSync = () => Buffer.from('Hola, ESM')
syncBuiltinESMExports()
fs.readFileSync === readFileSync
Expresiones import()
El import dinámico import()
es compatible tanto en CommonJS como en los módulos ES. En los módulos CommonJS, se puede utilizar para cargar módulos ES.
import.meta
La metapropiedad import.meta
es un Object
que contiene las siguientes propiedades. Solo se admite en módulos ES.
import.meta.dirname
Agregado en: v21.2.0, v20.11.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.2 - Candidato a lanzamiento
- <string> El nombre del directorio del módulo actual. Es lo mismo que
path.dirname()
deimport.meta.filename
.
import.meta.filename
Agregado en: v21.2.0, v20.11.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.2 - Candidato a lanzamiento
- <string> La ruta absoluta completa y el nombre de archivo del módulo actual, con los enlaces simbólicos resueltos.
- Esto es lo mismo que
url.fileURLToPath()
deimport.meta.url
.
import.meta.url
- <string> La URL
file:
absoluta del módulo.
Esto se define exactamente igual que en los navegadores, proporcionando la URL del archivo del módulo actual.
Esto permite patrones útiles, como la carga de archivos relativos:
import { readFileSync } from 'node:fs'
const buffer = readFileSync(new URL('./data.proto', import.meta.url))
import.meta.resolve(specifier)
[Historial]
Versión | Cambios |
---|---|
v20.6.0, v18.19.0 | Ya no está detrás del indicador CLI --experimental-import-meta-resolve , excepto para el parámetro no estándar parentURL . |
v20.6.0, v18.19.0 | Esta API ya no lanza un error cuando se dirigen a URLs file: que no se mapean a un archivo existente en el sistema de archivos local. |
v20.0.0, v18.19.0 | Esta API ahora devuelve una cadena de forma síncrona en lugar de una Promesa. |
v16.2.0, v14.18.0 | Se agregó soporte para el objeto WHATWG URL al parámetro parentURL . |
v13.9.0, v12.16.2 | Agregado en: v13.9.0, v12.16.2 |
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.2 - Candidato a lanzamiento
specifier
<string> El especificador de módulo para resolver en relación con el módulo actual.- Devuelve: <string> La cadena de URL absoluta a la que se resolvería el especificador.
import.meta.resolve
es una función de resolución relativa al módulo con ámbito en cada módulo, que devuelve la cadena de URL.
const dependencyAsset = import.meta.resolve('component-lib/asset.css')
// file:///app/node_modules/component-lib/asset.css
import.meta.resolve('./dep.js')
// file:///app/dep.js
Todas las características de la resolución de módulos de Node.js son compatibles. Las resoluciones de dependencias están sujetas a las resoluciones de exportaciones permitidas dentro del paquete.
Advertencias:
- Esto puede resultar en operaciones síncronas del sistema de archivos, lo que puede afectar el rendimiento de manera similar a
require.resolve
. - Esta característica no está disponible dentro de los cargadores personalizados (crearía un interbloqueo).
API no estándar:
Cuando se utiliza el indicador --experimental-import-meta-resolve
, esa función acepta un segundo argumento:
parent
<string> | <URL> Una URL de módulo principal absoluta opcional desde la cual resolver. Predeterminado:import.meta.url
Interoperabilidad con CommonJS
Sentencias import
Una sentencia import
puede hacer referencia a un módulo ES o a un módulo CommonJS. Las sentencias import
solo están permitidas en módulos ES, pero las expresiones dinámicas import()
son compatibles con CommonJS para cargar módulos ES.
Al importar módulos CommonJS, el objeto module.exports
se proporciona como la exportación por defecto. Las exportaciones con nombre pueden estar disponibles, proporcionadas por un análisis estático como una conveniencia para una mejor compatibilidad con el ecosistema.
require
El módulo CommonJS require
actualmente solo admite la carga de módulos ES síncronos (es decir, módulos ES que no utilizan await
de nivel superior).
Consulte Cargar módulos ECMAScript usando require()
para obtener más detalles.
Espacios de nombres CommonJS
[Historial]
Versión | Cambios |
---|---|
v23.0.0 | Se agregó el marcador de exportación 'module.exports' a los espacios de nombres CJS. |
v14.13.0 | Agregado en: v14.13.0 |
Los módulos CommonJS consisten en un objeto module.exports
que puede ser de cualquier tipo.
Para admitir esto, al importar CommonJS desde un módulo ECMAScript, se construye un envoltorio de espacio de nombres para el módulo CommonJS, que siempre proporciona una clave de exportación default
que apunta al valor module.exports
de CommonJS.
Además, se realiza un análisis estático heurístico en el texto fuente del módulo CommonJS para obtener una lista estática de exportaciones del mejor esfuerzo para proporcionar en el espacio de nombres a partir de los valores de module.exports
. Esto es necesario ya que estos espacios de nombres deben construirse antes de la evaluación del módulo CJS.
Estos objetos de espacio de nombres CommonJS también proporcionan la exportación default
como una exportación con nombre 'module.exports'
, para indicar inequívocamente que su representación en CommonJS utiliza este valor y no el valor del espacio de nombres. Esto refleja la semántica del manejo del nombre de exportación 'module.exports'
en el soporte de interop require(esm)
.
Al importar un módulo CommonJS, se puede importar de forma fiable utilizando la importación predeterminada del módulo ES o su sintaxis de azúcar correspondiente:
import { default as cjs } from 'cjs'
// Idéntico a lo anterior
import cjsSugar from 'cjs'
console.log(cjs)
console.log(cjs === cjsSugar)
// Imprime:
// <module.exports>
// true
Este Objeto Exótico del Espacio de Nombres del Módulo se puede observar directamente ya sea cuando se usa import * as m from 'cjs'
o una importación dinámica:
import * as m from 'cjs'
console.log(m)
console.log(m === (await import('cjs')))
// Imprime:
// [Module] { default: <module.exports>, 'module.exports': <module.exports> }
// true
Para una mejor compatibilidad con el uso existente en el ecosistema JS, Node.js además intenta determinar las exportaciones con nombre CommonJS de cada módulo CommonJS importado para proporcionarlas como exportaciones de módulos ES separadas utilizando un proceso de análisis estático.
Por ejemplo, considere un módulo CommonJS escrito:
// cjs.cjs
exports.name = 'exported'
El módulo anterior admite importaciones con nombre en módulos ES:
import { name } from './cjs.cjs'
console.log(name)
// Imprime: 'exported'
import cjs from './cjs.cjs'
console.log(cjs)
// Imprime: { name: 'exported' }
import * as m from './cjs.cjs'
console.log(m)
// Imprime:
// [Module] {
// default: { name: 'exported' },
// 'module.exports': { name: 'exported' },
// name: 'exported'
// }
Como se puede ver en el último ejemplo del Objeto Exótico del Espacio de Nombres del Módulo que se está registrando, la exportación name
se copia del objeto module.exports
y se establece directamente en el espacio de nombres del módulo ES cuando se importa el módulo.
Las actualizaciones de enlace en vivo o las nuevas exportaciones agregadas a module.exports
no se detectan para estas exportaciones con nombre.
La detección de exportaciones con nombre se basa en patrones de sintaxis comunes, pero no siempre detecta correctamente las exportaciones con nombre. En estos casos, usar el formulario de importación predeterminado descrito anteriormente puede ser una mejor opción.
La detección de exportaciones con nombre cubre muchos patrones de exportación comunes, patrones de reexportación y salidas de herramientas de construcción y transpiladores. Consulte cjs-module-lexer para conocer la semántica exacta implementada.
Diferencias entre los módulos ES y CommonJS
No require
, exports
ni module.exports
En la mayoría de los casos, el import
del módulo ES se puede usar para cargar módulos CommonJS.
Si es necesario, se puede construir una función require
dentro de un módulo ES usando module.createRequire()
.
No __filename
ni __dirname
Estas variables de CommonJS no están disponibles en los módulos ES.
Los casos de uso de __filename
y __dirname
se pueden replicar a través de import.meta.filename
e import.meta.dirname
.
No se admite la carga de complementos (Addons)
Actualmente, los complementos no son compatibles con las importaciones de módulos ES.
En su lugar, se pueden cargar con module.createRequire()
o process.dlopen
.
No require.resolve
La resolución relativa se puede manejar a través de new URL('./local', import.meta.url)
.
Para un reemplazo completo de require.resolve
, existe la API import.meta.resolve.
Alternativamente, se puede usar module.createRequire()
.
Sin NODE_PATH
NODE_PATH
no es parte de la resolución de especificadores de import
. Por favor, usa enlaces simbólicos si deseas este comportamiento.
Sin require.extensions
require.extensions
no es usado por import
. Los hooks de personalización de módulos pueden proporcionar un reemplazo.
Sin require.cache
require.cache
no es usado por import
ya que el cargador de módulos ES tiene su propia caché separada.
Módulos JSON
[Historia]
Versión | Cambios |
---|---|
v23.1.0 | Los módulos JSON ya no son experimentales. |
[Estable: 2 - Estable]
Estable: 2 Estabilidad: 2 - Estable
Se puede hacer referencia a los archivos JSON con import
:
import packageConfig from './package.json' with { type: 'json' };
La sintaxis with { type: 'json' }
es obligatoria; ver Atributos de Importación.
El JSON importado solo expone una exportación default
. No hay soporte para exportaciones con nombre. Se crea una entrada en la caché de CommonJS para evitar la duplicación. El mismo objeto se devuelve en CommonJS si el módulo JSON ya ha sido importado desde la misma ruta.
Módulos Wasm
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1 - Experimental
La importación de módulos WebAssembly es compatible bajo la bandera --experimental-wasm-modules
, permitiendo que cualquier archivo .wasm
sea importado como módulos normales, a la vez que soporta las importaciones de sus propios módulos.
Esta integración está en línea con la Propuesta de Integración de Módulos ES para WebAssembly.
Por ejemplo, un index.mjs
que contenga:
import * as M from './module.wasm'
console.log(M)
ejecutado bajo:
node --experimental-wasm-modules index.mjs
proporcionaría la interfaz de exportaciones para la instanciación de module.wasm
.
await
de nivel superior
Añadido en: v14.8.0
La palabra clave await
puede usarse en el cuerpo de nivel superior de un módulo ECMAScript.
Asumiendo un a.mjs
con
export const five = await Promise.resolve(5)
Y un b.mjs
con
import { five } from './a.mjs'
console.log(five) // Muestra `5`
node b.mjs # funciona
Si una expresión await
de nivel superior nunca se resuelve, el proceso node
saldrá con un código de estado 13
.
import { spawn } from 'node:child_process'
import { execPath } from 'node:process'
spawn(execPath, [
'--input-type=module',
'--eval',
// Promesa que nunca se resuelve:
'await new Promise(() => {})',
]).once('exit', code => {
console.log(code) // Muestra `13`
})
Cargadores
La documentación anterior sobre Cargadores ahora se encuentra en Módulos: Hooks de personalización.
Algoritmo de resolución y carga
Características
El resolutor predeterminado tiene las siguientes propiedades:
- Resolución basada en FileURL tal como se utiliza en los módulos ES
- Resolución de URL relativa y absoluta
- Sin extensiones predeterminadas
- Sin principales de carpeta
- Búsqueda de resolución de paquetes de especificadores sin formato a través de node_modules
- No falla en extensiones o protocolos desconocidos
- Opcionalmente puede proporcionar una sugerencia del formato para la fase de carga
El cargador predeterminado tiene las siguientes propiedades
- Soporte para la carga de módulos incorporados a través de URL
node:
- Soporte para la carga de módulos "en línea" a través de URL
data:
- Soporte para la carga de módulos
file:
- Falla en cualquier otro protocolo de URL
- Falla en extensiones desconocidas para la carga
file:
(solo admite.cjs
,.js
y.mjs
)
Algoritmo de resolución
El algoritmo para cargar un especificador de módulo ES se proporciona a través del método ESM_RESOLVE a continuación. Devuelve la URL resuelta para un especificador de módulo en relación con una parentURL.
El algoritmo de resolución determina la URL resuelta completa para una carga de módulo, junto con su formato de módulo sugerido. El algoritmo de resolución no determina si el protocolo de URL resuelto se puede cargar, o si las extensiones de archivo están permitidas, en cambio, estas validaciones son aplicadas por Node.js durante la fase de carga (por ejemplo, si se solicitó cargar una URL que tiene un protocolo que no es file:
, data:
o node:
).
El algoritmo también intenta determinar el formato del archivo en función de la extensión (consulte el algoritmo ESM_FILE_FORMAT
a continuación). Si no reconoce la extensión del archivo (por ejemplo, si no es .mjs
, .cjs
o .json
), entonces se devuelve un formato de undefined
, que se lanzará durante la fase de carga.
El algoritmo para determinar el formato del módulo de una URL resuelta se proporciona mediante ESM_FILE_FORMAT, que devuelve el formato de módulo único para cualquier archivo. El formato "module" se devuelve para un Módulo ECMAScript, mientras que el formato "commonjs" se utiliza para indicar la carga a través del cargador CommonJS heredado. En futuras actualizaciones se pueden ampliar formatos adicionales como "addon".
En los siguientes algoritmos, todos los errores de subrutina se propagan como errores de estas rutinas de nivel superior a menos que se indique lo contrario.
defaultConditions es la matriz de nombres de entorno condicional, ["node", "import"]
.
El resolutor puede lanzar los siguientes errores:
- Especificador de módulo no válido: el especificador de módulo es una URL, un nombre de paquete o un especificador de subruta de paquete no válidos.
- Configuración de paquete no válida: la configuración de package.json no es válida o contiene una configuración no válida.
- Destino de paquete no válido: las exportaciones o importaciones de paquetes definen un módulo de destino para el paquete que es un tipo no válido o un destino de cadena.
- Ruta de paquete no exportada: las exportaciones de paquetes no definen o permiten una subruta de destino en el paquete para el módulo dado.
- Importación de paquete no definida: las importaciones de paquetes no definen el especificador.
- Módulo no encontrado: el paquete o módulo solicitado no existe.
- Importación de directorio no admitida: la ruta resuelta corresponde a un directorio, que no es un destino admitido para las importaciones de módulos.
Especificación del Algoritmo de Resolución
ESM_RESOLVE(especificador, URLpadre)
PACKAGE_RESOLVE(especificadorPaquete, URLpadre)
PACKAGE_SELF_RESOLVE(nombrePaquete, subrutaPaquete, URLpadre)
PACKAGE_EXPORTS_RESOLVE(URLpaquete, subruta, exports, condiciones)
PACKAGE_IMPORTS_RESOLVE(especificador, URLpadre, condiciones)
PACKAGE_IMPORTS_EXPORTS_RESOLVE(claveCoincidente, objetoCoincidente, URLpaquete, esImports, condiciones)
PATTERN_KEY_COMPARE(claveA, claveB)
PACKAGE_TARGET_RESOLVE(URLpaquete, objetivo, patrónCoincidente, esImports, condiciones)
ESM_FILE_FORMAT(url)
LOOKUP_PACKAGE_SCOPE(url)
READ_PACKAGE_JSON(URLpaquete)
DETECT_MODULE_SYNTAX(fuente)
Personalización del algoritmo de resolución de especificadores ESM
Los hooks de personalización de módulos proporcionan un mecanismo para personalizar el algoritmo de resolución de especificadores ESM. Un ejemplo que proporciona la resolución al estilo CommonJS para los especificadores ESM es commonjs-extension-resolution-loader.