Moduli: API node:module
Aggiunto in: v0.3.7
L'oggetto Module
Fornisce metodi di utilità generali quando si interagisce con istanze di Module
, la variabile module
spesso vista nei moduli CommonJS. Vi si accede tramite import 'node:module'
o require('node:module')
.
module.builtinModules
[Cronologia]
Versione | Modifiche |
---|---|
v23.5.0 | La lista ora contiene anche i moduli solo con prefisso. |
v9.3.0, v8.10.0, v6.13.0 | Aggiunto in: v9.3.0, v8.10.0, v6.13.0 |
Un elenco dei nomi di tutti i moduli forniti da Node.js. Può essere utilizzato per verificare se un modulo è gestito da terzi o meno.
module
in questo contesto non è lo stesso oggetto fornito dal wrapper del modulo. Per accedervi, è necessario richiedere il modulo Module
:
// module.mjs
// In un modulo ECMAScript
import { builtinModules as builtin } from 'node:module'
// module.cjs
// In un modulo CommonJS
const builtin = require('node:module').builtinModules
module.createRequire(filename)
Aggiunto in: v12.2.0
filename
<string> | <URL> Nome del file da utilizzare per costruire la funzione require. Deve essere un oggetto URL di file, una stringa URL di file o una stringa di percorso assoluto.- Restituisce: <require> Funzione Require
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
// sibling-module.js è un modulo CommonJS.
const siblingModule = require('./sibling-module')
module.findPackageJSON(specifier[, base])
Aggiunto in: v23.2.0
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.1 - Sviluppo attivo
specifier
<string> | <URL> Lo specificatore per il modulo di cui recuperare ilpackage.json
. Quando si passa uno specificatore nudo, viene restituito ilpackage.json
alla radice del pacchetto. Quando si passa uno specificatore relativo o uno specificatore assoluto, viene restituito ilpackage.json
genitore più vicino.base
<string> | <URL> La posizione assoluta (stringa URLfile:
o percorso FS) del modulo contenitore. Per CJS, utilizzare__filename
(non__dirname
!); per ESM, utilizzareimport.meta.url
. Non è necessario passarlo sespecifier
è unospecificatore assoluto
.- Restituisce: <string> | <undefined> Un percorso se il
package.json
viene trovato. QuandostartLocation
è un pacchetto, ilpackage.json
radice del pacchetto; quando è relativo o non risolto, ilpackage.json
più vicino allastartLocation
.
/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'
// Stesso risultato quando si passa uno specificatore assoluto invece:
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'
// Quando si passa uno specificatore assoluto, si potrebbe ottenere un risultato diverso se il
// modulo risolto si trova all'interno di una sottocartella che ha `package.json` nidificati.
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'
// Stesso risultato quando si passa uno specificatore assoluto invece:
findPackageJSON(pathToFileURL(path.join(__dirname, '..')))
findPackageJSON('some-package', __filename)
// '/path/to/project/packages/bar/node_modules/some-package/package.json'
// Quando si passa uno specificatore assoluto, si potrebbe ottenere un risultato diverso se il
// modulo risolto si trova all'interno di una sottocartella che ha `package.json` nidificati.
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)
Aggiunto in: v18.6.0, v16.17.0
moduleName
<string> nome del modulo- Restituisce: <boolean> restituisce true se il modulo è interno, altrimenti restituisce false
import { isBuiltin } from 'node:module'
isBuiltin('node:fs') // true
isBuiltin('fs') // true
isBuiltin('wss') // false
module.register(specifier[, parentURL][, options])
[Cronologia]
Versione | Modifiche |
---|---|
v20.8.0, v18.19.0 | Aggiunto supporto per istanze URL WHATWG. |
v20.6.0, v18.19.0 | Aggiunto in: v20.6.0, v18.19.0 |
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.2 - Release candidate
specifier
<string> | <URL> Hook di personalizzazione da registrare; questa dovrebbe essere la stessa stringa che verrebbe passata aimport()
, tranne per il fatto che se è relativa, viene risolta rispetto aparentURL
.parentURL
<string> | <URL> Se vuoi risolverespecifier
rispetto a un URL di base, comeimport.meta.url
, puoi passare quell'URL qui. Predefinito:'data:'
options
<Oggetto>parentURL
<string> | <URL> Se vuoi risolverespecifier
rispetto a un URL di base, comeimport.meta.url
, puoi passare quell'URL qui. Questa proprietà viene ignorata separentURL
viene fornita come secondo argomento. Predefinito:'data:'
data
<any> Qualsiasi valore JavaScript arbitrario e clonabile da passare all'hookinitialize
.transferList
<Object[]> oggetti trasferibili da passare all'hookinitialize
.
Registra un modulo che esporta hook che personalizzano la risoluzione e il comportamento di caricamento dei moduli Node.js. Vedi Hook di personalizzazione.
module.registerHooks(options)
Aggiunto in: v23.5.0
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.1 - Sviluppo attivo
options
<Oggetto>load
<Funzione> | <undefined> Vedere hook di caricamento. Predefinito:undefined
.resolve
<Funzione> | <undefined> Vedere hook di risoluzione. Predefinito:undefined
.
Registra gli hook che personalizzano la risoluzione dei moduli di Node.js e il comportamento di caricamento. Vedere Hook di personalizzazione.
module.stripTypeScriptTypes(code[, options])
Aggiunto in: v23.2.0
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.1 - Sviluppo attivo
code
<stringa> Il codice da cui rimuovere le annotazioni di tipo.options
<Oggetto>mode
<stringa> Predefinito:'strip'
. I valori possibili sono:'strip'
Rimuove solo le annotazioni di tipo senza eseguire la trasformazione delle funzionalità di TypeScript.'transform'
Rimuove le annotazioni di tipo e trasforma le funzionalità di TypeScript in JavaScript.sourceMap
<booleano> Predefinito:false
. Solo quandomode
è'transform'
, setrue
, verrà generata una source map per il codice trasformato.sourceUrl
<stringa> Specifica l'URL di origine utilizzato nella source map.
Restituisce: <stringa> Il codice con le annotazioni di tipo rimosse.
module.stripTypeScriptTypes()
rimuove le annotazioni di tipo dal codice TypeScript. Può essere utilizzato per rimuovere le annotazioni di tipo dal codice TypeScript prima di eseguirlo convm.runInContext()
ovm.compileFunction()
. Per impostazione predefinita, genererà un errore se il codice contiene funzionalità di TypeScript che richiedono una trasformazione comeEnums
, vedere rimozione del tipo per maggiori informazioni. Quando la modalità è'transform'
, trasforma anche le funzionalità di TypeScript in JavaScript, vedere trasformazione delle funzionalità di TypeScript per maggiori informazioni. Quando la modalità è'strip'
, le source map non vengono generate, perché le posizioni vengono conservate. Se viene fornitasourceMap
, quando la modalità è'strip'
, verrà generato un errore.
ATTENZIONE: L'output di questa funzione non deve essere considerato stabile tra le versioni di Node.js, a causa delle modifiche nel parser di TypeScript.
import { stripTypeScriptTypes } from 'node:module'
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code)
console.log(strippedCode)
// Stampa: const a = 1;
const { stripTypeScriptTypes } = require('node:module')
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code)
console.log(strippedCode)
// Stampa: const a = 1;
Se viene fornito sourceUrl
, verrà utilizzato aggiunto come commento alla fine dell'output:
import { stripTypeScriptTypes } from 'node:module'
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' })
console.log(strippedCode)
// Stampa: 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)
// Stampa: const a = 1\n\n//# sourceURL=source.ts;
Quando mode
è 'transform'
, il codice viene trasformato in 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)
// Stampa:
// 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)
// Stampa:
// var MathUtil;
// (function(MathUtil) {
// MathUtil.add = (a, b)=>a + b;
// })(MathUtil || (MathUtil = {}));
// # sourceMappingURL=data:application/json;base64, ...
module.syncBuiltinESMExports()
Aggiunto in: v12.12.0
Il metodo module.syncBuiltinESMExports()
aggiorna tutti i binding attivi per gli ES Modules integrati per corrispondere alle proprietà delle esportazioni CommonJS. Non aggiunge né rimuove nomi esportati dagli ES Modules.
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 => {
// Sincronizza la proprietà readFile esistente con il nuovo valore
assert.strictEqual(esmFS.readFile, newAPI)
// readFileSync è stato eliminato da fs richiesto
assert.strictEqual('readFileSync' in fs, false)
// syncBuiltinESMExports() non rimuove readFileSync da esmFS
assert.strictEqual('readFileSync' in esmFS, true)
// syncBuiltinESMExports() non aggiunge nomi
assert.strictEqual(esmFS.newAPI, undefined)
})
Cache di compilazione dei moduli
[Cronologia]
Versione | Modifiche |
---|---|
v22.8.0 | aggiunge le API JavaScript iniziali per l'accesso in fase di esecuzione. |
v22.1.0 | Aggiunto in: v22.1.0 |
La cache di compilazione dei moduli può essere abilitata utilizzando il metodo module.enableCompileCache()
o la variabile d'ambiente NODE_COMPILE_CACHE=dir
. Dopo che è stata abilitata, ogni volta che Node.js compila un CommonJS o un modulo ECMAScript, utilizzerà la cache del codice V8 su disco, persistita nella directory specificata, per velocizzare la compilazione. Ciò può rallentare il primo caricamento di un grafo di moduli, ma i successivi caricamenti dello stesso grafo di moduli potrebbero ottenere un significativo aumento della velocità se il contenuto dei moduli non cambia.
Per ripulire la cache di compilazione generata su disco, è sufficiente rimuovere la directory della cache. La directory della cache verrà ricreata la prossima volta che la stessa directory verrà utilizzata per l'archiviazione della cache di compilazione. Per evitare di riempire il disco con una cache obsoleta, si consiglia di utilizzare una directory in os.tmpdir()
. Se la cache di compilazione è abilitata da una chiamata a module.enableCompileCache()
senza specificare la directory, Node.js utilizzerà la variabile d'ambiente NODE_COMPILE_CACHE=dir
se impostata, o altrimenti il valore predefinito path.join(os.tmpdir(), 'node-compile-cache')
. Per individuare la directory della cache di compilazione utilizzata da un'istanza di Node.js in esecuzione, utilizzare module.getCompileCacheDir()
.
Attualmente, quando si utilizza la cache di compilazione con la code coverage JavaScript di V8, la copertura raccolta da V8 potrebbe essere meno precisa nelle funzioni che vengono deserializzate dalla cache del codice. Si consiglia di disattivare questa opzione quando si eseguono i test per generare una copertura precisa.
La cache di compilazione del modulo abilitata può essere disabilitata dalla variabile d'ambiente NODE_DISABLE_COMPILE_CACHE=1
. Ciò può essere utile quando la cache di compilazione comporta comportamenti imprevisti o indesiderati (ad esempio, una copertura di test meno precisa).
La cache di compilazione generata da una versione di Node.js non può essere riutilizzata da una versione diversa di Node.js. La cache generata da diverse versioni di Node.js verrà archiviata separatamente se viene utilizzata la stessa directory di base per rendere persistente la cache, in modo che possano coesistere.
Al momento, quando la cache di compilazione è abilitata e un modulo viene caricato da zero, la cache di codice viene generata immediatamente dal codice compilato, ma verrà scritta su disco solo quando l'istanza di Node.js sta per uscire. Ciò è soggetto a modifiche. Il metodo module.flushCompileCache()
può essere utilizzato per garantire che la cache di codice accumulata venga scaricata su disco nel caso in cui l'applicazione voglia generare altre istanze di Node.js e consentire loro di condividere la cache molto prima che il padre esca.
module.constants.compileCacheStatus
Aggiunto in: v22.8.0
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.1 - Sviluppo attivo
Le seguenti costanti vengono restituite come campo status
nell'oggetto restituito da module.enableCompileCache()
per indicare il risultato del tentativo di abilitare la cache di compilazione del modulo.
Costante | Descrizione |
---|---|
ENABLED | Node.js ha abilitato con successo la cache di compilazione. La directory utilizzata per archiviare la cache di compilazione verrà restituita nel campo directory dell'oggetto restituito. |
ALREADY_ENABLED | La cache di compilazione è già stata abilitata in precedenza, tramite una precedente chiamata a module.enableCompileCache() o tramite la variabile d'ambiente NODE_COMPILE_CACHE=dir . La directory utilizzata per archiviare la cache di compilazione verrà restituita nel campo directory dell'oggetto restituito. |
FAILED | Node.js non riesce ad abilitare la cache di compilazione. Ciò può essere causato dalla mancanza di autorizzazioni per utilizzare la directory specificata o da vari tipi di errori del file system. Il dettaglio dell'errore verrà restituito nel campo message dell'oggetto restituito. |
DISABLED | Node.js non può abilitare la cache di compilazione perché è stata impostata la variabile d'ambiente NODE_DISABLE_COMPILE_CACHE=1 . |
module.enableCompileCache([cacheDir])
Aggiunto in: v22.8.0
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.1 - Sviluppo attivo
cacheDir
<stringa> | <undefined> Percorso opzionale per specificare la directory in cui verrà archiviata/recuperata la cache di compilazione.- Restituisce: <Oggetto>
status
<intero> Uno deimodule.constants.compileCacheStatus
message
<stringa> | <undefined> Se Node.js non può abilitare la cache di compilazione, qui si trova il messaggio di errore. Impostato solo sestatus
èmodule.constants.compileCacheStatus.FAILED
.directory
<stringa> | <undefined> Se la cache di compilazione è abilitata, qui si trova la directory in cui è archiviata. Impostato solo sestatus
èmodule.constants.compileCacheStatus.ENABLED
omodule.constants.compileCacheStatus.ALREADY_ENABLED
.
Abilita la cache di compilazione del modulo nell'istanza corrente di Node.js.
Se cacheDir
non è specificato, Node.js utilizzerà la directory specificata dalla variabile d'ambiente NODE_COMPILE_CACHE=dir
se è impostata, oppure utilizzerà path.join(os.tmpdir(), 'node-compile-cache')
altrimenti. Per casi d'uso generali, si consiglia di chiamare module.enableCompileCache()
senza specificare cacheDir
, in modo che la directory possa essere sovrascritta dalla variabile d'ambiente NODE_COMPILE_CACHE
quando necessario.
Poiché la cache di compilazione dovrebbe essere un'ottimizzazione silenziosa non necessaria per il funzionamento dell'applicazione, questo metodo è progettato per non generare alcuna eccezione quando la cache di compilazione non può essere abilitata. Invece, restituirà un oggetto contenente un messaggio di errore nel campo message
per facilitare il debug. Se la cache di compilazione è abilitata con successo, il campo directory
nell'oggetto restituito contiene il percorso della directory in cui è archiviata la cache di compilazione. Il campo status
nell'oggetto restituito sarà uno dei valori module.constants.compileCacheStatus
per indicare il risultato del tentativo di abilitare la cache di compilazione del modulo.
Questo metodo influisce solo sull'istanza corrente di Node.js. Per abilitarla nei thread worker figlio, chiamare questo metodo anche nei thread worker figlio oppure impostare il valore process.env.NODE_COMPILE_CACHE
sulla directory della cache di compilazione in modo che il comportamento possa essere ereditato nei worker figlio. La directory può essere ottenuta dal campo directory
restituito da questo metodo oppure con module.getCompileCacheDir()
.
module.flushCompileCache()
Aggiunto in: v23.0.0
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.1 - Sviluppo attivo
Svuota la cache di compilazione del modulo accumulata dai moduli già caricati nell'istanza corrente di Node.js su disco. Questa funzione ritorna dopo che tutte le operazioni di svuotamento del file system sono terminate, sia che abbiano avuto successo che no. In caso di errori, la funzione fallirà silenziosamente, poiché gli errori della cache di compilazione non dovrebbero interferire con il funzionamento effettivo dell'applicazione.
module.getCompileCacheDir()
Aggiunto in: v22.8.0
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.1 - Sviluppo attivo
- Restituisce: <string> | <undefined> Percorso della directory della cache di compilazione del modulo se è abilitata, altrimenti
undefined
.
Hook di personalizzazione
[Cronologia]
Versione | Modifiche |
---|---|
v23.5.0 | Aggiunto il supporto per gli hook sincroni e in-thread. |
v20.6.0, v18.19.0 | Aggiunto l'hook initialize per sostituire globalPreload . |
v18.6.0, v16.17.0 | Aggiunto il supporto per il concatenamento dei loader. |
v16.12.0 | Rimossi getFormat , getSource , transformSource e globalPreload ; aggiunti l'hook load e l'hook getGlobalPreload . |
v8.8.0 | Aggiunto in: v8.8.0 |
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.2 - Candidato per la release (versione asincrona) Stabilità: 1.1 - Sviluppo attivo (versione sincrona)
Sono supportati due tipi di hook di personalizzazione del modulo:
Abilitazione
La risoluzione e il caricamento dei moduli possono essere personalizzati tramite:
Gli hook possono essere registrati prima dell'esecuzione del codice dell'applicazione utilizzando i flag --import
o --require
:
node --import ./register-hooks.js ./my-app.js
node --require ./register-hooks.js ./my-app.js
// register-hooks.js
// Questo file può essere require()-ed solo se non contiene await di livello superiore.
// Utilizzare module.register() per registrare hook asincroni in un thread dedicato.
import { register } from 'node:module'
register('./hooks.mjs', import.meta.url)
// register-hooks.js
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
// Utilizzare module.register() per registrare hook asincroni in un thread dedicato.
register('./hooks.mjs', pathToFileURL(__filename))
// Utilizzare module.registerHooks() per registrare hook sincroni nel thread principale.
import { registerHooks } from 'node:module'
registerHooks({
resolve(specifier, context, nextResolve) {
/* implementazione */
},
load(url, context, nextLoad) {
/* implementazione */
},
})
// Utilizzare module.registerHooks() per registrare hook sincroni nel thread principale.
const { registerHooks } = require('node:module')
registerHooks({
resolve(specifier, context, nextResolve) {
/* implementazione */
},
load(url, context, nextLoad) {
/* implementazione */
},
})
Il file passato a --import
o --require
può anche essere un'esportazione da una dipendenza:
node --import some-package/register ./my-app.js
node --require some-package/register ./my-app.js
Dove some-package
ha un campo "exports"
che definisce l'esportazione /register
per mappare a un file che chiama register()
, come il seguente esempio register-hooks.js
.
L'uso di --import
o --require
garantisce che gli hook vengano registrati prima che vengano importati tutti i file dell'applicazione, incluso il punto di ingresso dell'applicazione e anche per qualsiasi thread worker per impostazione predefinita.
In alternativa, register()
e registerHooks()
possono essere chiamati dal punto di ingresso, sebbene import()
dinamico debba essere utilizzato per qualsiasi codice ESM che deve essere eseguito dopo la registrazione degli hook.
import { register } from 'node:module'
register('http-to-https', import.meta.url)
// Poiché si tratta di un `import()` dinamico, gli hook `http-to-https` verranno eseguiti
// per gestire `./my-app.js` e qualsiasi altro file che importa o richiede.
await import('./my-app.js')
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
register('http-to-https', pathToFileURL(__filename))
// Poiché si tratta di un `import()` dinamico, gli hook `http-to-https` verranno eseguiti
// per gestire `./my-app.js` e qualsiasi altro file che importa o richiede.
import('./my-app.js')
Gli hook di personalizzazione verranno eseguiti per tutti i moduli caricati successivamente alla registrazione e per i moduli a cui fanno riferimento tramite import
e require
integrato. La funzione require
creata dagli utenti tramite module.createRequire()
può essere personalizzata solo dagli hook sincroni.
In questo esempio, stiamo registrando gli hook http-to-https
, ma saranno disponibili solo per i moduli importati successivamente — in questo caso, my-app.js
e qualsiasi cosa a cui fa riferimento tramite import
o require
integrato nelle dipendenze CommonJS.
Se import('./my-app.js')
fosse invece stato un import './my-app.js'
statico, l'app sarebbe già stata caricata prima che gli hook http-to-https
venissero registrati. Ciò è dovuto alla specifica dei moduli ES, in cui le importazioni statiche vengono valutate prima dalle foglie dell'albero, quindi di nuovo al tronco. Ci possono essere importazioni statiche all'interno di my-app.js
, che non verranno valutate fino a quando my-app.js
non viene importato dinamicamente.
Se vengono utilizzati hook sincroni, sono supportati sia import
, require
e require
utente creato utilizzando createRequire()
.
import { registerHooks, createRequire } from 'node:module'
registerHooks({
/* implementazione di hook sincroni */
})
const require = createRequire(import.meta.url)
// Gli hook sincroni influenzano la funzione import, require() e la funzione require() utente
// creata tramite createRequire().
await import('./my-app.js')
require('./my-app-2.js')
const { register, registerHooks } = require('node:module')
const { pathToFileURL } = require('node:url')
registerHooks({
/* implementazione di hook sincroni */
})
const userRequire = createRequire(__filename)
// Gli hook sincroni influenzano la funzione import, require() e la funzione require() utente
// creata tramite createRequire().
import('./my-app.js')
require('./my-app-2.js')
userRequire('./my-app-3.js')
Infine, se tutto ciò che si desidera fare è registrare gli hook prima dell'esecuzione dell'app e non si desidera creare un file separato a tale scopo, è possibile passare un 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
Concatenamento
È possibile chiamare register
più di una volta:
// 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')
In questo esempio, gli hook registrati formeranno delle catene. Queste catene vengono eseguite in modalità LIFO (Last-In, First-Out). Se sia foo.mjs
che bar.mjs
definiscono un hook resolve
, verranno chiamati in questo modo (si noti la direzione da destra a sinistra): predefinito di Node ← ./foo.mjs
← ./bar.mjs
(a partire da ./bar.mjs
, poi ./foo.mjs
, quindi il predefinito di Node.js). Lo stesso vale per tutti gli altri hook.
Gli hook registrati influenzano anche register
stesso. In questo esempio, bar.mjs
verrà risolto e caricato tramite gli hook registrati da foo.mjs
(perché gli hook di foo
saranno già stati aggiunti alla catena). Ciò consente di fare cose come scrivere hook in linguaggi non JavaScript, purché gli hook registrati in precedenza vengano transpilati in JavaScript.
Il metodo register
non può essere chiamato dall'interno del modulo che definisce gli hook.
Il concatenamento di registerHooks
funziona in modo simile. Se hook sincroni e asincroni sono combinati, gli hook sincroni vengono sempre eseguiti per primi prima che gli hook asincroni inizino l'esecuzione, ovvero, nell'ultimo hook sincrono in esecuzione, il suo hook successivo include l'invocazione degli hook asincroni.
// entrypoint.mjs
import { registerHooks } from 'node:module'
const hook1 = {
/* implementazione degli hook */
}
const hook2 = {
/* implementazione degli hook */
}
// hook2 eseguito prima di hook1.
registerHooks(hook1)
registerHooks(hook2)
// entrypoint.cjs
const { registerHooks } = require('node:module')
const hook1 = {
/* implementazione degli hook */
}
const hook2 = {
/* implementazione degli hook */
}
// hook2 eseguito prima di hook1.
registerHooks(hook1)
registerHooks(hook2)
Comunicazione con gli hook di personalizzazione dei moduli
Gli hook asincroni vengono eseguiti su un thread dedicato, separato dal thread principale che esegue il codice dell'applicazione. Ciò significa che la mutazione di variabili globali non influenzerà gli altri thread e che i canali di messaggi devono essere utilizzati per comunicare tra i thread.
Il metodo register
può essere utilizzato per passare dati a un hook initialize
. I dati passati all'hook possono includere oggetti trasferibili come le porte.
import { register } from 'node:module'
import { MessageChannel } from 'node:worker_threads'
// Questo esempio dimostra come un canale di messaggi può essere utilizzato per
// comunicare con gli hook, inviando `port2` agli hook.
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')
// Questo esempio mostra come un canale di messaggi può essere utilizzato per
// comunicare con gli hook, inviando `port2` agli hook.
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],
})
Gli hook di modulo sincroni vengono eseguiti nello stesso thread in cui viene eseguito il codice dell'applicazione. Possono mutare direttamente le variabili globali del contesto a cui accede il thread principale.
Hook
Hook asincroni accettati da module.register()
Il metodo register
può essere utilizzato per registrare un modulo che esporta un insieme di hook. Gli hook sono funzioni che vengono chiamate da Node.js per personalizzare il processo di risoluzione e caricamento dei moduli. Le funzioni esportate devono avere nomi e firme specifici e devono essere esportate come esportazioni nominate.
export async function initialize({ number, port }) {
// Riceve dati da `register`.
}
export async function resolve(specifier, context, nextResolve) {
// Prendi uno specifier `import` o `require` e risolvilo in un URL.
}
export async function load(url, context, nextLoad) {
// Prendi un URL risolto e restituisci il codice sorgente da valutare.
}
Gli hook asincroni vengono eseguiti in un thread separato, isolato dal thread principale in cui viene eseguito il codice dell'applicazione. Ciò significa che è un realm diverso. Il thread degli hook può essere terminato dal thread principale in qualsiasi momento, quindi non fare affidamento sul completamento di operazioni asincrone (come console.log
). Vengono ereditati nei worker figlio per impostazione predefinita.
Hook sincroni accettati da module.registerHooks()
Aggiunto in: v23.5.0
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.1 - Sviluppo attivo
Il metodo module.registerHooks()
accetta funzioni hook sincrone. initialize()
non è supportato né necessario, poiché l'implementatore dell'hook può semplicemente eseguire il codice di inizializzazione direttamente prima della chiamata a module.registerHooks()
.
function resolve(specifier, context, nextResolve) {
// Prende uno specifier `import` o `require` e lo risolve in un URL.
}
function load(url, context, nextLoad) {
// Prende un URL risolto e restituisce il codice sorgente da valutare.
}
Gli hook sincroni vengono eseguiti nello stesso thread e nello stesso realm in cui vengono caricati i moduli. A differenza degli hook asincroni, non vengono ereditati nei thread di lavoro figlio per impostazione predefinita, sebbene se gli hook vengono registrati utilizzando un file precaricato da --import
o --require
, i thread di lavoro figlio possono ereditare gli script precaricati tramite l'ereditarietà process.execArgv
. Consulta la documentazione di Worker
per i dettagli.
Negli hook sincroni, gli utenti possono aspettarsi che console.log()
venga completato nello stesso modo in cui si aspettano che console.log()
venga completato nel codice del modulo.
Convenzioni degli hook
Gli hook fanno parte di una catena, anche se quella catena è composta solo da un hook personalizzato (fornito dall'utente) e dall'hook predefinito, che è sempre presente. Le funzioni hook sono annidate: ognuna deve sempre restituire un oggetto semplice e l'incatenamento avviene a seguito della chiamata da parte di ciascuna funzione di next\<hookName\>()
, che è un riferimento all'hook del caricatore successivo (in ordine LIFO).
Un hook che restituisce un valore privo di una proprietà richiesta genera un'eccezione. Un hook che restituisce senza chiamare next\<hookName\>()
e senza restituire shortCircuit: true
genera anche un'eccezione. Questi errori hanno lo scopo di prevenire interruzioni involontarie nella catena. Restituisci shortCircuit: true
da un hook per segnalare che la catena termina intenzionalmente nel tuo hook.
initialize()
Aggiunto in: v20.6.0, v18.19.0
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.2 - Candidato al rilascio
data
<any> I dati daregister(loader, import.meta.url, { data })
.
L'hook initialize
è accettato solo da register
. registerHooks()
non lo supporta né ne ha bisogno poiché l'inizializzazione fatta per gli hook sincroni può essere eseguita direttamente prima della chiamata a registerHooks()
.
L'hook initialize
fornisce un modo per definire una funzione personalizzata che viene eseguita nel thread degli hook quando il modulo degli hook viene inizializzato. L'inizializzazione avviene quando il modulo degli hook viene registrato tramite register
.
Questo hook può ricevere dati da un'invocazione di register
, incluse le porte e altri oggetti trasferibili. Il valore restituito di initialize
può essere una <Promise>, nel qual caso verrà atteso prima che l'esecuzione del thread dell'applicazione principale riprenda.
Codice di personalizzazione del modulo:
// path-to-my-hooks.js
export async function initialize({ number, port }) {
port.postMessage(`increment: ${number + 1}`)
}
Codice del chiamante:
import assert from 'node:assert'
import { register } from 'node:module'
import { MessageChannel } from 'node:worker_threads'
// Questo esempio mostra come un canale di messaggi può essere utilizzato per comunicare
// tra il thread principale (dell'applicazione) e gli hook in esecuzione sul thread degli hook,
// inviando `port2` all'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')
// Questo esempio mostra come un canale di messaggi può essere utilizzato per comunicare
// tra il thread principale (dell'applicazione) e gli hook in esecuzione sul thread degli hook,
// inviando `port2` all'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)
[Cronologia]
Versione | Modifiche |
---|---|
v23.5.0 | Aggiunto il supporto per gli hook sincroni e in-thread. |
v21.0.0, v20.10.0, v18.19.0 | La proprietà context.importAssertions è stata sostituita con context.importAttributes . L'utilizzo del vecchio nome è ancora supportato e genererà un avviso sperimentale. |
v18.6.0, v16.17.0 | Aggiunto il supporto per il concatenamento degli hook di risoluzione. Ogni hook deve chiamare nextResolve() o includere una proprietà shortCircuit impostata su true nel suo valore restituito. |
v17.1.0, v16.14.0 | Aggiunto il supporto per le asserzioni di importazione. |
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.2 - Release candidate (versione asincrona) Stabilità: 1.1 - Sviluppo attivo (versione sincrona)
specifier
<string>context
<Object>conditions
<string[]> Condizioni di esportazione delpackage.json
rilevanteimportAttributes
<Object> Un oggetto le cui coppie chiave-valore rappresentano gli attributi per il modulo da importareparentURL
<string> | <undefined> Il modulo che importa questo, o undefined se questo è il punto di ingresso di Node.js
nextResolve
<Function> L'hookresolve
successivo nella catena, o l'hookresolve
predefinito di Node.js dopo l'ultimo hookresolve
fornito dall'utenteRestituisce: <Object> | <Promise> La versione asincrona accetta un oggetto contenente le seguenti proprietà o una
Promise
che si risolverà in tale oggetto. La versione sincrona accetta solo un oggetto restituito in modo sincrono.format
<string> | <null> | <undefined> Un suggerimento per l'hook di caricamento (potrebbe essere ignorato)'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'
importAttributes
<Object> | <undefined> Gli attributi di importazione da utilizzare durante la memorizzazione nella cache del modulo (opzionale; se escluso verrà utilizzato l'input)shortCircuit
<undefined> | <boolean> Un segnale che questo hook intende terminare la catena di hookresolve
. Predefinito:false
url
<string> L'URL assoluto a cui questo input si risolve
La catena di hook resolve
è responsabile di comunicare a Node.js dove trovare e come memorizzare nella cache una determinata istruzione o espressione import
o chiamata require
. Può facoltativamente restituire un formato (come 'module'
) come suggerimento per l'hook load
. Se viene specificato un formato, l'hook load
è in ultima analisi responsabile della fornitura del valore format
finale (ed è libero di ignorare il suggerimento fornito da resolve
); se resolve
fornisce un format
, è necessario un hook load
personalizzato anche se solo per passare il valore all'hook load
predefinito di Node.js.
Gli attributi di tipo di importazione fanno parte della chiave della cache per il salvataggio dei moduli caricati nella cache interna dei moduli. L'hook resolve
è responsabile della restituzione di un oggetto importAttributes
se il modulo deve essere memorizzato nella cache con attributi diversi da quelli presenti nel codice sorgente.
La proprietà conditions
in context
è un array di condizioni che verranno utilizzate per corrispondere alle condizioni di esportazione del pacchetto per questa richiesta di risoluzione. Possono essere utilizzati per cercare mappature condizionali altrove o per modificare l'elenco quando si richiama la logica di risoluzione predefinita.
Le attuali condizioni di esportazione del pacchetto sono sempre nell'array context.conditions
passato all'hook. Per garantire il comportamento predefinito di risoluzione dello specificatore del modulo Node.js quando si chiama defaultResolve
, l'array context.conditions
passato a esso deve includere tutti gli elementi dell'array context.conditions
originariamente passato all'hook resolve
.
// Versione asincrona accettata da module.register().
export async function resolve(specifier, context, nextResolve) {
const { parentURL = null } = context
if (Math.random() > 0.5) {
// Alcune condizioni.
// Per alcuni o tutti gli specificatori, eseguire una logica personalizzata per la risoluzione.
// Restituire sempre un oggetto della forma {url: <string>}.
return {
shortCircuit: true,
url: parentURL ? new URL(specifier, parentURL).href : new URL(specifier).href,
}
}
if (Math.random() < 0.5) {
// Un'altra condizione.
// Quando si chiama `defaultResolve`, gli argomenti possono essere modificati. In questo
// caso si aggiunge un altro valore per la corrispondenza delle esportazioni condizionali.
return nextResolve(specifier, {
...context,
conditions: [...context.conditions, 'another-condition'],
})
}
// Rimanda all'hook successivo nella catena, che sarebbe la
// risoluzione predefinita di Node.js se questo è l'ultimo loader specificato dall'utente.
return nextResolve(specifier)
}
// Versione sincrona accettata da module.registerHooks().
function resolve(specifier, context, nextResolve) {
// Simile alla resolve() asincrona sopra, poiché questa non ha
// alcuna logica asincrona.
}
load(url, context, nextLoad)
[Cronologia]
Versione | Modifiche |
---|---|
v23.5.0 | Aggiunto il supporto per la versione sincrona e in-thread. |
v20.6.0 | Aggiunto il supporto per source con formato commonjs . |
v18.6.0, v16.17.0 | Aggiunto il supporto per l'incatenamento degli hook di caricamento. Ogni hook deve chiamare nextLoad() o includere una proprietà shortCircuit impostata su true nel suo ritorno. |
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.2 - Release candidate (versione asincrona) Stabilità: 1.1 - Sviluppo attivo (versione sincrona)
url
<string> L'URL restituito dalla catenaresolve
context
<Object>conditions
<string[]> Condizioni di esportazione delpackage.json
pertinenteformat
<string> | <null> | <undefined> Il formato facoltativamente fornito dalla catena di hookresolve
importAttributes
<Object>
nextLoad
<Function> L'hookload
successivo nella catena, o l'hookload
predefinito di Node.js dopo l'ultimo hookload
fornito dall'utenteRestituisce: <Object> | <Promise> La versione asincrona accetta un oggetto contenente le seguenti proprietà o una
Promise
che si risolverà in tale oggetto. La versione sincrona accetta solo un oggetto restituito in modo sincrono.format
<string>shortCircuit
<undefined> | <boolean> Un segnale che indica che questo hook intende terminare la catena di hookload
. Predefinito:false
source
<string> | <ArrayBuffer> | <TypedArray> L'origine da far valutare a Node.js
L'hook load
fornisce un modo per definire un metodo personalizzato per determinare come un URL deve essere interpretato, recuperato e analizzato. È anche responsabile della convalida degli attributi di importazione.
Il valore finale di format
deve essere uno dei seguenti:
format | Descrizione | Tipi accettabili per source restituiti da load |
---|---|---|
'builtin' | Carica un modulo builtin di Node.js | Non applicabile |
'commonjs' | Carica un modulo CommonJS di Node.js | { string , ArrayBuffer , TypedArray , null , undefined } |
'json' | Carica un file JSON | { string , ArrayBuffer , TypedArray } |
'module' | Carica un modulo ES | { string , ArrayBuffer , TypedArray } |
'wasm' | Carica un modulo WebAssembly | { ArrayBuffer , TypedArray } |
Il valore di source
viene ignorato per il tipo 'builtin'
perché attualmente non è possibile sostituire il valore di un modulo (core) builtin di Node.js.
Avvertenza nell'hook load
asincrono
Quando si utilizza l'hook load
asincrono, omettere o fornire una source
per 'commonjs'
ha effetti molto diversi:
- Quando viene fornita una
source
, tutte le chiamaterequire
da questo modulo verranno elaborate dal caricatore ESM con gli hookresolve
eload
registrati; tutte le chiamaterequire.resolve
da questo modulo verranno elaborate dal caricatore ESM con gli hookresolve
registrati; sarà disponibile solo un sottoinsieme dell'API CommonJS (ad esempio, nessunrequire.extensions
, nessunrequire.cache
, nessunrequire.resolve.paths
) e il monkey-patching sul caricatore di moduli CommonJS non verrà applicato. - Se
source
èundefined
onull
, verrà gestita dal caricatore di moduli CommonJS e le chiamaterequire
/require.resolve
non passeranno attraverso gli hook registrati. Questo comportamento persource
nullo è temporaneo: in futuro,source
nullo non sarà supportato.
Queste avvertenze non si applicano all'hook load
sincrono, nel qual caso l'insieme completo di API CommonJS disponibili per i moduli CommonJS personalizzati e require
/require.resolve
passano sempre attraverso gli hook registrati.
L'implementazione asincrona interna di Node.js per load
, che è il valore di next
per l'ultimo hook nella catena load
, restituisce null
per source
quando format
è 'commonjs'
per compatibilità con le versioni precedenti. Ecco un esempio di hook che acconsentirebbe all'utilizzo del comportamento non predefinito:
import { readFile } from 'node:fs/promises'
// Versione asincrona accettata da module.register(). Questa correzione non è necessaria
// per la versione sincrona accettata da 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
}
Questo non si applica nemmeno all'hook load
sincrono, nel qual caso la source
restituita contiene il codice sorgente caricato dal successivo hook, indipendentemente dal formato del modulo.
- L'oggetto specifico
ArrayBuffer
è unSharedArrayBuffer
. - L'oggetto specifico
TypedArray
è unUint8Array
.
Se il valore sorgente di un formato basato su testo (cioè, 'json'
, 'module'
) non è una stringa, viene convertito in una stringa utilizzando util.TextDecoder
.
L'hook load
fornisce un modo per definire un metodo personalizzato per recuperare il codice sorgente di un URL risolto. Ciò consentirebbe a un caricatore di evitare potenzialmente la lettura di file dal disco. Potrebbe anche essere utilizzato per mappare un formato non riconosciuto a uno supportato, ad esempio yaml
a module
.
// Versione asincrona accettata da module.register().
export async function load(url, context, nextLoad) {
const { format } = context
if (Math.random() > 0.5) {
// Qualche condizione
/*
Per alcuni o tutti gli URL, esegui una logica personalizzata per recuperare la sorgente.
Restituisce sempre un oggetto della forma {
format: <string>,
source: <string|buffer>,
}.
*/
return {
format,
shortCircuit: true,
source: '...',
}
}
// Rinvia all'hook successivo nella catena.
return nextLoad(url)
}
// Versione sincrona accettata da module.registerHooks().
function load(url, context, nextLoad) {
// Simile al load() asincrono sopra, dato che quest'ultimo non ha
// alcuna logica asincrona.
}
In uno scenario più avanzato, questo può essere utilizzato anche per trasformare una sorgente non supportata in una supportata (vedere Esempi di seguito).
Esempi
I vari hook di personalizzazione dei moduli possono essere usati insieme per ottenere personalizzazioni di vasta portata dei comportamenti di caricamento e valutazione del codice Node.js.
Importazione da HTTPS
L'hook seguente registra degli hook per abilitare un supporto rudimentale per tali specificatori. Anche se questo può sembrare un miglioramento significativo della funzionalità principale di Node.js, ci sono notevoli svantaggi nell'uso effettivo di questi hook: le prestazioni sono molto più lente rispetto al caricamento di file dal disco, non c'è memorizzazione nella cache e non c'è sicurezza.
// https-hooks.mjs
import { get } from 'node:https'
export function load(url, context, nextLoad) {
// Per caricare JavaScript tramite la rete, dobbiamo recuperarlo e
// restituirlo.
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({
// Questo esempio presuppone che tutto il JavaScript fornito dalla rete sia
// codice del modulo ES.
format: 'module',
shortCircuit: true,
source: data,
})
)
}).on('error', err => reject(err))
})
}
// Lascia che Node.js gestisca tutti gli altri URL.
return nextLoad(url)
}
// main.mjs
import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js'
console.log(VERSION)
Con il modulo di hook precedente, l'esecuzione di node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./https-hooks.mjs"));' ./main.mjs
stampa la versione corrente di CoffeeScript in base al modulo all'URL in main.mjs
.
Transpilazione
Le sorgenti in formati che Node.js non comprende possono essere convertite in JavaScript usando l'hook load
.
Questo è meno performante rispetto alla transpilazione dei file sorgente prima dell'esecuzione di Node.js; gli hook di transpilazione devono essere usati solo per scopi di sviluppo e test.
Versione asincrona
// 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)) {
// I file CoffeeScript possono essere sia CommonJS che moduli ES, quindi vogliamo che
// qualsiasi file CoffeeScript venga trattato da Node.js come un file .js nella
// stessa posizione. Per determinare come Node.js interpreterebbe un file .js
// arbitrario, cerca nel file system il file package.json genitore più vicino
// e leggi il suo campo "type".
const format = await getPackageType(url)
const { source: rawSource } = await nextLoad(url, { ...context, format })
// Questo hook converte il codice sorgente CoffeeScript in codice sorgente JavaScript
// per tutti i file CoffeeScript importati.
const transformedSource = coffeescript.compile(rawSource.toString(), url)
return {
format,
shortCircuit: true,
source: transformedSource,
}
}
// Lascia che Node.js gestisca tutti gli altri URL.
return nextLoad(url)
}
async function getPackageType(url) {
// `url` è solo un percorso di file durante la prima iterazione quando viene passato
// l'url risolto dall'hook load()
// un percorso di file effettivo da load() conterrà un'estensione di file poiché è
// richiesto dalle specifiche
// questo semplice controllo di verità per verificare se `url` contiene un'estensione di file
// funzionerà per la maggior parte dei progetti ma non copre alcuni casi limite (come
// file senza estensione o un url che termina con uno spazio finale)
const isFilePath = !!extname(url)
// Se è un percorso di file, ottieni la directory in cui si trova
const dir = isFilePath ? dirname(fileURLToPath(url)) : url
// Componi un percorso di file per un package.json nella stessa directory,
// che potrebbe esistere o meno
const packagePath = resolvePath(dir, 'package.json')
// Prova a leggere il package.json possibilmente inesistente
const type = await readFile(packagePath, { encoding: 'utf8' })
.then(filestring => JSON.parse(filestring).type)
.catch(err => {
if (err?.code !== 'ENOENT') console.error(err)
})
// Se package.json esisteva e conteneva un campo `type` con un valore, voilà
if (type) return type
// Altrimenti, (se non alla radice) continua a controllare la directory successiva
// Se alla radice, fermati e restituisci false
return dir.length > 1 && getPackageType(resolvePath(dir, '..'))
}
Versione sincrona
// 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 })
Esecuzione degli hook
# main.coffee {#maincoffee}
import { scream } from './scream.coffee'
console.log scream 'hello, world'
import { version } from 'node:process'
console.log "Presentato da Node.js versione #{version}"
# scream.coffee {#screamcoffee}
export scream = (str) -> str.toUpperCase()
Con i moduli hook precedenti, l'esecuzione di 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
fa sì che main.coffee
venga trasformato in JavaScript dopo che il suo codice sorgente viene caricato dal disco ma prima che Node.js lo esegua; e così via per tutti i file .coffee
, .litcoffee
o .coffee.md
a cui si fa riferimento tramite istruzioni import
di qualsiasi file caricato.
Mappe di importazione
I due esempi precedenti hanno definito gli hook load
. Questo è un esempio di hook resolve
. Questo modulo hook legge un file import-map.json
che definisce quali specificatori sovrascrivere ad altri URL (questa è un'implementazione molto semplicistica di un piccolo sottoinsieme della specifica "mappe di importazione").
Versione asincrona
// 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)
}
Versione sincrona
// 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 })
Utilizzo degli hook
Con questi file:
// main.js
import 'a-module'
// import-map.json
{
"imports": {
"a-module": "./some-module.js"
}
}
// some-module.js
console.log('some module!')
L'esecuzione di 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
dovrebbe stampare some module!
.
Supporto per source map v3
Aggiunto in: v13.7.0, v12.17.0
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1 - Sperimentale
Helper per interagire con la cache della source map. Questa cache viene popolata quando l'analisi della source map è abilitata e le direttive di inclusione della source map vengono trovate nel footer di un modulo.
Per abilitare l'analisi della source map, Node.js deve essere eseguito con il flag --enable-source-maps
, o con la code coverage abilitata impostando NODE_V8_COVERAGE=dir
.
// module.mjs
// In un modulo ECMAScript
import { findSourceMap, SourceMap } from 'node:module'
// module.cjs
// In un modulo CommonJS
const { findSourceMap, SourceMap } = require('node:module')
module.findSourceMap(path)
Aggiunto in: v13.7.0, v12.17.0
path
<stringa>- Restituisce: <module.SourceMap> | <undefined> Restituisce
module.SourceMap
se viene trovata una mappa di origine,undefined
altrimenti.
path
è il percorso risolto per il file per il quale deve essere recuperata una mappa di origine corrispondente.
Classe: module.SourceMap
Aggiunto in: v13.7.0, v12.17.0
new SourceMap(payload[, { lineLengths }])
{#new-sourcemappayload-{-linelengths-}}
payload
<Oggetto>lineLengths
<numero[]>
Crea una nuova istanza sourceMap
.
payload
è un oggetto con chiavi corrispondenti al formato Source map v3:
file
: <stringa>version
: <numero>sources
: <stringa[]>sourcesContent
: <stringa[]>names
: <stringa[]>mappings
: <stringa>sourceRoot
: <stringa>
lineLengths
è un array facoltativo della lunghezza di ogni riga nel codice generato.
sourceMap.payload
- Restituisce: <Oggetto>
Getter per il payload utilizzato per costruire l'istanza SourceMap
.
sourceMap.findEntry(lineOffset, columnOffset)
lineOffset
<number> L'offset del numero di riga a base zero nella sorgente generatacolumnOffset
<number> L'offset del numero di colonna a base zero nella sorgente generata- Restituisce: <Object>
Dato un offset di riga e un offset di colonna nel file sorgente generato, restituisce un oggetto che rappresenta l'intervallo SourceMap nel file originale se trovato, oppure un oggetto vuoto in caso contrario.
L'oggetto restituito contiene le seguenti chiavi:
- generatedLine: <number> L'offset di riga dell'inizio dell'intervallo nella sorgente generata
- generatedColumn: <number> L'offset di colonna dell'inizio dell'intervallo nella sorgente generata
- originalSource: <string> Il nome del file della sorgente originale, come riportato nel SourceMap
- originalLine: <number> L'offset di riga dell'inizio dell'intervallo nella sorgente originale
- originalColumn: <number> L'offset di colonna dell'inizio dell'intervallo nella sorgente originale
- name: <string>
Il valore restituito rappresenta l'intervallo grezzo così come appare nel SourceMap, basato su offset a base zero, non sui numeri di riga e colonna a base 1 come appaiono nei messaggi di errore e negli oggetti CallSite.
Per ottenere i numeri di riga e colonna corrispondenti a base 1 da un lineNumber e un columnNumber come riportati dagli stack di errore e dagli oggetti CallSite, utilizzare sourceMap.findOrigin(lineNumber, columnNumber)
sourceMap.findOrigin(lineNumber, columnNumber)
lineNumber
<number> Il numero di riga in base 1 del punto di chiamata nel sorgente generatocolumnNumber
<number> Il numero di colonna in base 1 del punto di chiamata nel sorgente generato- Restituisce: <Object>
Dato un lineNumber
e un columnNumber
in base 1 da un punto di chiamata nel sorgente generato, trova la posizione del punto di chiamata corrispondente nel sorgente originale.
Se lineNumber
e columnNumber
forniti non vengono trovati in nessuna source map, viene restituito un oggetto vuoto. Altrimenti, l'oggetto restituito contiene le seguenti chiavi:
- name: <string> | <undefined> Il nome dell'intervallo nella source map, se ne è stato fornito uno
- fileName: <string> Il nome del file del sorgente originale, come riportato nella SourceMap
- lineNumber: <number> Il numero di riga in base 1 del punto di chiamata corrispondente nel sorgente originale
- columnNumber: <number> Il numero di colonna in base 1 del punto di chiamata corrispondente nel sorgente originale