Módulos: API node:module
Adicionado em: v0.3.7
O objeto Module
Fornece métodos de utilidade geral ao interagir com instâncias de Module
, a variável module
frequentemente vista em módulos CommonJS. Acessado via import 'node:module'
ou require('node:module')
.
module.builtinModules
[Histórico]
Versão | Mudanças |
---|---|
v23.5.0 | A lista agora também contém módulos apenas com prefixo. |
v9.3.0, v8.10.0, v6.13.0 | Adicionado em: v9.3.0, v8.10.0, v6.13.0 |
Uma lista dos nomes de todos os módulos fornecidos pelo Node.js. Pode ser usada para verificar se um módulo é mantido por terceiros ou não.
module
neste contexto não é o mesmo objeto fornecido pelo wrapper de módulo. Para acessá-lo, solicite o módulo Module
:
// module.mjs
// Em um módulo ECMAScript
import { builtinModules as builtin } from 'node:module'
// module.cjs
// Em um módulo CommonJS
const builtin = require('node:module').builtinModules
module.createRequire(filename)
Adicionado em: v12.2.0
filename
<string> | <URL> Nome do arquivo a ser usado para construir a função require. Deve ser um objeto de URL de arquivo, string de URL de arquivo ou string de caminho absoluto.- Retorna: <require> Função require
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
// sibling-module.js é um módulo CommonJS.
const siblingModule = require('./sibling-module')
module.findPackageJSON(specifier[, base])
Adicionado em: v23.2.0
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.1 - Desenvolvimento ativo
specifier
<string> | <URL> O especificador para o módulo cujopackage.json
deve ser recuperado. Ao passar um especificador simples, opackage.json
na raiz do pacote é retornado. Ao passar um especificador relativo ou um especificador absoluto, opackage.json
pai mais próximo é retornado.base
<string> | <URL> A localização absoluta (string de URLfile:
ou caminho do sistema de arquivos) do módulo contendo. Para CJS, use__filename
(não__dirname
!); para ESM, useimport.meta.url
. Você não precisa passá-lo sespecifier
for umespecificador absoluto
.- Retorna: <string> | <undefined> Um caminho se o
package.json
for encontrado. QuandostartLocation
é um pacote, opackage.json
raiz do pacote; quando relativo ou não resolvido, opackage.json
mais próximo destartLocation
.
/path/to/project
├ packages/
├ bar/
├ bar.js
└ package.json // nome = '@foo/bar'
└ qux/
├ node_modules/
└ some-package/
└ package.json // nome = 'some-package'
├ qux.js
└ package.json // nome = '@foo/qux'
├ main.js
└ package.json // nome = '@foo'
// /path/to/project/packages/bar/bar.js
import { findPackageJSON } from 'node:module'
findPackageJSON('..', import.meta.url)
// '/path/to/project/package.json'
// Mesmo resultado ao passar um especificador absoluto:
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'
// Ao passar um especificador absoluto, você pode obter um resultado diferente se o
// módulo resolvido estiver dentro de uma subpasta que tenha `package.json` aninhado.
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'
// Mesmo resultado ao passar um especificador absoluto:
findPackageJSON(pathToFileURL(path.join(__dirname, '..')))
findPackageJSON('some-package', __filename)
// '/path/to/project/packages/bar/node_modules/some-package/package.json'
// Ao passar um especificador absoluto, você pode obter um resultado diferente se o
// módulo resolvido estiver dentro de uma subpasta que tenha `package.json` aninhado.
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)
Adicionado em: v18.6.0, v16.17.0
moduleName
<string> nome do módulo- Retorna: <boolean> retorna true se o módulo for embutido, caso contrário, retorna false
import { isBuiltin } from 'node:module'
isBuiltin('node:fs') // true
isBuiltin('fs') // true
isBuiltin('wss') // false
module.register(specifier[, parentURL][, options])
[Histórico]
Versão | Mudanças |
---|---|
v20.8.0, v18.19.0 | Adicionado suporte para instâncias de URL WHATWG. |
v20.6.0, v18.19.0 | Adicionado em: v20.6.0, v18.19.0 |
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.2 - Candidato a Lançamento
specifier
<string> | <URL> Hooks de personalização a serem registrados; esta deve ser a mesma string que seria passada paraimport()
, exceto que, se for relativa, ela é resolvida em relação aparentURL
.parentURL
<string> | <URL> Se você quiser resolverspecifier
em relação a uma URL base, comoimport.meta.url
, você pode passar essa URL aqui. Padrão:'data:'
options
<Object>parentURL
<string> | <URL> Se você quiser resolverspecifier
em relação a uma URL base, comoimport.meta.url
, você pode passar essa URL aqui. Esta propriedade é ignorada se oparentURL
for fornecido como o segundo argumento. Padrão:'data:'
data
<any> Qualquer valor JavaScript arbitrário e clonável para passar para o hookinitialize
.transferList
<Object[]> objetos transferíveis a serem passados para o hookinitialize
.
Registre um módulo que exporta hooks que personalizam a resolução de módulos do Node.js e o comportamento de carregamento. Consulte Hooks de personalização.
module.registerHooks(options)
Adicionado em: v23.5.0
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.1 - Desenvolvimento ativo
options
<Objeto>load
<Função> | <undefined> Veja gancho de carregamento. Padrão:undefined
.resolve
<Função> | <undefined> Veja gancho de resolução. Padrão:undefined
.
Registre ganchos que personalizam a resolução de módulos do Node.js e o comportamento de carregamento. Consulte Ganchos de personalização.
module.stripTypeScriptTypes(code[, options])
Adicionado em: v23.2.0
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.1 - Desenvolvimento ativo
code
<string> O código do qual remover as anotações de tipo.options
<Objeto>mode
<string> Padrão:'strip'
. Valores possíveis são:'strip'
Remove apenas as anotações de tipo sem realizar a transformação de recursos do TypeScript.'transform'
Remove as anotações de tipo e transforma os recursos do TypeScript em JavaScript.sourceMap
<boolean> Padrão:false
. Apenas quandomode
for'transform'
, setrue
, um mapa de origem será gerado para o código transformado.sourceUrl
<string> Especifica a url de origem usada no mapa de origem.
Retorna: <string> O código com as anotações de tipo removidas.
module.stripTypeScriptTypes()
remove as anotações de tipo do código TypeScript. Ele pode ser usado para remover as anotações de tipo do código TypeScript antes de executá-lo comvm.runInContext()
ouvm.compileFunction()
. Por padrão, ele lançará um erro se o código contiver recursos do TypeScript que exigem transformação, comoEnums
, consulte remoção de tipo para obter mais informações. Quando o modo é'transform'
, ele também transforma os recursos do TypeScript em JavaScript, consulte transformar recursos do TypeScript para obter mais informações. Quando o modo é'strip'
, os mapas de origem não são gerados, porque os locais são preservados. SesourceMap
for fornecido, quando o modo for'strip'
, um erro será lançado.
AVISO: A saída desta função não deve ser considerada estável entre as versões do Node.js, devido a alterações no analisador 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;
Se sourceUrl
for fornecido, ele será usado anexado como um comentário no final da saída:
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;
Quando mode
é 'transform'
, o código é transformado em 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()
Adicionado em: v12.12.0
O método module.syncBuiltinESMExports()
atualiza todas as vinculações dinâmicas para Módulos ES integrados para corresponder às propriedades das exportações do CommonJS. Ele não adiciona nem remove nomes exportados dos 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 => {
// Ele sincroniza a propriedade readFile existente com o novo valor
assert.strictEqual(esmFS.readFile, newAPI)
// readFileSync foi excluído do fs requerido
assert.strictEqual('readFileSync' in fs, false)
// syncBuiltinESMExports() não remove readFileSync do esmFS
assert.strictEqual('readFileSync' in esmFS, true)
// syncBuiltinESMExports() não adiciona nomes
assert.strictEqual(esmFS.newAPI, undefined)
})
Cache de compilação de módulos
[Histórico]
Versão | Mudanças |
---|---|
v22.8.0 | adiciona APIs JavaScript iniciais para acesso em tempo de execução. |
v22.1.0 | Adicionado em: v22.1.0 |
O cache de compilação de módulos pode ser ativado usando o método module.enableCompileCache()
ou a variável de ambiente NODE_COMPILE_CACHE=dir
. Depois de ativado, sempre que o Node.js compilar um CommonJS ou um Módulo ECMAScript, ele usará o cache de código V8 em disco persistido no diretório especificado para acelerar a compilação. Isso pode desacelerar o primeiro carregamento de um gráfico de módulo, mas os carregamentos subsequentes do mesmo gráfico de módulo podem obter uma aceleração significativa se o conteúdo dos módulos não mudar.
Para limpar o cache de compilação gerado em disco, basta remover o diretório do cache. O diretório de cache será recriado na próxima vez que o mesmo diretório for usado para armazenamento de cache de compilação. Para evitar encher o disco com cache obsoleto, recomenda-se usar um diretório em os.tmpdir()
. Se o cache de compilação for ativado por uma chamada para module.enableCompileCache()
sem especificar o diretório, o Node.js usará a variável de ambiente NODE_COMPILE_CACHE=dir
se ela estiver definida ou o padrão será path.join(os.tmpdir(), 'node-compile-cache')
caso contrário. Para localizar o diretório de cache de compilação usado por uma instância do Node.js em execução, use module.getCompileCacheDir()
.
Atualmente, ao usar o cache de compilação com a cobertura de código V8 JavaScript, a cobertura que está sendo coletada pelo V8 pode ser menos precisa em funções que são desserializadas do cache de código. É recomendável desativá-lo ao executar testes para gerar uma cobertura precisa.
O cache de compilação de módulos ativado pode ser desativado pela variável de ambiente NODE_DISABLE_COMPILE_CACHE=1
. Isso pode ser útil quando o cache de compilação leva a comportamentos inesperados ou indesejados (por exemplo, cobertura de teste menos precisa).
O cache de compilação gerado por uma versão do Node.js não pode ser reutilizado por uma versão diferente do Node.js. O cache gerado por versões diferentes do Node.js será armazenado separadamente se o mesmo diretório base for usado para persistir o cache, para que eles possam coexistir.
No momento, quando o cache de compilação é ativado e um módulo é carregado novamente, o cache de código é gerado a partir do código compilado imediatamente, mas só será gravado em disco quando a instância do Node.js estiver prestes a sair. Isso está sujeito a alterações. O método module.flushCompileCache()
pode ser usado para garantir que o cache de código acumulado seja descarregado em disco caso o aplicativo queira gerar outras instâncias do Node.js e permitir que elas compartilhem o cache muito antes do pai sair.
module.constants.compileCacheStatus
Adicionado em: v22.8.0
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.1 - Desenvolvimento Ativo
As seguintes constantes são retornadas como o campo status
no objeto retornado por module.enableCompileCache()
para indicar o resultado da tentativa de habilitar o cache de compilação de módulos.
Constante | Descrição |
---|---|
ENABLED | O Node.js habilitou o cache de compilação com sucesso. O diretório usado para armazenar o cache de compilação será retornado no campo directory no objeto retornado. |
ALREADY_ENABLED | O cache de compilação já foi habilitado anteriormente, seja por uma chamada anterior para module.enableCompileCache() , ou pela variável de ambiente NODE_COMPILE_CACHE=dir . O diretório usado para armazenar o cache de compilação será retornado no campo directory no objeto retornado. |
FAILED | O Node.js falha ao habilitar o cache de compilação. Isso pode ser causado pela falta de permissão para usar o diretório especificado ou por vários tipos de erros do sistema de arquivos. O detalhe da falha será retornado no campo message no objeto retornado. |
DISABLED | O Node.js não pode habilitar o cache de compilação porque a variável de ambiente NODE_DISABLE_COMPILE_CACHE=1 foi definida. |
module.enableCompileCache([cacheDir])
Adicionado em: v22.8.0
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.1 - Desenvolvimento Ativo
cacheDir
<string> | <undefined> Caminho opcional para especificar o diretório onde o cache de compilação será armazenado/recuperado.- Retorna: <Objeto>
status
<inteiro> Um dosmodule.constants.compileCacheStatus
message
<string> | <undefined> Se o Node.js não puder habilitar o cache de compilação, isso conterá a mensagem de erro. Definido apenas sestatus
formodule.constants.compileCacheStatus.FAILED
.directory
<string> | <undefined> Se o cache de compilação estiver habilitado, isso conterá o diretório onde o cache de compilação é armazenado. Definido apenas sestatus
formodule.constants.compileCacheStatus.ENABLED
oumodule.constants.compileCacheStatus.ALREADY_ENABLED
.
Habilita o cache de compilação de módulos na instância atual do Node.js.
Se cacheDir
não for especificado, o Node.js usará o diretório especificado pela variável de ambiente NODE_COMPILE_CACHE=dir
se estiver definida ou usará path.join(os.tmpdir(), 'node-compile-cache')
caso contrário. Para casos de uso geral, é recomendável chamar module.enableCompileCache()
sem especificar cacheDir
, para que o diretório possa ser substituído pela variável de ambiente NODE_COMPILE_CACHE
quando necessário.
Como o cache de compilação deve ser uma otimização silenciosa que não é necessária para que o aplicativo seja funcional, este método foi projetado para não lançar nenhuma exceção quando o cache de compilação não puder ser habilitado. Em vez disso, ele retornará um objeto contendo uma mensagem de erro no campo message
para ajudar na depuração. Se o cache de compilação for habilitado com sucesso, o campo directory
no objeto retornado conterá o caminho para o diretório onde o cache de compilação está armazenado. O campo status
no objeto retornado seria um dos valores de module.constants.compileCacheStatus
para indicar o resultado da tentativa de habilitar o cache de compilação de módulos.
Este método afeta apenas a instância atual do Node.js. Para habilitá-lo em threads de trabalho secundárias, chame este método também em threads de trabalho secundárias ou defina o valor process.env.NODE_COMPILE_CACHE
para o diretório de cache de compilação para que o comportamento possa ser herdado pelos trabalhadores secundários. O diretório pode ser obtido do campo directory
retornado por este método ou com module.getCompileCacheDir()
.
module.flushCompileCache()
Adicionado em: v23.0.0
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.1 - Desenvolvimento Ativo
Limpa o cache de compilação de módulos acumulado de módulos já carregados na instância atual do Node.js para o disco. Isso retorna após todas as operações de limpeza do sistema de arquivos terminarem, independentemente de terem sucesso ou não. Se houver algum erro, isso falhará silenciosamente, pois as falhas no cache de compilação não devem interferir na operação real do aplicativo.
module.getCompileCacheDir()
Adicionado em: v22.8.0
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.1 - Desenvolvimento Ativo
- Retorna: <string> | <undefined> Caminho para o diretório do cache de compilação de módulos se estiver habilitado ou
undefined
caso contrário.
Hooks de Personalização
[Histórico]
Versão | Alterações |
---|---|
v23.5.0 | Adicionada suporte para hooks síncronos e em thread. |
v20.6.0, v18.19.0 | Adicionado o hook initialize para substituir globalPreload . |
v18.6.0, v16.17.0 | Adicionado suporte para encadeamento de loaders. |
v16.12.0 | Removido getFormat , getSource , transformSource e globalPreload ; adicionado o hook load e o hook getGlobalPreload . |
v8.8.0 | Adicionado em: v8.8.0 |
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.2 - Candidato a lançamento (versão assíncrona) Estabilidade: 1.1 - Desenvolvimento ativo (versão síncrona)
Existem dois tipos de hooks de personalização de módulos que são suportados atualmente:
Habilitando
A resolução e o carregamento de módulos podem ser personalizados por:
Os hooks podem ser registrados antes da execução do código do aplicativo usando a flag --import
ou --require
:
node --import ./register-hooks.js ./my-app.js
node --require ./register-hooks.js ./my-app.js
// register-hooks.js
// Este arquivo só pode ser require()-d se não contiver await de nível superior.
// Use module.register() para registrar hooks assíncronos em um thread 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')
// Use module.register() para registrar hooks assíncronos em um thread dedicado.
register('./hooks.mjs', pathToFileURL(__filename))
// Use module.registerHooks() para registrar hooks síncronos no thread principal.
import { registerHooks } from 'node:module'
registerHooks({
resolve(specifier, context, nextResolve) {
/* implementação */
},
load(url, context, nextLoad) {
/* implementação */
},
})
// Use module.registerHooks() para registrar hooks síncronos no thread principal.
const { registerHooks } = require('node:module')
registerHooks({
resolve(specifier, context, nextResolve) {
/* implementação */
},
load(url, context, nextLoad) {
/* implementação */
},
})
O arquivo passado para --import
ou --require
também pode ser uma exportação de uma dependência:
node --import some-package/register ./my-app.js
node --require some-package/register ./my-app.js
Onde some-package
tem um campo "exports"
definindo a exportação /register
para mapear para um arquivo que chama register()
, como o exemplo register-hooks.js
a seguir.
Usar --import
ou --require
garante que os hooks sejam registrados antes que qualquer arquivo de aplicativo seja importado, incluindo o ponto de entrada do aplicativo e para quaisquer threads de worker por padrão também.
Alternativamente, register()
e registerHooks()
podem ser chamados do ponto de entrada, embora import()
dinâmico deva ser usado para qualquer código ESM que deva ser executado após o registro dos hooks.
import { register } from 'node:module'
register('http-to-https', import.meta.url)
// Como este é um `import()` dinâmico, os hooks `http-to-https` serão executados
// para lidar com `./my-app.js` e quaisquer outros arquivos que ele importe ou requeira.
await import('./my-app.js')
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
register('http-to-https', pathToFileURL(__filename))
// Como este é um `import()` dinâmico, os hooks `http-to-https` serão executados
// para lidar com `./my-app.js` e quaisquer outros arquivos que ele importe ou requeira.
import('./my-app.js')
Os hooks de personalização serão executados para quaisquer módulos carregados posteriormente ao registro e os módulos que eles referenciam via import
e o require
interno. A função require
criada por usuários usando module.createRequire()
só pode ser personalizada pelos hooks síncronos.
Neste exemplo, estamos registrando os hooks http-to-https
, mas eles só estarão disponíveis para módulos importados posteriormente — neste caso, my-app.js
e qualquer coisa que ele referencie via import
ou require
interno em dependências CommonJS.
Se o import('./my-app.js')
fosse, em vez disso, um import './my-app.js'
estático, o aplicativo já teria sido carregado antes que os hooks http-to-https
fossem registrados. Isso devido à especificação de módulos ES, onde as importações estáticas são avaliadas primeiro nas folhas da árvore e, em seguida, de volta ao tronco. Pode haver importações estáticas dentro de my-app.js
, que não serão avaliadas até que my-app.js
seja importado dinamicamente.
Se hooks síncronos forem usados, tanto import
, require
e require
do usuário criado usando createRequire()
são suportados.
import { registerHooks, createRequire } from 'node:module'
registerHooks({
/* implementação de hooks síncronos */
})
const require = createRequire(import.meta.url)
// Os hooks síncronos afetam import, require() e a função require() do usuário
// criada por meio de createRequire().
await import('./my-app.js')
require('./my-app-2.js')
const { register, registerHooks } = require('node:module')
const { pathToFileURL } = require('node:url')
registerHooks({
/* implementação de hooks síncronos */
})
const userRequire = createRequire(__filename)
// Os hooks síncronos afetam import, require() e a função require() do usuário
// criada por meio de createRequire().
import('./my-app.js')
require('./my-app-2.js')
userRequire('./my-app-3.js')
Finalmente, se tudo o que você deseja fazer é registrar hooks antes que seu aplicativo seja executado e não quiser criar um arquivo separado para esse propósito, você pode passar um URL data:
para --import
:
node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("http-to-https", pathToFileURL("./"));' ./my-app.js
Encadeamento
É possível chamar register
mais de uma 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')
Neste exemplo, os hooks registrados formarão cadeias. Essas cadeias são executadas no estilo último a entrar, primeiro a sair (LIFO). Se tanto foo.mjs
quanto bar.mjs
definirem um hook resolve
, eles serão chamados da seguinte forma (observe da direita para a esquerda): o padrão do Node ← ./foo.mjs
← ./bar.mjs
(começando com ./bar.mjs
, depois ./foo.mjs
, e depois o padrão do Node.js). O mesmo se aplica a todos os outros hooks.
Os hooks registrados também afetam o próprio register
. Neste exemplo, bar.mjs
será resolvido e carregado por meio dos hooks registrados por foo.mjs
(porque os hooks de foo
já terão sido adicionados à cadeia). Isso permite coisas como escrever hooks em linguagens que não são JavaScript, desde que os hooks registrados anteriormente sejam transpilados para JavaScript.
O método register
não pode ser chamado de dentro do módulo que define os hooks.
O encadeamento de registerHooks
funciona de forma semelhante. Se hooks síncronos e assíncronos forem misturados, os hooks síncronos serão sempre executados primeiro antes que os hooks assíncronos comecem a ser executados, ou seja, no último hook síncrono a ser executado, seu próximo hook inclui a invocação dos hooks assíncronos.
// entrypoint.mjs
import { registerHooks } from 'node:module'
const hook1 = {
/* implementação de hooks */
}
const hook2 = {
/* implementação de hooks */
}
// hook2 é executado antes de hook1.
registerHooks(hook1)
registerHooks(hook2)
// entrypoint.cjs
const { registerHooks } = require('node:module')
const hook1 = {
/* implementação de hooks */
}
const hook2 = {
/* implementação de hooks */
}
// hook2 é executado antes de hook1.
registerHooks(hook1)
registerHooks(hook2)
Comunicação com hooks de customização de módulo
Hooks assíncronos são executados em uma thread dedicada, separada da thread principal que executa o código do aplicativo. Isso significa que mutar variáveis globais não afetará as outras threads, e canais de mensagens devem ser usados para comunicar entre as threads.
O método register
pode ser usado para passar dados para um hook initialize
. Os dados passados para o hook podem incluir objetos transferíveis como portas.
import { register } from 'node:module'
import { MessageChannel } from 'node:worker_threads'
// Este exemplo demonstra como um canal de mensagens pode ser usado para
// comunicar com os hooks, enviando `port2` para os 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 exemplo demonstra como um canal de mensagens pode ser usado para
// comunicar com os hooks, enviando `port2` para os 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],
})
Hooks de módulos síncronos são executados na mesma thread onde o código do aplicativo é executado. Eles podem mutar diretamente os globais do contexto acessados pela thread principal.
Hooks
Hooks assíncronos aceitos por module.register()
O método register
pode ser usado para registrar um módulo que exporta um conjunto de hooks. Os hooks são funções que são chamadas pelo Node.js para customizar o processo de resolução e carregamento de módulos. As funções exportadas devem ter nomes e assinaturas específicas, e devem ser exportadas como exportações nomeadas.
export async function initialize({ number, port }) {
// Recebe dados de `register`.
}
export async function resolve(specifier, context, nextResolve) {
// Pega um especificador `import` ou `require` e resolve-o para uma URL.
}
export async function load(url, context, nextLoad) {
// Pega uma URL resolvida e retorna o código-fonte a ser avaliado.
}
Hooks assíncronos são executados em uma thread separada, isolada da thread principal onde o código do aplicativo é executado. Isso significa que é um realm diferente. A thread dos hooks pode ser terminada pela thread principal a qualquer momento, então não dependa de operações assíncronas (como console.log
) para serem concluídas. Elas são herdadas para workers filhos por padrão.
Ganchos síncronos aceitos por module.registerHooks()
Adicionado em: v23.5.0
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.1 - Desenvolvimento ativo
O método module.registerHooks()
aceita funções de gancho síncronas. initialize()
não é suportado nem necessário, pois o implementador do gancho pode simplesmente executar o código de inicialização diretamente antes da chamada para module.registerHooks()
.
function resolve(specifier, context, nextResolve) {
// Recebe um especificador `import` ou `require` e o resolve para uma URL.
}
function load(url, context, nextLoad) {
// Recebe uma URL resolvida e retorna o código-fonte a ser avaliado.
}
Ganchos síncronos são executados na mesma thread e no mesmo realm onde os módulos são carregados. Ao contrário dos ganchos assíncronos, eles não são herdados por padrão em threads de worker filho, embora, se os ganchos forem registrados usando um arquivo pré-carregado por --import
ou --require
, threads de worker filho podem herdar os scripts pré-carregados por meio da herança de process.execArgv
. Consulte a documentação de Worker
para obter detalhes.
Em ganchos síncronos, os usuários podem esperar que console.log()
seja concluído da mesma forma que esperam que console.log()
no código do módulo seja concluído.
Convenções de ganchos
Ganchos fazem parte de uma cadeia, mesmo que essa cadeia consista em apenas um gancho personalizado (fornecido pelo usuário) e o gancho padrão, que está sempre presente. As funções de gancho se aninham: cada uma deve sempre retornar um objeto simples, e o encadeamento acontece como resultado de cada função chamar next\<hookName\>()
, que é uma referência ao gancho do carregador subsequente (em ordem LIFO).
Um gancho que retorna um valor sem uma propriedade necessária aciona uma exceção. Um gancho que retorna sem chamar next\<hookName\>()
e sem retornar shortCircuit: true
também aciona uma exceção. Esses erros são para ajudar a evitar interrupções não intencionais na cadeia. Retorne shortCircuit: true
de um gancho para sinalizar que a cadeia está terminando intencionalmente em seu gancho.
initialize()
Adicionado em: v20.6.0, v18.19.0
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.2 - Candidato a lançamento
data
<any> Os dados deregister(loader, import.meta.url, { data })
.
O hook initialize
é aceito apenas por register
. registerHooks()
não o suporta nem precisa dele, já que a inicialização feita para hooks síncronos pode ser executada diretamente antes da chamada para registerHooks()
.
O hook initialize
fornece uma maneira de definir uma função personalizada que é executada na thread de hooks quando o módulo de hooks é inicializado. A inicialização acontece quando o módulo de hooks é registrado via register
.
Este hook pode receber dados de uma invocação de register
, incluindo portas e outros objetos transferíveis. O valor de retorno de initialize
pode ser uma <Promise>, caso em que será aguardado antes que a execução da thread principal do aplicativo seja retomada.
Código de customização do módulo:
// path-to-my-hooks.js
export async function initialize({ number, port }) {
port.postMessage(`increment: ${number + 1}`)
}
Código do chamador:
import assert from 'node:assert'
import { register } from 'node:module'
import { MessageChannel } from 'node:worker_threads'
// Este exemplo mostra como um canal de mensagem pode ser usado para se comunicar
// entre a thread principal (do aplicativo) e os hooks executados na thread de hooks,
// enviando `port2` para o hook `initialize`.
const { port1, port2 } = new MessageChannel()
port1.on('message', msg => {
assert.strictEqual(msg, 'increment: 2')
})
port1.unref()
register('./path-to-my-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 exemplo mostra como um canal de mensagem pode ser usado para se comunicar
// entre a thread principal (do aplicativo) e os hooks executados na thread de hooks,
// enviando `port2` para o hook `initialize`.
const { port1, port2 } = new MessageChannel()
port1.on('message', msg => {
assert.strictEqual(msg, 'increment: 2')
})
port1.unref()
register('./path-to-my-hooks.js', {
parentURL: pathToFileURL(__filename),
data: { number: 1, port: port2 },
transferList: [port2],
})
resolve(specifier, context, nextResolve)
[Histórico]
Versão | Mudanças |
---|---|
v23.5.0 | Adiciona suporte para hooks síncronos e em thread. |
v21.0.0, v20.10.0, v18.19.0 | A propriedade context.importAssertions é substituída por context.importAttributes . O uso do nome antigo ainda é suportado e emitirá um aviso experimental. |
v18.6.0, v16.17.0 | Adiciona suporte para encadeamento de hooks de resolução. Cada hook deve chamar nextResolve() ou incluir uma propriedade shortCircuit definida como true em seu retorno. |
v17.1.0, v16.14.0 | Adiciona suporte para asserções de importação. |
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.2 - Candidato a lançamento (versão assíncrona) Estabilidade: 1.1 - Desenvolvimento ativo (versão síncrona)
specifier
<string>context
<Object>conditions
<string[]> Condições de exportação dopackage.json
relevanteimportAttributes
<Object> Um objeto cujos pares de chave-valor representam os atributos para o módulo a ser importadoparentURL
<string> | <undefined> O módulo que está importando este, ou indefinido se este for o ponto de entrada do Node.js
nextResolve
<Function> O hookresolve
subsequente na cadeia, ou o hookresolve
padrão do Node.js após o último hookresolve
fornecido pelo usuárioRetorna: <Object> | <Promise> A versão assíncrona recebe um objeto contendo as seguintes propriedades ou uma
Promise
que será resolvida para tal objeto. A versão síncrona aceita apenas um objeto retornado de forma síncrona.format
<string> | <null> | <undefined> Uma dica para o hook de carregamento (pode ser ignorada)'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'
importAttributes
<Object> | <undefined> Os atributos de importação a serem usados ao armazenar o módulo em cache (opcional; se excluído, a entrada será usada)shortCircuit
<undefined> | <boolean> Um sinal de que este hook pretende terminar a cadeia de hooksresolve
. Padrão:false
url
<string> A URL absoluta para a qual esta entrada é resolvida
A cadeia de hooks resolve
é responsável por dizer ao Node.js onde encontrar e como armazenar em cache uma determinada declaração ou expressão import
, ou chamada require
. Opcionalmente, ele pode retornar um formato (como 'module'
) como uma dica para o hook load
. Se um formato for especificado, o hook load
é o responsável final por fornecer o valor format
final (e é livre para ignorar a dica fornecida por resolve
); se resolve
fornecer um format
, um hook load
personalizado é necessário mesmo que seja apenas para passar o valor para o hook load
padrão do Node.js.
Os atributos de tipo de importação fazem parte da chave de cache para salvar módulos carregados no cache de módulos interno. O hook resolve
é responsável por retornar um objeto importAttributes
se o módulo deve ser armazenado em cache com atributos diferentes daqueles presentes no código-fonte.
A propriedade conditions
em context
é um array de condições que serão usadas para corresponder às condições de exportação de pacote para esta solicitação de resolução. Elas podem ser usadas para procurar mapeamentos condicionais em outro lugar ou para modificar a lista ao chamar a lógica de resolução padrão.
As atuais condições de exportação de pacote estão sempre no array context.conditions
passado para o hook. Para garantir o comportamento de resolução de especificador de módulo padrão do Node.js ao chamar defaultResolve
, o array context.conditions
passado para ele deve incluir todos os elementos do array context.conditions
originalmente passado para o hook resolve
.
// Versão assíncrona aceita por module.register().
export async function resolve(specifier, context, nextResolve) {
const { parentURL = null } = context
if (Math.random() > 0.5) {
// Alguma condição.
// Para alguns ou todos os especificadores, faça alguma lógica personalizada para resolução.
// Sempre retorne um objeto da forma {url: <string>}.
return {
shortCircuit: true,
url: parentURL ? new URL(specifier, parentURL).href : new URL(specifier).href,
}
}
if (Math.random() < 0.5) {
// Outra condição.
// Ao chamar `defaultResolve`, os argumentos podem ser modificados. Nesse caso, está adicionando outro valor para correspondência de exportações condicionais.
return nextResolve(specifier, {
...context,
conditions: [...context.conditions, 'outra-condição'],
})
}
// Remeta para o próximo hook na cadeia, que seria a resolução padrão do Node.js se este for o último carregador especificado pelo usuário.
return nextResolve(specifier)
}
// Versão síncrona aceita por module.registerHooks().
function resolve(specifier, context, nextResolve) {
// Semelhante ao resolve() assíncrono acima, já que aquele não possui
// nenhuma lógica assíncrona.
}
load(url, context, nextLoad)
[Histórico]
Versão | Mudanças |
---|---|
v23.5.0 | Adicionado suporte para versão síncrona e em thread. |
v20.6.0 | Adicionado suporte para source com formato commonjs . |
v18.6.0, v16.17.0 | Adicionado suporte para encadeamento de hooks de carregamento. Cada hook deve chamar nextLoad() ou incluir uma propriedade shortCircuit definida como true em seu retorno. |
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1.2 - Candidato a lançamento (versão assíncrona) Estabilidade: 1.1 - Desenvolvimento ativo (versão síncrona)
url
<string> A URL retornada pela cadeia deresolve
context
<Objeto>conditions
<string[]> Condições de exportação dopackage.json
relevanteformat
<string> | <null> | <undefined> O formato opcionalmente fornecido pela cadeia de hookresolve
importAttributes
<Objeto>
nextLoad
<Função> O hookload
subsequente na cadeia, ou o hookload
padrão do Node.js após o último hookload
fornecido pelo usuárioRetorna: <Objeto> | <Promise> A versão assíncrona recebe um objeto contendo as seguintes propriedades ou uma
Promise
que será resolvida para tal objeto. A versão síncrona aceita apenas um objeto retornado de forma síncrona.format
<string>shortCircuit
<undefined> | <boolean> Um sinal de que este hook pretende encerrar a cadeia de hooksload
. Padrão:false
source
<string> | <ArrayBuffer> | <TypedArray> A fonte para o Node.js avaliar
O hook load
fornece uma maneira de definir um método personalizado de determinar como uma URL deve ser interpretada, recuperada e analisada. Ele também é responsável por validar os atributos de importação.
O valor final de format
deve ser um dos seguintes:
format | Descrição | Tipos aceitáveis para source retornada por load |
---|---|---|
'builtin' | Carregar um módulo embutido do Node.js | Não aplicável |
'commonjs' | Carregar um módulo CommonJS do Node.js | { string , ArrayBuffer , TypedArray , null , undefined } |
'json' | Carregar um arquivo JSON | { string , ArrayBuffer , TypedArray } |
'module' | Carregar um módulo ES | { string , ArrayBuffer , TypedArray } |
'wasm' | Carregar um módulo WebAssembly | { ArrayBuffer , TypedArray } |
O valor de source
é ignorado para o tipo 'builtin'
porque atualmente não é possível substituir o valor de um módulo embutido (core) do Node.js.
Advertência no hook load
assíncrono
Ao usar o hook load
assíncrono, omitir vs fornecer um source
para 'commonjs'
tem efeitos muito diferentes:
- Quando um
source
é fornecido, todas as chamadasrequire
deste módulo serão processadas pelo carregador ESM com hooksresolve
eload
registrados; todas as chamadasrequire.resolve
deste módulo serão processadas pelo carregador ESM com hooksresolve
registrados; apenas um subconjunto da API CommonJS estará disponível (por exemplo, semrequire.extensions
, semrequire.cache
, semrequire.resolve.paths
) e o monkey-patching no carregador de módulo CommonJS não será aplicado. - Se
source
for indefinido ounull
, ele será tratado pelo carregador de módulo CommonJS e as chamadasrequire
/require.resolve
não passarão pelos hooks registrados. Este comportamento parasource
nulo é temporário — no futuro,source
nulo não será suportado.
Estas advertências não se aplicam ao hook load
síncrono, caso em que o conjunto completo de APIs CommonJS disponível para os módulos CommonJS personalizados e require
/require.resolve
sempre passam pelos hooks registrados.
A implementação interna assíncrona load
do Node.js, que é o valor de next
para o último hook na cadeia load
, retorna null
para source
quando format
é 'commonjs'
por compatibilidade com versões anteriores. Aqui está um exemplo de hook que optaria por usar o comportamento não padrão:
import { readFile } from 'node:fs/promises'
// Versão assíncrona aceita por module.register(). Esta correção não é necessária
// para a versão síncrona aceita 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
}
Isto também não se aplica ao hook load
síncrono, caso em que o source
retornado contém o código-fonte carregado pelo próximo hook, independentemente do formato do módulo.
- O objeto específico
ArrayBuffer
é umSharedArrayBuffer
. - O objeto específico
TypedArray
é umUint8Array
.
Se o valor de origem de um formato baseado em texto (ou seja, 'json'
, 'module'
) não for uma string, ele será convertido em uma string usando util.TextDecoder
.
O hook load
fornece uma maneira de definir um método personalizado para recuperar o código-fonte de um URL resolvido. Isso permitiria que um carregador evitasse potencialmente a leitura de arquivos do disco. Ele também pode ser usado para mapear um formato não reconhecido para um formato suportado, por exemplo, yaml
para module
.
// Versão assíncrona aceita por module.register().
export async function load(url, context, nextLoad) {
const { format } = context
if (Math.random() > 0.5) {
// Alguma condição
/*
Para alguns ou todos os URLs, faça alguma lógica personalizada para recuperar a origem.
Sempre retorne um objeto da forma {
format: <string>,
source: <string|buffer>,
}.
*/
return {
format,
shortCircuit: true,
source: '...',
}
}
// Deferir para o próximo hook na cadeia.
return nextLoad(url)
}
// Versão síncrona aceita por module.registerHooks().
function load(url, context, nextLoad) {
// Semelhante ao load() assíncrono acima, já que este não tem
// nenhuma lógica assíncrona.
}
Num cenário mais avançado, isto também pode ser usado para transformar uma fonte não suportada numa suportada (ver Exemplos abaixo).
Exemplos
Os vários hooks de customização de módulo podem ser usados juntos para realizar customizações de grande alcance nos comportamentos de carregamento e avaliação de código Node.js.
Importar de HTTPS
O hook abaixo registra hooks para habilitar suporte rudimentar para tais especificadores. Embora isso possa parecer uma melhoria significativa para a funcionalidade principal do Node.js, existem desvantagens substanciais no uso real desses hooks: o desempenho é muito mais lento do que carregar arquivos do disco, não há cache e não há segurança.
// https-hooks.mjs
import { get } from 'node:https'
export function load(url, context, nextLoad) {
// Para que o JavaScript seja carregado pela rede, precisamos buscá-lo e
// retorná-lo.
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 exemplo assume que todo JavaScript fornecido pela rede é
// código de módulo ES.
format: 'module',
shortCircuit: true,
source: data,
})
)
}).on('error', err => reject(err))
})
}
// Deixe o Node.js lidar com todas as outras URLs.
return nextLoad(url)
}
// main.mjs
import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js'
console.log(VERSION)
Com o módulo de hooks anterior, executar node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./https-hooks.mjs"));' ./main.mjs
imprime a versão atual do CoffeeScript por módulo no URL em main.mjs
.
Transpilação
Fontes que estão em formatos que o Node.js não entende podem ser convertidas em JavaScript usando o hook de carregamento
.
Isso é menos performático do que transcompilar arquivos de origem antes de executar o Node.js; hooks de transpilador só devem ser usados para fins de desenvolvimento e teste.
Versão Assí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)) {
// Arquivos CoffeeScript podem ser tanto CommonJS quanto módulos ES, então queremos que
// qualquer arquivo CoffeeScript seja tratado pelo Node.js da mesma forma que um arquivo .js
// na mesma localização. Para determinar como o Node.js interpretaria um arquivo .js arbitrário,
// procure no sistema de arquivos pelo arquivo package.json pai mais próximo
// e leia seu campo "type".
const format = await getPackageType(url)
const { source: rawSource } = await nextLoad(url, { ...context, format })
// Este hook converte o código-fonte CoffeeScript em código-fonte JavaScript
// para todos os arquivos CoffeeScript importados.
const transformedSource = coffeescript.compile(rawSource.toString(), url)
return {
format,
shortCircuit: true,
source: transformedSource,
}
}
// Deixe o Node.js lidar com todos os outros URLs.
return nextLoad(url)
}
async function getPackageType(url) {
// `url` é apenas um caminho de arquivo durante a primeira iteração quando passado o
// url resolvido do hook load()
// um caminho de arquivo real de load() conterá uma extensão de arquivo, pois é
// exigido pela especificação
// esta simples verificação de verdade para saber se `url` contém uma extensão de arquivo irá
// funcionar para a maioria dos projetos, mas não cobre alguns casos extremos (como
// arquivos sem extensão ou um url terminando em um espaço à direita)
const isFilePath = !!extname(url)
// Se for um caminho de arquivo, obtenha o diretório em que está
const dir = isFilePath ? dirname(fileURLToPath(url)) : url
// Componha um caminho de arquivo para um package.json no mesmo diretório,
// que pode ou não existir
const packagePath = resolvePath(dir, 'package.json')
// Tente ler o package.json possivelmente inexistente
const type = await readFile(packagePath, { encoding: 'utf8' })
.then(filestring => JSON.parse(filestring).type)
.catch(err => {
if (err?.code !== 'ENOENT') console.error(err)
})
// Se o package.json existisse e contivesse um campo `type` com um valor, voilà
if (type) return type
// Caso contrário, (se não estiver na raiz) continue verificando o próximo diretório acima
// Se estiver na raiz, pare e retorne false
return dir.length > 1 && getPackageType(resolvePath(dir, '..'))
}
Versão 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 })
Executando Hooks
# main.coffee {#maincoffee}
import { scream } from './scream.coffee'
console.log scream 'hello, world'
import { version } from 'node:process'
console.log "Trazido a você pela versão Node.js #{version}"
# scream.coffee {#screamcoffee}
export scream = (str) -> str.toUpperCase()
Com os módulos de hooks precedentes, executar node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee
ou node --import ./coffeescript-sync-hooks.mjs ./main.coffee
faz com que main.coffee
seja transformado em JavaScript depois que seu código-fonte é carregado do disco, mas antes que o Node.js o execute; e assim por diante para quaisquer arquivos .coffee
, .litcoffee
ou .coffee.md
referenciados por meio de instruções import
de qualquer arquivo carregado.
Mapas de importação
Os dois exemplos anteriores definiam hooks load
. Este é um exemplo de um hook resolve
. Este módulo de hooks lê um arquivo import-map.json
que define quais especificadores substituir para outros URLs (esta é uma implementação muito simplista de um pequeno subconjunto da especificação de "mapas de importação").
Versão assí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)
}
Versão 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 })
Usando os hooks
Com esses arquivos:
// main.js
import 'a-module'
// import-map.json
{
"imports": {
"a-module": "./some-module.js"
}
}
// some-module.js
console.log('some module!')
Executar node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./import-map-hooks.js"));' main.js
ou node --import ./import-map-sync-hooks.js main.js
deve imprimir some module!
.
Suporte a source map v3
Adicionado em: v13.7.0, v12.17.0
[Estável: 1 - Experimental]
Estável: 1 Estabilidade: 1 - Experimental
Auxiliares para interagir com o cache de source map. Este cache é populado quando o parsing de source map está habilitado e diretivas de inclusão de source map são encontradas no rodapé de um módulo.
Para habilitar o parsing de source map, o Node.js deve ser executado com a flag --enable-source-maps
, ou com a cobertura de código habilitada definindo NODE_V8_COVERAGE=dir
.
// module.mjs
// Em um módulo ECMAScript
import { findSourceMap, SourceMap } from 'node:module'
// module.cjs
// Em um módulo CommonJS
const { findSourceMap, SourceMap } = require('node:module')
module.findSourceMap(path)
Adicionado em: v13.7.0, v12.17.0
path
<string>- Retorna: <module.SourceMap> | <undefined> Retorna
module.SourceMap
se um mapa de origem for encontrado,undefined
caso contrário.
path
é o caminho resolvido para o arquivo para o qual um mapa de origem correspondente deve ser buscado.
Classe: module.SourceMap
Adicionado em: v13.7.0, v12.17.0
new SourceMap(payload[, { lineLengths }])
{#new-sourcemappayload-{-linelengths-}}
payload
<Objeto>lineLengths
<number[]>
Cria uma nova instância sourceMap
.
payload
é um objeto com chaves correspondentes ao formato Source map v3:
file
: <string>version
: <number>sources
: <string[]>sourcesContent
: <string[]>names
: <string[]>mappings
: <string>sourceRoot
: <string>
lineLengths
é um array opcional do comprimento de cada linha no código gerado.
sourceMap.payload
- Retorna: <Objeto>
Getter para o payload usado para construir a instância SourceMap
.
sourceMap.findEntry(lineOffset, columnOffset)
lineOffset
<number> O deslocamento do número da linha com índice zero na fonte geradacolumnOffset
<number> O deslocamento do número da coluna com índice zero na fonte gerada- Retorna: <Object>
Dado um deslocamento de linha e um deslocamento de coluna no arquivo de origem gerado, retorna um objeto representando o intervalo do SourceMap no arquivo original, se encontrado, ou um objeto vazio, caso contrário.
O objeto retornado contém as seguintes chaves:
- generatedLine: <number> O deslocamento da linha do início do intervalo na fonte gerada
- generatedColumn: <number> O deslocamento da coluna do início do intervalo na fonte gerada
- originalSource: <string> O nome do arquivo da fonte original, conforme relatado no SourceMap
- originalLine: <number> O deslocamento da linha do início do intervalo na fonte original
- originalColumn: <number> O deslocamento da coluna do início do intervalo na fonte original
- name: <string>
O valor retornado representa o intervalo bruto, conforme aparece no SourceMap, com base em deslocamentos com índice zero, não números de linha e coluna com índice 1, conforme aparecem em mensagens de erro e objetos CallSite.
Para obter os números de linha e coluna com índice 1 correspondentes de um lineNumber e columnNumber, conforme são relatados por pilhas de erros e objetos CallSite, use sourceMap.findOrigin(lineNumber, columnNumber)
sourceMap.findOrigin(lineNumber, columnNumber)
lineNumber
<number> O número da linha (indexado a 1) do local da chamada na fonte geradacolumnNumber
<number> O número da coluna (indexado a 1) do local da chamada na fonte gerada- Retorna: <Object>
Dado um lineNumber
e um columnNumber
indexados a 1 de um local de chamada na fonte gerada, encontra o local de chamada correspondente na fonte original.
Se o lineNumber
e o columnNumber
fornecidos não forem encontrados em nenhum mapa de origem, um objeto vazio será retornado. Caso contrário, o objeto retornado contém as seguintes chaves:
- name: <string> | <undefined> O nome do intervalo no mapa de origem, se um tiver sido fornecido
- fileName: <string> O nome do arquivo da fonte original, conforme relatado no SourceMap
- lineNumber: <number> O número da linha (indexado a 1) do local da chamada correspondente na fonte original
- columnNumber: <number> O número da coluna (indexado a 1) do local da chamada correspondente na fonte original