Módulos: API node:module
Añadido en: v0.3.7
El objeto Module
Proporciona métodos de utilidad general al interactuar con instancias de Module
, la variable module
que se ve a menudo en los módulos CommonJS. Se accede a través de import 'node:module'
o require('node:module')
.
module.builtinModules
[Historial]
Versión | Cambios |
---|---|
v23.5.0 | La lista ahora también contiene módulos solo de prefijo. |
v9.3.0, v8.10.0, v6.13.0 | Añadido en: v9.3.0, v8.10.0, v6.13.0 |
Una lista de los nombres de todos los módulos proporcionados por Node.js. Se puede usar para verificar si un módulo es mantenido por un tercero o no.
module
en este contexto no es el mismo objeto que proporciona el envoltorio de módulo. Para acceder a él, requiere el módulo Module
:
// module.mjs
// En un módulo ECMAScript
import { builtinModules as builtin } from 'node:module'
// module.cjs
// En un módulo CommonJS
const builtin = require('node:module').builtinModules
module.createRequire(filename)
Agregado en: v12.2.0
filename
<string> | <URL> Nombre de archivo que se utilizará para construir la función require. Debe ser un objeto URL de archivo, una cadena URL de archivo o una cadena de ruta absoluta.- Devuelve: <require> Función Require
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
// sibling-module.js es un módulo CommonJS.
const siblingModule = require('./sibling-module')
module.findPackageJSON(specifier[, base])
Agregado en: v23.2.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.1 - Desarrollo Activo
specifier
<string> | <URL> El especificador del módulo cuyopackage.json
se va a recuperar. Al pasar un especificador desnudo, se devuelve elpackage.json
en la raíz del paquete. Al pasar un especificador relativo o un especificador absoluto, se devuelve elpackage.json
principal más cercano.base
<string> | <URL> La ubicación absoluta (cadena URLfile:
o ruta del sistema de archivos) del módulo contenedor. Para CJS, use__filename
(¡no__dirname
!); para ESM, useimport.meta.url
. No necesita pasarlo sispecifier
es unespecificador absoluto
.- Devuelve: <string> | <undefined> Una ruta si se encuentra el
package.json
. CuandostartLocation
es un paquete, elpackage.json
raíz del paquete; cuando es relativo o no resuelto, elpackage.json
más cercano astartLocation
.
/path/to/project
├ packages/
├ bar/
├ bar.js
└ package.json // name = '@foo/bar'
└ qux/
├ node_modules/
└ some-package/
└ package.json // name = 'some-package'
├ qux.js
└ package.json // name = '@foo/qux'
├ main.js
└ package.json // name = '@foo'
// /path/to/project/packages/bar/bar.js
import { findPackageJSON } from 'node:module'
findPackageJSON('..', import.meta.url)
// '/path/to/project/package.json'
// Mismo resultado al pasar un especificador absoluto en su lugar:
findPackageJSON(new URL('../', import.meta.url))
findPackageJSON(import.meta.resolve('../'))
findPackageJSON('some-package', import.meta.url)
// '/path/to/project/packages/bar/node_modules/some-package/package.json'
// Al pasar un especificador absoluto, es posible que obtenga un resultado diferente si
// el módulo resuelto está dentro de una subcarpeta que tiene anidado `package.json`.
findPackageJSON(import.meta.resolve('some-package'))
// '/path/to/project/packages/bar/node_modules/some-package/some-subfolder/package.json'
findPackageJSON('@foo/qux', import.meta.url)
// '/path/to/project/packages/qux/package.json'
// /path/to/project/packages/bar/bar.js
const { findPackageJSON } = require('node:module')
const { pathToFileURL } = require('node:url')
const path = require('node:path')
findPackageJSON('..', __filename)
// '/path/to/project/package.json'
// Mismo resultado al pasar un especificador absoluto en su lugar:
findPackageJSON(pathToFileURL(path.join(__dirname, '..')))
findPackageJSON('some-package', __filename)
// '/path/to/project/packages/bar/node_modules/some-package/package.json'
// Al pasar un especificador absoluto, es posible que obtenga un resultado diferente si
// el módulo resuelto está dentro de una subcarpeta que tiene anidado `package.json`.
findPackageJSON(pathToFileURL(require.resolve('some-package')))
// '/path/to/project/packages/bar/node_modules/some-package/some-subfolder/package.json'
findPackageJSON('@foo/qux', __filename)
// '/path/to/project/packages/qux/package.json'
module.isBuiltin(moduleName)
Agregado en: v18.6.0, v16.17.0
moduleName
<string> nombre del módulo- Devuelve: <boolean> devuelve true si el módulo es integrado; de lo contrario, devuelve false
import { isBuiltin } from 'node:module'
isBuiltin('node:fs') // true
isBuiltin('fs') // true
isBuiltin('wss') // false
module.register(specifier[, parentURL][, options])
[Historial]
Versión | Cambios |
---|---|
v20.8.0, v18.19.0 | Agregado soporte para instancias de URL WHATWG. |
v20.6.0, v18.19.0 | Agregado en: v20.6.0, v18.19.0 |
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.2 - Candidato a lanzamiento
specifier
<string> | <URL> Hooks de personalización que se registrarán; esta debe ser la misma cadena que se pasaría aimport()
, excepto que si es relativa, se resuelve con relación aparentURL
.parentURL
<string> | <URL> Si deseas resolverspecifier
con relación a una URL base, comoimport.meta.url
, puedes pasar esa URL aquí. Predeterminado:'data:'
options
<Objeto>parentURL
<string> | <URL> Si deseas resolverspecifier
con relación a una URL base, comoimport.meta.url
, puedes pasar esa URL aquí. Esta propiedad se ignora siparentURL
se proporciona como el segundo argumento. Predeterminado:'data:'
data
<any> Cualquier valor de JavaScript arbitrario y clonable que se pasará al hookinitialize
.transferList
<Objeto[]> objetos transferibles que se pasarán al hookinitialize
.
Registra un módulo que exporta hooks que personalizan la resolución de módulos de Node.js y el comportamiento de carga. Consulta Hooks de personalización.
module.registerHooks(options)
Agregado en: v23.5.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.1 - Desarrollo activo
options
<Object>load
<Function> | <undefined> Ver gancho de carga. Predeterminado:undefined
.resolve
<Function> | <undefined> Ver gancho de resolución. Predeterminado:undefined
.
Registra ganchos que personalizan la resolución de módulos de Node.js y el comportamiento de carga. Ver ganchos de personalización.
module.stripTypeScriptTypes(code[, options])
Añadido en: v23.2.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.1 - Desarrollo activo
code
<string> El código del que se eliminarán las anotaciones de tipo.options
<Object>mode
<string> Predeterminado:'strip'
. Los valores posibles son:'strip'
Eliminar solo las anotaciones de tipo sin realizar la transformación de las características de TypeScript.'transform'
Eliminar las anotaciones de tipo y transformar las características de TypeScript a JavaScript.sourceMap
<boolean> Predeterminado:false
. Solo cuandomode
es'transform'
, si estrue
, se generará un mapa de origen para el código transformado.sourceUrl
<string> Especifica la URL de origen utilizada en el mapa de origen.
Devuelve: <string> El código con las anotaciones de tipo eliminadas.
module.stripTypeScriptTypes()
elimina las anotaciones de tipo del código TypeScript. Se puede utilizar para eliminar las anotaciones de tipo del código TypeScript antes de ejecutarlo convm.runInContext()
ovm.compileFunction()
. De forma predeterminada, lanzará un error si el código contiene características de TypeScript que requieren transformación comoEnums
, consulte eliminación de tipos para obtener más información. Cuando el modo es'transform'
, también transforma las características de TypeScript a JavaScript, consulte transformar las características de TypeScript para obtener más información. Cuando el modo es'strip'
, no se generan mapas de origen, porque las ubicaciones se conservan. Si se proporcionasourceMap
, cuando el modo es'strip'
, se lanzará un error.
ADVERTENCIA: La salida de esta función no debe considerarse estable entre las versiones de Node.js, debido a los cambios en el analizador de TypeScript.
import { stripTypeScriptTypes } from 'node:module'
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code)
console.log(strippedCode)
// Imprime: const a = 1;
const { stripTypeScriptTypes } = require('node:module')
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code)
console.log(strippedCode)
// Imprime: const a = 1;
Si se proporciona sourceUrl
, se utilizará y se adjuntará como un comentario al final de la salida:
import { stripTypeScriptTypes } from 'node:module'
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' })
console.log(strippedCode)
// Imprime: const a = 1\n\n//# sourceURL=source.ts;
const { stripTypeScriptTypes } = require('node:module')
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' })
console.log(strippedCode)
// Imprime: const a = 1\n\n//# sourceURL=source.ts;
Cuando mode
es 'transform'
, el código se transforma a JavaScript:
import { stripTypeScriptTypes } from 'node:module'
const code = `
namespace MathUtil {
export const add = (a: number, b: number) => a + b;
}`
const strippedCode = stripTypeScriptTypes(code, { mode: 'transform', sourceMap: true })
console.log(strippedCode)
// Imprime:
// var MathUtil;
// (function(MathUtil) {
// MathUtil.add = (a, b)=>a + b;
// })(MathUtil || (MathUtil = {}));
// # sourceMappingURL=data:application/json;base64, ...
const { stripTypeScriptTypes } = require('node:module')
const code = `
namespace MathUtil {
export const add = (a: number, b: number) => a + b;
}`
const strippedCode = stripTypeScriptTypes(code, { mode: 'transform', sourceMap: true })
console.log(strippedCode)
// Imprime:
// var MathUtil;
// (function(MathUtil) {
// MathUtil.add = (a, b)=>a + b;
// })(MathUtil || (MathUtil = {}));
// # sourceMappingURL=data:application/json;base64, ...
module.syncBuiltinESMExports()
Añadido en: v12.12.0
El método module.syncBuiltinESMExports()
actualiza todos los enlaces activos para los Módulos ES incorporados para que coincidan con las propiedades de las exportaciones CommonJS. No añade ni elimina nombres exportados de los Módulos ES.
const fs = require('node:fs')
const assert = require('node:assert')
const { syncBuiltinESMExports } = require('node:module')
fs.readFile = newAPI
delete fs.readFileSync
function newAPI() {
// ...
}
fs.newAPI = newAPI
syncBuiltinESMExports()
import('node:fs').then(esmFS => {
// Sincroniza la propiedad readFile existente con el nuevo valor
assert.strictEqual(esmFS.readFile, newAPI)
// readFileSync ha sido eliminado del fs requerido
assert.strictEqual('readFileSync' in fs, false)
// syncBuiltinESMExports() no elimina readFileSync de esmFS
assert.strictEqual('readFileSync' in esmFS, true)
// syncBuiltinESMExports() no añade nombres
assert.strictEqual(esmFS.newAPI, undefined)
})
Caché de compilación de módulos
[Historial]
Versión | Cambios |
---|---|
v22.8.0 | Agrega las API de JavaScript iniciales para el acceso en tiempo de ejecución. |
v22.1.0 | Agregado en: v22.1.0 |
El caché de compilación de módulos se puede habilitar utilizando el método module.enableCompileCache()
o la variable de entorno NODE_COMPILE_CACHE=dir
. Después de habilitarse, cada vez que Node.js compile un módulo CommonJS o un módulo ECMAScript, utilizará el caché de código V8 en disco persistido en el directorio especificado para acelerar la compilación. Esto puede ralentizar la primera carga de un gráfico de módulos, pero las cargas posteriores del mismo gráfico de módulos pueden obtener una aceleración significativa si el contenido de los módulos no cambia.
Para limpiar el caché de compilación generado en el disco, simplemente elimina el directorio de caché. El directorio de caché se volverá a crear la próxima vez que se utilice el mismo directorio para el almacenamiento de caché de compilación. Para evitar llenar el disco con caché obsoleta, se recomienda usar un directorio en os.tmpdir()
. Si el caché de compilación se habilita mediante una llamada a module.enableCompileCache()
sin especificar el directorio, Node.js utilizará la variable de entorno NODE_COMPILE_CACHE=dir
si está establecida, o el valor predeterminado path.join(os.tmpdir(), 'node-compile-cache')
en caso contrario. Para ubicar el directorio de caché de compilación utilizado por una instancia de Node.js en ejecución, utiliza module.getCompileCacheDir()
.
Actualmente, cuando se utiliza el caché de compilación con la cobertura de código JavaScript V8, la cobertura que recopila V8 puede ser menos precisa en las funciones que se deserializan desde el caché de código. Se recomienda desactivar esto al ejecutar pruebas para generar una cobertura precisa.
El caché de compilación de módulos habilitado se puede deshabilitar mediante la variable de entorno NODE_DISABLE_COMPILE_CACHE=1
. Esto puede ser útil cuando el caché de compilación conduce a comportamientos inesperados o no deseados (por ejemplo, una cobertura de prueba menos precisa).
El caché de compilación generado por una versión de Node.js no se puede reutilizar en una versión diferente de Node.js. El caché generado por diferentes versiones de Node.js se almacenará por separado si se utiliza el mismo directorio base para persistir el caché, por lo que pueden coexistir.
Por el momento, cuando el caché de compilación está habilitado y un módulo se carga de nuevo, el caché de código se genera a partir del código compilado inmediatamente, pero solo se escribirá en el disco cuando la instancia de Node.js esté a punto de salir. Esto está sujeto a cambios. El método module.flushCompileCache()
se puede utilizar para asegurar que el caché de código acumulado se vacíe al disco en caso de que la aplicación quiera generar otras instancias de Node.js y permitirles compartir el caché mucho antes de que el padre salga.
module.constants.compileCacheStatus
Agregado en: v22.8.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.1 - Desarrollo Activo
Las siguientes constantes se devuelven como el campo status
en el objeto devuelto por module.enableCompileCache()
para indicar el resultado del intento de habilitar la caché de compilación de módulos.
Constante | Descripción |
---|---|
ENABLED | Node.js ha habilitado la caché de compilación correctamente. El directorio utilizado para almacenar la caché de compilación se devolverá en el campo directory en el objeto devuelto. |
ALREADY_ENABLED | La caché de compilación ya se ha habilitado antes, ya sea mediante una llamada anterior a module.enableCompileCache() o mediante la variable de entorno NODE_COMPILE_CACHE=dir . El directorio utilizado para almacenar la caché de compilación se devolverá en el campo directory en el objeto devuelto. |
FAILED | Node.js no puede habilitar la caché de compilación. Esto puede deberse a la falta de permiso para usar el directorio especificado o a varios tipos de errores del sistema de archivos. El detalle del error se devolverá en el campo message en el objeto devuelto. |
DISABLED | Node.js no puede habilitar la caché de compilación porque se ha establecido la variable de entorno NODE_DISABLE_COMPILE_CACHE=1 . |
module.enableCompileCache([cacheDir])
Agregado en: v22.8.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.1 - Desarrollo Activo
cacheDir
<string> | <undefined> Ruta opcional para especificar el directorio donde se almacenará/recuperará la caché de compilación.- Devuelve: <Object>
status
<integer> Uno de losmodule.constants.compileCacheStatus
message
<string> | <undefined> Si Node.js no puede habilitar la caché de compilación, esto contiene el mensaje de error. Solo se establece sistatus
esmodule.constants.compileCacheStatus.FAILED
.directory
<string> | <undefined> Si la caché de compilación está habilitada, esto contiene el directorio donde se almacena la caché de compilación. Solo se establece sistatus
esmodule.constants.compileCacheStatus.ENABLED
omodule.constants.compileCacheStatus.ALREADY_ENABLED
.
Habilita la caché de compilación de módulos en la instancia actual de Node.js.
Si no se especifica cacheDir
, Node.js utilizará el directorio especificado por la variable de entorno NODE_COMPILE_CACHE=dir
si está establecida, o utilizará path.join(os.tmpdir(), 'node-compile-cache')
de lo contrario. Para casos de uso generales, se recomienda llamar a module.enableCompileCache()
sin especificar cacheDir
, de modo que el directorio pueda ser sobrescrito por la variable de entorno NODE_COMPILE_CACHE
cuando sea necesario.
Dado que se supone que la caché de compilación es una optimización silenciosa que no es necesaria para que la aplicación sea funcional, este método está diseñado para no generar ninguna excepción cuando la caché de compilación no se pueda habilitar. En cambio, devolverá un objeto que contiene un mensaje de error en el campo message
para ayudar en la depuración. Si la caché de compilación se habilita correctamente, el campo directory
en el objeto devuelto contiene la ruta al directorio donde se almacena la caché de compilación. El campo status
en el objeto devuelto sería uno de los valores de module.constants.compileCacheStatus
para indicar el resultado del intento de habilitar la caché de compilación de módulos.
Este método solo afecta a la instancia actual de Node.js. Para habilitarlo en subprocesos de trabajo secundarios, llame a este método también en subprocesos de trabajo secundarios, o establezca el valor de process.env.NODE_COMPILE_CACHE
al directorio de la caché de compilación para que el comportamiento pueda heredarse en los trabajadores secundarios. El directorio se puede obtener del campo directory
devuelto por este método, o con module.getCompileCacheDir()
.
module.flushCompileCache()
Agregado en: v23.0.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.1 - Desarrollo Activo
Vacía la caché de compilación de módulos acumulada de los módulos ya cargados en la instancia actual de Node.js en el disco. Esto se devuelve después de que todas las operaciones de vaciado del sistema de archivos llegan a su fin, sin importar si tienen éxito o no. Si hay algún error, esto fallará silenciosamente, ya que los errores de caché de compilación no deberían interferir con el funcionamiento real de la aplicación.
module.getCompileCacheDir()
Agregado en: v22.8.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.1 - Desarrollo Activo
- Devuelve: <string> | <undefined> Ruta al directorio de la caché de compilación de módulos si está habilitada, o
undefined
en caso contrario.
Ganchos de personalización
[Historial]
Versión | Cambios |
---|---|
v23.5.0 | Se agregó soporte para ganchos síncronos y en subprocesos. |
v20.6.0, v18.19.0 | Se agregó el gancho initialize para reemplazar globalPreload . |
v18.6.0, v16.17.0 | Se agregó soporte para encadenar cargadores. |
v16.12.0 | Se eliminaron getFormat , getSource , transformSource y globalPreload ; se agregó el gancho load y el gancho getGlobalPreload . |
v8.8.0 | Se agregó en: v8.8.0 |
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.2 - Candidato a versión (versión asíncrona) Estabilidad: 1.1 - Desarrollo activo (versión síncrona)
Actualmente, se admiten dos tipos de ganchos de personalización de módulos:
Habilitación
La resolución y carga de módulos se pueden personalizar mediante:
Los ganchos se pueden registrar antes de que se ejecute el código de la aplicación utilizando el flag --import
o --require
:
node --import ./register-hooks.js ./my-app.js
node --require ./register-hooks.js ./my-app.js
// register-hooks.js
// Este archivo solo se puede require()-ar si no contiene await de nivel superior.
// Utilice module.register() para registrar ganchos asíncronos en un hilo dedicado.
import { register } from 'node:module'
register('./hooks.mjs', import.meta.url)
// register-hooks.js
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
// Utilice module.register() para registrar ganchos asíncronos en un hilo dedicado.
register('./hooks.mjs', pathToFileURL(__filename))
// Utilice module.registerHooks() para registrar ganchos síncronos en el hilo principal.
import { registerHooks } from 'node:module'
registerHooks({
resolve(specifier, context, nextResolve) {
/* implementación */
},
load(url, context, nextLoad) {
/* implementación */
},
})
// Utilice module.registerHooks() para registrar ganchos síncronos en el hilo principal.
const { registerHooks } = require('node:module')
registerHooks({
resolve(specifier, context, nextResolve) {
/* implementación */
},
load(url, context, nextLoad) {
/* implementación */
},
})
El archivo pasado a --import
o --require
también puede ser una exportación de una dependencia:
node --import some-package/register ./my-app.js
node --require some-package/register ./my-app.js
Donde some-package
tiene un campo "exports"
que define la exportación /register
para mapear a un archivo que llama a register()
, como el siguiente ejemplo register-hooks.js
.
Usar --import
o --require
garantiza que los ganchos se registren antes de que se importe cualquier archivo de aplicación, incluido el punto de entrada de la aplicación y para cualquier hilo de trabajador de forma predeterminada también.
Alternativamente, register()
y registerHooks()
se pueden llamar desde el punto de entrada, aunque se debe usar import()
dinámico para cualquier código ESM que deba ejecutarse después de que se registren los ganchos.
import { register } from 'node:module'
register('http-to-https', import.meta.url)
// Dado que esto es un `import()` dinámico, los ganchos `http-to-https` se ejecutarán
// para manejar `./my-app.js` y cualquier otro archivo que importe o requiera.
await import('./my-app.js')
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
register('http-to-https', pathToFileURL(__filename))
// Dado que esto es un `import()` dinámico, los ganchos `http-to-https` se ejecutarán
// para manejar `./my-app.js` y cualquier otro archivo que importe o requiera.
import('./my-app.js')
Los ganchos de personalización se ejecutarán para cualquier módulo cargado después del registro y los módulos a los que hacen referencia a través de import
y el require
incorporado. La función require
creada por los usuarios usando module.createRequire()
solo se puede personalizar con los ganchos síncronos.
En este ejemplo, estamos registrando los ganchos http-to-https
, pero solo estarán disponibles para los módulos importados posteriormente; en este caso, my-app.js
y cualquier cosa a la que haga referencia a través de import
o require
incorporado en las dependencias de CommonJS.
Si el import('./my-app.js')
hubiera sido en cambio un import './my-app.js'
estático, la aplicación ya se habría cargado antes de que se registraran los ganchos http-to-https
. Esto se debe a la especificación de módulos ES, donde las importaciones estáticas se evalúan primero desde las hojas del árbol, luego de vuelta al tronco. Puede haber importaciones estáticas dentro de my-app.js
, que no se evaluarán hasta que my-app.js
se importe dinámicamente.
Si se utilizan ganchos síncronos, se admiten tanto import
, require
como require
de usuario creado usando createRequire()
.
import { registerHooks, createRequire } from 'node:module'
registerHooks({
/* implementación de ganchos síncronos */
})
const require = createRequire(import.meta.url)
// Los ganchos síncronos afectan a import, require() y a la función require() de usuario
// creada a través de createRequire().
await import('./my-app.js')
require('./my-app-2.js')
const { register, registerHooks } = require('node:module')
const { pathToFileURL } = require('node:url')
registerHooks({
/* implementación de ganchos síncronos */
})
const userRequire = createRequire(__filename)
// Los ganchos síncronos afectan a import, require() y a la función require() de usuario
// creada a través de createRequire().
import('./my-app.js')
require('./my-app-2.js')
userRequire('./my-app-3.js')
Finalmente, si todo lo que quiere hacer es registrar ganchos antes de que se ejecute su aplicación y no desea crear un archivo separado para ese propósito, puede pasar una URL data:
a --import
:
node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("http-to-https", pathToFileURL("./"));' ./my-app.js
Encadenamiento
Es posible llamar a register
más de una vez:
// entrypoint.mjs
import { register } from 'node:module'
register('./foo.mjs', import.meta.url)
register('./bar.mjs', import.meta.url)
await import('./my-app.mjs')
// entrypoint.cjs
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
const parentURL = pathToFileURL(__filename)
register('./foo.mjs', parentURL)
register('./bar.mjs', parentURL)
import('./my-app.mjs')
En este ejemplo, los hooks registrados formarán cadenas. Estas cadenas se ejecutan de último en entrar, primero en salir (LIFO). Si tanto foo.mjs
como bar.mjs
definen un hook resolve
, se llamarán de la siguiente manera (nótese de derecha a izquierda): el valor predeterminado de Node ← ./foo.mjs
← ./bar.mjs
(comenzando con ./bar.mjs
, luego ./foo.mjs
y luego el valor predeterminado de Node.js). Lo mismo se aplica a todos los demás hooks.
Los hooks registrados también afectan a register
en sí mismo. En este ejemplo, bar.mjs
se resolverá y cargará a través de los hooks registrados por foo.mjs
(porque los hooks de foo
ya se habrán añadido a la cadena). Esto permite cosas como escribir hooks en lenguajes que no son JavaScript, siempre y cuando los hooks registrados anteriormente se transpilen a JavaScript.
El método register
no se puede llamar desde dentro del módulo que define los hooks.
El encadenamiento de registerHooks
funciona de manera similar. Si se mezclan hooks síncronos y asíncronos, los hooks síncronos siempre se ejecutan primero antes de que empiecen a ejecutarse los hooks asíncronos, es decir, en el último hook síncrono que se ejecuta, su siguiente hook incluye la invocación de los hooks asíncronos.
// entrypoint.mjs
import { registerHooks } from 'node:module'
const hook1 = {
/* implementación de hooks */
}
const hook2 = {
/* implementación de hooks */
}
// hook2 se ejecuta antes que hook1.
registerHooks(hook1)
registerHooks(hook2)
// entrypoint.cjs
const { registerHooks } = require('node:module')
const hook1 = {
/* implementación de hooks */
}
const hook2 = {
/* implementación de hooks */
}
// hook2 se ejecuta antes que hook1.
registerHooks(hook1)
registerHooks(hook2)
Comunicación con hooks de personalización de módulos
Los hooks asíncronos se ejecutan en un hilo dedicado, separado del hilo principal que ejecuta el código de la aplicación. Esto significa que la mutación de variables globales no afectará a los otros hilos, y se deben usar canales de mensajes para comunicarse entre los hilos.
El método register
se puede usar para pasar datos a un hook initialize
. Los datos pasados al hook pueden incluir objetos transferibles como puertos.
import { register } from 'node:module'
import { MessageChannel } from 'node:worker_threads'
// Este ejemplo demuestra cómo se puede usar un canal de mensajes para
// comunicarse con los hooks, enviando `port2` a los hooks.
const { port1, port2 } = new MessageChannel()
port1.on('message', msg => {
console.log(msg)
})
port1.unref()
register('./my-hooks.mjs', {
parentURL: import.meta.url,
data: { number: 1, port: port2 },
transferList: [port2],
})
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
const { MessageChannel } = require('node:worker_threads')
// Este ejemplo muestra cómo se puede usar un canal de mensajes para
// comunicarse con los hooks, enviando `port2` a los hooks.
const { port1, port2 } = new MessageChannel()
port1.on('message', msg => {
console.log(msg)
})
port1.unref()
register('./my-hooks.mjs', {
parentURL: pathToFileURL(__filename),
data: { number: 1, port: port2 },
transferList: [port2],
})
Los hooks de módulo síncronos se ejecutan en el mismo hilo donde se ejecuta el código de la aplicación. Pueden mutar directamente las variables globales del contexto al que accede el hilo principal.
Hooks
Hooks asíncronos aceptados por module.register()
El método register
se puede usar para registrar un módulo que exporta un conjunto de hooks. Los hooks son funciones que Node.js llama para personalizar el proceso de resolución y carga de módulos. Las funciones exportadas deben tener nombres y firmas específicos, y deben exportarse como exportaciones con nombre.
export async function initialize({ number, port }) {
// Recibe datos de `register`.
}
export async function resolve(specifier, context, nextResolve) {
// Toma un especificador `import` o `require` y lo resuelve a una URL.
}
export async function load(url, context, nextLoad) {
// Toma una URL resuelta y devuelve el código fuente para ser evaluado.
}
Los hooks asíncronos se ejecutan en un hilo separado, aislado del hilo principal donde se ejecuta el código de la aplicación. Eso significa que es un realm diferente. El hilo de los hooks puede ser terminado por el hilo principal en cualquier momento, así que no dependas de operaciones asíncronas (como console.log
) para que se completen. Se heredan en los workers secundarios de forma predeterminada.
Hooks síncronos aceptados por module.registerHooks()
Agregado en: v23.5.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.1 - Desarrollo activo
El método module.registerHooks()
acepta funciones de hook síncronas. initialize()
no es compatible ni necesario, ya que el implementador del hook puede simplemente ejecutar el código de inicialización directamente antes de la llamada a module.registerHooks()
.
function resolve(specifier, context, nextResolve) {
// Toma un especificador `import` o `require` y lo resuelve a una URL.
}
function load(url, context, nextLoad) {
// Toma una URL resuelta y devuelve el código fuente para ser evaluado.
}
Los hooks síncronos se ejecutan en el mismo hilo y en el mismo ámbito donde se cargan los módulos. A diferencia de los hooks asíncronos, no se heredan en los hilos de trabajo secundarios de forma predeterminada, aunque si los hooks se registran usando un archivo precargado por --import
o --require
, los hilos de trabajo secundarios pueden heredar los scripts precargados a través de la herencia de process.execArgv
. Consulte la documentación de Worker
para obtener más detalles.
En los hooks síncronos, los usuarios pueden esperar que console.log()
se complete de la misma manera que esperan que console.log()
en el código del módulo se complete.
Convenciones de los hooks
Los hooks son parte de una cadena, incluso si esa cadena consiste solo en un hook personalizado (proporcionado por el usuario) y el hook predeterminado, que siempre está presente. Las funciones de los hooks se anidan: cada una siempre debe devolver un objeto plano, y el encadenamiento ocurre como resultado de que cada función llame a next\<hookName\>()
, que es una referencia al hook del cargador subsiguiente (en orden LIFO).
Un hook que devuelve un valor que carece de una propiedad requerida desencadena una excepción. Un hook que devuelve sin llamar a next\<hookName\>()
y sin devolver shortCircuit: true
también desencadena una excepción. Estos errores son para ayudar a prevenir interrupciones no intencionales en la cadena. Devuelve shortCircuit: true
desde un hook para indicar que la cadena termina intencionalmente en tu hook.
initialize()
Añadido en: v20.6.0, v18.19.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.2 - Candidato a lanzamiento
data
<any> Los datos deregister(loader, import.meta.url, { data })
.
El hook initialize
solo es aceptado por register
. registerHooks()
no lo admite ni lo necesita, ya que la inicialización realizada para los hooks síncronos se puede ejecutar directamente antes de la llamada a registerHooks()
.
El hook initialize
proporciona una forma de definir una función personalizada que se ejecuta en el hilo de los hooks cuando se inicializa el módulo de los hooks. La inicialización ocurre cuando el módulo de los hooks se registra a través de register
.
Este hook puede recibir datos de una invocación de register
, incluidos puertos y otros objetos transferibles. El valor devuelto de initialize
puede ser un <Promise>, en cuyo caso se esperará antes de que se reanude la ejecución del hilo de la aplicación principal.
Código de personalización del módulo:
// ruta-a-mis-hooks.js
export async function initialize({ number, port }) {
port.postMessage(`incremento: ${number + 1}`)
}
Código del llamador:
import assert from 'node:assert'
import { register } from 'node:module'
import { MessageChannel } from 'node:worker_threads'
// Este ejemplo muestra cómo se puede usar un canal de mensajes para comunicarse
// entre el hilo principal (de la aplicación) y los hooks que se ejecutan en el hilo de los hooks,
// enviando `port2` al hook `initialize`.
const { port1, port2 } = new MessageChannel()
port1.on('message', msg => {
assert.strictEqual(msg, 'incremento: 2')
})
port1.unref()
register('./ruta-a-mis-hooks.js', {
parentURL: import.meta.url,
data: { number: 1, port: port2 },
transferList: [port2],
})
const assert = require('node:assert')
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
const { MessageChannel } = require('node:worker_threads')
// Este ejemplo muestra cómo se puede usar un canal de mensajes para comunicarse
// entre el hilo principal (de la aplicación) y los hooks que se ejecutan en el hilo de los hooks,
// enviando `port2` al hook `initialize`.
const { port1, port2 } = new MessageChannel()
port1.on('message', msg => {
assert.strictEqual(msg, 'incremento: 2')
})
port1.unref()
register('./ruta-a-mis-hooks.js', {
parentURL: pathToFileURL(__filename),
data: { number: 1, port: port2 },
transferList: [port2],
})
resolve(specifier, context, nextResolve)
[Historial]
Versión | Cambios |
---|---|
v23.5.0 | Se añade soporte para hooks síncronos e en-hilo. |
v21.0.0, v20.10.0, v18.19.0 | La propiedad context.importAssertions se reemplaza por context.importAttributes . El uso del nombre antiguo todavía es compatible y emitirá una advertencia experimental. |
v18.6.0, v16.17.0 | Se añade soporte para encadenar hooks resolve. Cada hook debe llamar a nextResolve() o incluir una propiedad shortCircuit establecida en true en su retorno. |
v17.1.0, v16.14.0 | Se añade soporte para aserciones de importación. |
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.2 - Candidato a lanzamiento (versión asíncrona) Estabilidad: 1.1 - Desarrollo activo (versión síncrona)
specifier
<string>context
<Object>conditions
<string[]> Condiciones de exportación delpackage.json
relevante.importAttributes
<Object> Un objeto cuyas parejas clave-valor representan los atributos para el módulo a importar.parentURL
<string> | <undefined> El módulo que importa este, o undefined si este es el punto de entrada de Node.js.
nextResolve
<Function> El siguiente hookresolve
en la cadena, o el hookresolve
predeterminado de Node.js después del último hookresolve
proporcionado por el usuario.Retorna: <Object> | <Promise> La versión asíncrona toma un objeto que contiene las siguientes propiedades, o una
Promise
que se resolverá en dicho objeto. La versión síncrona solo acepta un objeto retornado síncronamente.format
<string> | <null> | <undefined> Una sugerencia para el hook de carga (podría ser ignorada)'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'
importAttributes
<Object> | <undefined> Los atributos de importación a usar al almacenar el módulo en caché (opcional; si se excluye, se usará la entrada).shortCircuit
<undefined> | <boolean> Una señal de que este hook intenta terminar la cadena de hooksresolve
. Predeterminado:false
url
<string> La URL absoluta a la que se resuelve esta entrada.
La cadena de hooks resolve
es responsable de decirle a Node.js dónde encontrar y cómo almacenar en caché una declaración o expresión import
dada, o una llamada require
. Opcionalmente, puede devolver un formato (como 'module'
) como una sugerencia para el hook load
. Si se especifica un formato, el hook load
es en última instancia responsable de proporcionar el valor final de format
(y es libre de ignorar la sugerencia proporcionada por resolve
); si resolve
proporciona un format
, se requiere un hook load
personalizado, incluso si es solo para pasar el valor al hook load
predeterminado de Node.js.
Los atributos de tipo de importación son parte de la clave de caché para guardar los módulos cargados en la caché interna de módulos. El hook resolve
es responsable de devolver un objeto importAttributes
si el módulo debe almacenarse en caché con atributos diferentes a los que estaban presentes en el código fuente.
La propiedad conditions
en context
es un array de condiciones que se utilizarán para coincidir con las condiciones de exportación del paquete para esta solicitud de resolución. Se pueden usar para buscar mapeos condicionales en otro lugar o para modificar la lista al llamar a la lógica de resolución predeterminada.
Las condiciones de exportación del paquete actuales están siempre en el array context.conditions
que se pasa al hook. Para garantizar el comportamiento predeterminado de resolución de especificadores de módulos de Node.js al llamar a defaultResolve
, el array context.conditions
que se le pasa debe incluir todos los elementos del array context.conditions
que se pasaron originalmente al hook resolve
.
// Versión asíncrona aceptada por module.register().
export async function resolve(specifier, context, nextResolve) {
const { parentURL = null } = context
if (Math.random() > 0.5) {
// Alguna condición.
// Para algunos o todos los especificadores, realiza alguna lógica personalizada para resolver.
// Siempre devuelve un objeto de la forma {url: <string>}.
return {
shortCircuit: true,
url: parentURL ? new URL(specifier, parentURL).href : new URL(specifier).href,
}
}
if (Math.random() < 0.5) {
// Otra condición.
// Al llamar a `defaultResolve`, los argumentos pueden ser modificados. En este
// caso, está añadiendo otro valor para coincidir con las exportaciones condicionales.
return nextResolve(specifier, {
...context,
conditions: [...context.conditions, 'another-condition'],
})
}
// Defiere al siguiente hook en la cadena, que sería el
// resolve predeterminado de Node.js si este es el último cargador especificado por el usuario.
return nextResolve(specifier)
}
// Versión síncrona aceptada por module.registerHooks().
function resolve(specifier, context, nextResolve) {
// Similar al resolve() asíncrono de arriba, ya que este no tiene
// ninguna lógica asíncrona.
}
load(url, context, nextLoad)
[Historial]
Versión | Cambios |
---|---|
v23.5.0 | Se agregó soporte para la versión síncrona y en hilo. |
v20.6.0 | Se agregó soporte para source con formato commonjs . |
v18.6.0, v16.17.0 | Se agregó soporte para encadenar hooks de carga. Cada hook debe llamar a nextLoad() o incluir una propiedad shortCircuit establecida en true en su retorno. |
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.2 - Candidato a lanzamiento (versión asíncrona) Estabilidad: 1.1 - Desarrollo activo (versión síncrona)
url
<string> La URL devuelta por la cadenaresolve
context
<Object>conditions
<string[]> Condiciones de exportación delpackage.json
relevanteformat
<string> | <null> | <undefined> El formato opcionalmente proporcionado por la cadena de hooksresolve
importAttributes
<Object>
nextLoad
<Function> El siguiente hookload
en la cadena, o el hookload
predeterminado de Node.js después del último hookload
proporcionado por el usuarioDevuelve: <Object> | <Promise> La versión asíncrona toma un objeto que contiene las siguientes propiedades, o una
Promise
que se resolverá en dicho objeto. La versión síncrona solo acepta un objeto devuelto síncronamente.format
<string>shortCircuit
<undefined> | <boolean> Una señal de que este hook pretende terminar la cadena de hooksload
. Predeterminado:false
source
<string> | <ArrayBuffer> | <TypedArray> La fuente para que Node.js evalúe
El hook load
proporciona una forma de definir un método personalizado para determinar cómo se debe interpretar, recuperar y analizar una URL. También se encarga de validar los atributos de importación.
El valor final de format
debe ser uno de los siguientes:
format | Descripción | Tipos aceptables para source devueltos por load |
---|---|---|
'builtin' | Cargar un módulo integrado de Node.js | No aplicable |
'commonjs' | Cargar un módulo CommonJS de Node.js | { string , ArrayBuffer , TypedArray , null , undefined } |
'json' | Cargar un archivo JSON | { string , ArrayBuffer , TypedArray } |
'module' | Cargar un módulo ES | { string , ArrayBuffer , TypedArray } |
'wasm' | Cargar un módulo WebAssembly | { ArrayBuffer , TypedArray } |
El valor de source
se ignora para el tipo 'builtin'
porque actualmente no es posible reemplazar el valor de un módulo integrado (principal) de Node.js.
Advertencia en el hook load
asíncrono
Cuando se utiliza el hook load
asíncrono, omitir o proporcionar un source
para 'commonjs'
tiene efectos muy diferentes:
- Cuando se proporciona un
source
, todas las llamadas arequire
de este módulo serán procesadas por el cargador ESM con hooksresolve
yload
registrados; todas las llamadas arequire.resolve
de este módulo serán procesadas por el cargador ESM con hooksresolve
registrados; solo un subconjunto de la API de CommonJS estará disponible (p. ej., sinrequire.extensions
, sinrequire.cache
, sinrequire.resolve.paths
) y la modificación del cargador de módulos CommonJS no se aplicará. - Si
source
no está definido o esnull
, será manejado por el cargador de módulos CommonJS y las llamadasrequire
/require.resolve
no pasarán por los hooks registrados. Este comportamiento parasource
nulo es temporal; en el futuro, no se admitirásource
nulo.
Estas advertencias no se aplican al hook load
síncrono, en cuyo caso el conjunto completo de API de CommonJS disponibles para los módulos CommonJS personalizados, y require
/require.resolve
siempre pasan por los hooks registrados.
La implementación interna asíncrona load
de Node.js, que es el valor de next
para el último hook en la cadena load
, devuelve null
para source
cuando format
es 'commonjs'
por compatibilidad con versiones anteriores. Aquí hay un ejemplo de hook que optaría por usar el comportamiento no predeterminado:
import { readFile } from 'node:fs/promises'
// Versión asíncrona aceptada por module.register(). Esta corrección no es necesaria
// para la versión síncrona aceptada por module.registerSync().
export async function load(url, context, nextLoad) {
const result = await nextLoad(url, context)
if (result.format === 'commonjs') {
result.source ??= await readFile(new URL(result.responseURL ?? url))
}
return result
}
Esto tampoco se aplica al hook load
síncrono, en cuyo caso el source
devuelto contiene código fuente cargado por el siguiente hook, independientemente del formato del módulo.
- El objeto específico
ArrayBuffer
es unSharedArrayBuffer
. - El objeto específico
TypedArray
es unUint8Array
.
Si el valor de origen de un formato basado en texto (es decir, 'json'
, 'module'
) no es una cadena, se convierte en una cadena utilizando util.TextDecoder
.
El hook load
proporciona una forma de definir un método personalizado para recuperar el código fuente de una URL resuelta. Esto permitiría a un cargador evitar potencialmente la lectura de archivos del disco. También podría utilizarse para asignar un formato no reconocido a uno compatible, por ejemplo, yaml
a module
.
// Versión asíncrona aceptada por module.register().
export async function load(url, context, nextLoad) {
const { format } = context
if (Math.random() > 0.5) {
// Alguna condición
/*
Para algunas o todas las URL, realice alguna lógica personalizada para recuperar el origen.
Siempre devuelve un objeto de la forma {
format: <string>,
source: <string|buffer>,
}.
*/
return {
format,
shortCircuit: true,
source: '...',
}
}
// Diferir al siguiente hook en la cadena.
return nextLoad(url)
}
// Versión síncrona aceptada por module.registerHooks().
function load(url, context, nextLoad) {
// Similar al load() asíncrono anterior, ya que ese no tiene
// ninguna lógica asíncrona.
}
En un escenario más avanzado, esto también se puede usar para transformar una fuente no compatible en una compatible (consulte Ejemplos a continuación).
Ejemplos
Los diversos ganchos de personalización de módulos se pueden usar juntos para lograr amplias personalizaciones de los comportamientos de carga y evaluación de código de Node.js.
Importar desde HTTPS
El siguiente gancho registra ganchos para habilitar un soporte rudimentario para tales especificadores. Si bien esto puede parecer una mejora significativa en la funcionalidad principal de Node.js, existen desventajas sustanciales al usar realmente estos ganchos: el rendimiento es mucho más lento que cargar archivos desde el disco, no hay almacenamiento en caché y no hay seguridad.
// https-hooks.mjs
import { get } from 'node:https'
export function load(url, context, nextLoad) {
// Para que JavaScript se cargue a través de la red, necesitamos buscarlo y
// devolverlo.
if (url.startsWith('https://')) {
return new Promise((resolve, reject) => {
get(url, res => {
let data = ''
res.setEncoding('utf8')
res.on('data', chunk => (data += chunk))
res.on('end', () =>
resolve({
// Este ejemplo asume que todo el JavaScript proporcionado por la red es código
// de módulo ES.
format: 'module',
shortCircuit: true,
source: data,
})
)
}).on('error', err => reject(err))
})
}
// Dejar que Node.js maneje todas las demás URLs.
return nextLoad(url)
}
// main.mjs
import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js'
console.log(VERSION)
Con el módulo de ganchos anterior, al ejecutar node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./https-hooks.mjs"));' ./main.mjs
imprime la versión actual de CoffeeScript según el módulo en la URL en main.mjs
.
Transpilación
Las fuentes que están en formatos que Node.js no entiende se pueden convertir a JavaScript usando el hook load
.
Esto es menos eficiente que la transpilación de archivos fuente antes de ejecutar Node.js; los hooks de transpilación solo deben usarse para fines de desarrollo y prueba.
Versión asíncrona
// coffeescript-hooks.mjs
import { readFile } from 'node:fs/promises'
import { dirname, extname, resolve as resolvePath } from 'node:path'
import { cwd } from 'node:process'
import { fileURLToPath, pathToFileURL } from 'node:url'
import coffeescript from 'coffeescript'
const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/
export async function load(url, context, nextLoad) {
if (extensionsRegex.test(url)) {
// Los archivos de CoffeeScript pueden ser módulos CommonJS o ES, por lo que queremos que
// cualquier archivo de CoffeeScript sea tratado por Node.js de la misma manera que un archivo .js en la
// misma ubicación. Para determinar cómo Node.js interpretaría un archivo .js arbitrario,
// busque en el sistema de archivos el archivo package.json principal más cercano
// y lea su campo "type".
const format = await getPackageType(url)
const { source: rawSource } = await nextLoad(url, { ...context, format })
// Este hook convierte el código fuente de CoffeeScript en código fuente de JavaScript
// para todos los archivos de CoffeeScript importados.
const transformedSource = coffeescript.compile(rawSource.toString(), url)
return {
format,
shortCircuit: true,
source: transformedSource,
}
}
// Deje que Node.js gestione todas las demás URL.
return nextLoad(url)
}
async function getPackageType(url) {
// `url` es solo una ruta de archivo durante la primera iteración cuando se pasa la
// url resuelta desde el hook load()
// una ruta de archivo real desde load() contendrá una extensión de archivo, ya que es
// requerida por la especificación
// esta sencilla comprobación de verdad sobre si `url` contiene una extensión de archivo funcionará
// para la mayoría de los proyectos, pero no cubre algunos casos extremos (como
// archivos sin extensión o una url que termina en un espacio final)
const isFilePath = !!extname(url)
// Si es una ruta de archivo, obtenga el directorio en el que se encuentra
const dir = isFilePath ? dirname(fileURLToPath(url)) : url
// Componer una ruta de archivo a un package.json en el mismo directorio,
// que puede o no existir
const packagePath = resolvePath(dir, 'package.json')
// Intente leer el package.json posiblemente inexistente
const type = await readFile(packagePath, { encoding: 'utf8' })
.then(filestring => JSON.parse(filestring).type)
.catch(err => {
if (err?.code !== 'ENOENT') console.error(err)
})
// Si package.json existía y contenía un campo `type` con un valor, voilà
if (type) return type
// De lo contrario, (si no está en la raíz) continúe verificando el siguiente directorio hacia arriba
// Si está en la raíz, deténgase y devuelva falso
return dir.length > 1 && getPackageType(resolvePath(dir, '..'))
}
Versión síncrona
// coffeescript-sync-hooks.mjs
import { readFileSync } from 'node:fs/promises'
import { registerHooks } from 'node:module'
import { dirname, extname, resolve as resolvePath } from 'node:path'
import { cwd } from 'node:process'
import { fileURLToPath, pathToFileURL } from 'node:url'
import coffeescript from 'coffeescript'
const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/
function load(url, context, nextLoad) {
if (extensionsRegex.test(url)) {
const format = getPackageType(url)
const { source: rawSource } = nextLoad(url, { ...context, format })
const transformedSource = coffeescript.compile(rawSource.toString(), url)
return {
format,
shortCircuit: true,
source: transformedSource,
}
}
return nextLoad(url)
}
function getPackageType(url) {
const isFilePath = !!extname(url)
const dir = isFilePath ? dirname(fileURLToPath(url)) : url
const packagePath = resolvePath(dir, 'package.json')
let type
try {
const filestring = readFileSync(packagePath, { encoding: 'utf8' })
type = JSON.parse(filestring).type
} catch (err) {
if (err?.code !== 'ENOENT') console.error(err)
}
if (type) return type
return dir.length > 1 && getPackageType(resolvePath(dir, '..'))
}
registerHooks({ load })
Ejecución de hooks
# main.coffee {#maincoffee}
import { scream } from './scream.coffee'
console.log scream 'hello, world'
import { version } from 'node:process'
console.log "Presentado por la versión de Node.js #{version}"
# scream.coffee {#screamcoffee}
export scream = (str) -> str.toUpperCase()
Con los módulos de hooks precedentes, ejecutar node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee
o node --import ./coffeescript-sync-hooks.mjs ./main.coffee
hace que main.coffee
se convierta en JavaScript después de que su código fuente se cargue desde el disco pero antes de que Node.js lo ejecute; y así sucesivamente para cualquier archivo .coffee
, .litcoffee
o .coffee.md
al que se haga referencia a través de sentencias import
de cualquier archivo cargado.
Mapas de importación
Los dos ejemplos anteriores definieron hooks load
. Este es un ejemplo de un hook resolve
. Este módulo de hooks lee un archivo import-map.json
que define qué especificadores deben anularse a otras URL (esta es una implementación muy simplista de un pequeño subconjunto de la especificación de "mapas de importación").
Versión asíncrona
// import-map-hooks.js
import fs from 'node:fs/promises'
const { imports } = JSON.parse(await fs.readFile('import-map.json'))
export async function resolve(specifier, context, nextResolve) {
if (Object.hasOwn(imports, specifier)) {
return nextResolve(imports[specifier], context)
}
return nextResolve(specifier, context)
}
Versión síncrona
// import-map-sync-hooks.js
import fs from 'node:fs/promises'
import module from 'node:module'
const { imports } = JSON.parse(fs.readFileSync('import-map.json', 'utf-8'))
function resolve(specifier, context, nextResolve) {
if (Object.hasOwn(imports, specifier)) {
return nextResolve(imports[specifier], context)
}
return nextResolve(specifier, context)
}
module.registerHooks({ resolve })
Uso de los hooks
Con estos archivos:
// main.js
import 'a-module'
// import-map.json
{
"imports": {
"a-module": "./some-module.js"
}
}
// some-module.js
console.log('some module!')
Ejecutar node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./import-map-hooks.js"));' main.js
o node --import ./import-map-sync-hooks.js main.js
debería imprimir some module!
.
Soporte de mapa de origen v3
Agregado en: v13.7.0, v12.17.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1 - Experimental
Ayudas para interactuar con la caché de mapas de origen. Esta caché se completa cuando el análisis de mapas de origen está habilitado y se encuentran directivas de inclusión de mapas de origen en el pie de página de los módulos.
Para habilitar el análisis de mapas de origen, Node.js debe ejecutarse con la bandera --enable-source-maps
, o con la cobertura de código habilitada configurando NODE_V8_COVERAGE=dir
.
// module.mjs
// En un módulo ECMAScript
import { findSourceMap, SourceMap } from 'node:module'
// module.cjs
// En un módulo CommonJS
const { findSourceMap, SourceMap } = require('node:module')
module.findSourceMap(path)
Agregado en: v13.7.0, v12.17.0
path
<string>- Devuelve: <module.SourceMap> | <undefined> Devuelve
module.SourceMap
si se encuentra un mapa de origen,undefined
en caso contrario.
path
es la ruta resuelta del archivo para el que se debe obtener un mapa de origen correspondiente.
Clase: module.SourceMap
Agregado en: v13.7.0, v12.17.0
new SourceMap(payload[, { lineLengths }])
{#new-sourcemappayload-{-linelengths-}}
payload
<Object>lineLengths
<number[]>
Crea una nueva instancia sourceMap
.
payload
es un objeto con claves que coinciden con el formato Source map v3:
file
: <string>version
: <number>sources
: <string[]>sourcesContent
: <string[]>names
: <string[]>mappings
: <string>sourceRoot
: <string>
lineLengths
es un array opcional de la longitud de cada línea en el código generado.
sourceMap.payload
- Devuelve: <Objeto>
Getter para la carga útil utilizada para construir la instancia de SourceMap
.
sourceMap.findEntry(lineOffset, columnOffset)
lineOffset
<número> El desplazamiento del número de línea con índice cero en la fuente generadacolumnOffset
<número> El desplazamiento del número de columna con índice cero en la fuente generada- Devuelve: <Objeto>
Dado un desplazamiento de línea y un desplazamiento de columna en el archivo fuente generado, devuelve un objeto que representa el rango de SourceMap en el archivo original si se encuentra, o un objeto vacío si no.
El objeto devuelto contiene las siguientes claves:
- generatedLine: <número> El desplazamiento de línea del inicio del rango en la fuente generada
- generatedColumn: <número> El desplazamiento de columna del inicio del rango en la fuente generada
- originalSource: <cadena de texto> El nombre del archivo de la fuente original, tal como se informa en el SourceMap
- originalLine: <número> El desplazamiento de línea del inicio del rango en la fuente original
- originalColumn: <número> El desplazamiento de columna del inicio del rango en la fuente original
- name: <cadena de texto>
El valor devuelto representa el rango sin procesar tal como aparece en el SourceMap, basado en desplazamientos con índice cero, no en números de línea y columna con índice 1 tal como aparecen en los mensajes de error y los objetos CallSite.
Para obtener los números de línea y columna con índice 1 correspondientes de un lineNumber y columnNumber tal como son informados por las pilas de Error y los objetos CallSite, utilice sourceMap.findOrigin(lineNumber, columnNumber)
sourceMap.findOrigin(lineNumber, columnNumber)
lineNumber
<number> El número de línea (indexado en 1) del sitio de llamada en la fuente generada.columnNumber
<number> El número de columna (indexado en 1) del sitio de llamada en la fuente generada.- Devuelve: <Object>
Dado un lineNumber
y un columnNumber
(indexados en 1) de un sitio de llamada en la fuente generada, encuentra la ubicación correspondiente del sitio de llamada en la fuente original.
Si el lineNumber
y el columnNumber
proporcionados no se encuentran en ningún mapa de origen, se devuelve un objeto vacío. De lo contrario, el objeto devuelto contiene las siguientes claves:
- name: <string> | <undefined> El nombre del rango en el mapa de origen, si se proporcionó alguno.
- fileName: <string> El nombre del archivo de la fuente original, tal como se informa en el mapa de origen.
- lineNumber: <number> El
lineNumber
(indexado en 1) del sitio de llamada correspondiente en la fuente original. - columnNumber: <number> El
columnNumber
(indexado en 1) del sitio de llamada correspondiente en la fuente original.