Moduli: API node:module
Aggiunto in: v0.3.7
L'oggetto Module
Fornisce metodi di utilità generali quando si interagisce con le 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 | L'elenco ora contiene anche moduli solo 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, 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 file da utilizzare per costruire la funzione require. Deve essere un oggetto URL file, una stringa URL 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 bare, 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 viene trovato il
package.json
. 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 // name = '@foo/bar'
└ qux/
├ node_modules/
└ some-package/
└ package.json // name = 'some-package'
├ qux.js
└ package.json // name = '@foo/qux'
├ main.js
└ package.json // name = '@foo'
// /path/to/project/packages/bar/bar.js
import { findPackageJSON } from 'node:module';
findPackageJSON('..', import.meta.url);
// '/path/to/project/package.json'
// Stesso risultato quando si passa invece uno specificatore assoluto:
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 invece uno specificatore assoluto:
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
<stringa> nome del modulo- Restituisce: <booleano> restituisce true se il modulo è incorporato, 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 il supporto per le 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 - Candidato alla versione
specifier
<stringa> | <URL> Hook di personalizzazione da registrare; questa dovrebbe essere la stessa stringa che verrebbe passata aimport()
, tranne che se è relativa, viene risolta rispetto aparentURL
.parentURL
<stringa> | <URL> Se vuoi risolverespecifier
rispetto a un URL di base, comeimport.meta.url
, puoi passare quell'URL qui. Predefinito:'data:'
options
<Oggetto>parentURL
<stringa> | <URL> Se vuoi risolverespecifier
rispetto a un URL di base, comeimport.meta.url
, puoi passare quell'URL qui. Questa proprietà viene ignorata separentURL
viene fornito come secondo argomento. Predefinito:'data:'
data
<qualsiasi> 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 dei moduli Node.js e il comportamento di caricamento. Vedi Hook di personalizzazione.
module.registerHooks(options)
Aggiunto in: v23.5.0
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.1 - Sviluppo attivo
options
<Object>load
<Function> | <undefined> Vedi load hook. Predefinito:undefined
.resolve
<Function> | <undefined> Vedi resolve hook. Predefinito:undefined
.
Registra gli hook che personalizzano la risoluzione dei moduli Node.js e il comportamento di caricamento. Vedi Hook di personalizzazione.
module.stripTypeScriptTypes(code[, options])
Aggiunto in: v23.2.0
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.1 - Sviluppo attivo
code
<string> Il codice da cui rimuovere le annotazioni di tipo.options
<Object>mode
<string> 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
<boolean> Predefinito:false
. Solo quandomode
è'transform'
, setrue
, verrà generata una source map per il codice trasformato.sourceUrl
<string> Specifica l'URL di origine utilizzato nella source map.
Restituisce: <string> 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à TypeScript che richiedono la trasformazione comeEnums
, vedere type-stripping per ulteriori informazioni. Quando la modalità è'transform'
, trasforma anche le funzionalità TypeScript in JavaScript, vedere transform TypeScript features per ulteriori informazioni. Quando la modalità è'strip'
, le source map non vengono generate, perché le posizioni vengono preservate. SesourceMap
viene fornito, 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 TypeScript.
import { stripTypeScriptTypes } from 'node:module';
const code = 'const a: number = 1;';
const strippedCode = stripTypeScriptTypes(code);
console.log(strippedCode);
// Prints: const a = 1;
const { stripTypeScriptTypes } = require('node:module');
const code = 'const a: number = 1;';
const strippedCode = stripTypeScriptTypes(code);
console.log(strippedCode);
// Prints: const a = 1;
Se sourceUrl
viene fornito, verrà 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);
// Prints: 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);
// Prints: 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);
// Prints:
// 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);
// Prints:
// 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 live per gli ES Modules incorporati in modo che corrispondano alle proprietà degli exports 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 dall'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 | aggiunte 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 ECMAScript Module, utilizzerà la cache del codice V8 su disco persistente nella directory specificata per velocizzare la compilazione. Ciò potrebbe rallentare il primo caricamento di un grafico dei moduli, ma i caricamenti successivi dello stesso grafico dei moduli potrebbero ottenere un notevole aumento di velocità se il contenuto dei moduli non cambia.
Per pulire 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 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, altrimenti verrà impostata come predefinita 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 copertura del codice JavaScript V8, la copertura raccolta da V8 potrebbe essere meno precisa nelle funzioni deserializzate dalla cache del codice. Si consiglia di disattivare questa opzione quando si eseguono test per generare una copertura precisa.
La cache di compilazione dei moduli abilitata può essere disabilitata dalla variabile d'ambiente NODE_DISABLE_COMPILE_CACHE=1
. Ciò può essere utile quando la cache di compilazione porta a comportamenti imprevisti o indesiderati (ad esempio, una copertura dei 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 versioni diverse di Node.js verrà archiviata separatamente se viene utilizzata la stessa directory di base per conservare la cache, in modo che possano coesistere.
Al momento, quando la cache di compilazione è abilitata e un modulo viene caricato di nuovo, la cache del codice viene generata immediatamente dal codice compilato, ma verrà scritta su disco solo quando l'istanza di Node.js sta per uscire. Questo è soggetto a modifiche. Il metodo module.flushCompileCache()
può essere utilizzato per garantire che la cache del codice accumulata venga scaricata su disco nel caso in cui l'applicazione desideri 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 correttamente la cache di compilazione. La directory utilizzata per memorizzare la cache di compilazione verrà restituita nel campo directory nell'oggetto restituito. |
ALREADY_ENABLED | La cache di compilazione è già stata abilitata in precedenza, o da una precedente chiamata a module.enableCompileCache() , o dalla variabile d'ambiente NODE_COMPILE_CACHE=dir . La directory utilizzata per memorizzare la cache di compilazione verrà restituita nel campo directory nell'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. I dettagli dell'errore verranno restituiti nel campo message nell'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
<numero intero> Uno deimodule.constants.compileCacheStatus
message
<stringa> | <undefined> Se Node.js non può abilitare la cache di compilazione, questo contiene il messaggio di errore. Impostato solo sestatus
èmodule.constants.compileCacheStatus.FAILED
.directory
<stringa> | <undefined> Se la cache di compilazione è abilitata, questo contiene la directory in cui è archiviata la cache di compilazione. 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 che non è richiesta per la funzionalità 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 viene abilitata correttamente, 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 abilitarlo nei thread di lavoro figlio, o chiama questo metodo anche nei thread di lavoro figlio, oppure imposta il valore process.env.NODE_COMPILE_CACHE
nella directory della cache di compilazione in modo che il comportamento possa essere ereditato dai 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 dei moduli accumulata dai moduli già caricati nell'istanza corrente di Node.js su disco. Restituisce al termine di tutte le operazioni del file system di svuotamento, indipendentemente dal fatto che abbiano successo o meno. In caso di errori, l'operazione fallirà silenziosamente, poiché le mancate corrispondenze 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: <stringa> | <undefined> Percorso della directory della cache di compilazione dei moduli 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 l'incatenamento 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 al rilascio (versione asincrona) Stabilità: 1.1 - Sviluppo attivo (versione sincrona)
Esistono due tipi di hook di personalizzazione dei moduli attualmente supportati:
Abilitazione
La risoluzione e il caricamento dei moduli possono essere personalizzati tramite:
Gli hook possono essere registrati prima che venga eseguito il codice dell'applicazione utilizzando il 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.
// Usa 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');
// Usa module.register() per registrare hook asincroni in un thread dedicato.
register('./hooks.mjs', pathToFileURL(__filename));
// Usa module.registerHooks() per registrare hook sincroni nel thread principale.
import { registerHooks } from 'node:module';
registerHooks({
resolve(specifier, context, nextResolve) { /* implementation */ },
load(url, context, nextLoad) { /* implementation */ },
});
// Usa module.registerHooks() per registrare hook sincroni nel thread principale.
const { registerHooks } = require('node:module');
registerHooks({
resolve(specifier, context, nextResolve) { /* implementation */ },
load(url, context, nextLoad) { /* implementation */ },
});
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
da mappare a un file che chiama register()
, come il seguente esempio register-hooks.js
.
L'utilizzo di --import
o --require
garantisce che gli hook vengano registrati prima che vengano importati i file dell'applicazione, inclusi il punto di ingresso dell'applicazione e per eventuali thread di worker anche 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 che gli hook sono stati registrati.
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 i moduli a cui fanno riferimento tramite import
e il require
integrato. La funzione require
creata dagli utenti utilizzando 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 faccia riferimento tramite import
o require
integrato nelle dipendenze CommonJS.
Se import('./my-app.js')
fosse stato invece un import './my-app.js'
statico, l'app sarebbe stata già caricata prima che gli hook http-to-https
fossero registrati. Questo è dovuto alla specifica dei moduli ES, dove 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({ /* implementation of synchronous hooks */ });
const require = createRequire(import.meta.url);
// Gli hook sincroni influiscono su import, require() e sulla funzione require() utente
// creato tramite createRequire().
await import('./my-app.js');
require('./my-app-2.js');
const { register, registerHooks } = require('node:module');
const { pathToFileURL } = require('node:url');
registerHooks({ /* implementation of synchronous hooks */ });
const userRequire = createRequire(__filename);
// Gli hook sincroni influiscono su import, require() e sulla funzione require() utente
// creato 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 che l'app venga eseguita 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
Concatenazione
È 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 (nota da destra a sinistra): predefinito di Node ← ./foo.mjs
← ./bar.mjs
(a partire da ./bar.mjs
, poi ./foo.mjs
, poi il predefinito di Node.js). Lo stesso vale per tutti gli altri hook.
Gli hook registrati influiscono anche su 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 scrivere hook in linguaggi diversi da JavaScript, a condizione che gli hook registrati in precedenza vengano transpilati in JavaScript.
Il metodo register
non può essere chiamato dall'interno del modulo che definisce gli hook.
La concatenazione di registerHooks
funziona in modo simile. Se gli hook sincroni e asincroni sono mescolati, gli hook sincroni vengono sempre eseguiti per primi prima che inizino a essere eseguiti gli hook asincroni, 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 = { /* implementation of hooks */ };
const hook2 = { /* implementation of hooks */ };
// hook2 run before hook1.
registerHooks(hook1);
registerHooks(hook2);
// entrypoint.cjs
const { registerHooks } = require('node:module');
const hook1 = { /* implementation of hooks */ };
const hook2 = { /* implementation of hooks */ };
// hook2 run before 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 modifica delle variabili globali non influirà sugli altri thread e 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 dei moduli sincroni vengono eseguiti sullo stesso thread in cui viene eseguito il codice dell'applicazione. Possono modificare 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 denominate.
export async function initialize({ number, port }) {
// Riceve dati da `register`.
}
export async function resolve(specifier, context, nextResolve) {
// Prende uno specificatore `import` o `require` e lo risolve in un URL.
}
export async function load(url, context, nextLoad) {
// Prende un URL risolto e restituisce 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 delle operazioni asincrone (come console.log
). Per impostazione predefinita, vengono ereditati nei worker figlio.
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é chi implementa l'hook può semplicemente eseguire il codice di inizializzazione direttamente prima della chiamata a module.registerHooks()
.
function resolve(specifier, context, nextResolve) {
// Prendi uno specifier `import` o `require` e risolvilo in un URL.
}
function load(url, context, nextLoad) {
// Prendi un URL risolto e restituisci 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 worker figlio per impostazione predefinita, anche se gli hook vengono registrati utilizzando un file precaricato da --import
o --require
, i thread worker figlio possono ereditare gli script precaricati tramite l'ereditarietà di process.execArgv
. Vedere 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()
nel codice del modulo venga completato.
Convenzioni degli hook
Gli hook fanno parte di una catena, anche se quella catena è costituita da un solo hook personalizzato (fornito dall'utente) e dall'hook predefinito, che è sempre presente. Le funzioni hook sono nidificate: ognuna deve sempre restituire un oggetto semplice e l'incatenamento avviene come risultato di ogni funzione che chiama next\<hookName\>()
, che è un riferimento all'hook del loader 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 servono a prevenire interruzioni involontarie nella catena. Restituire shortCircuit: true
da un hook per segnalare che la catena termina intenzionalmente nel tuo hook.
initialize()
Aggiunto in: v20.6.0, v18.19.0
[Stable: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.2 - Release candidate
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 eseguita 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 una chiamata a register
, incluse porte e altri oggetti trasferibili. Il valore di ritorno di initialize
può essere un <Promise>, nel qual caso sarà 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 di hook resolve. Ogni hook deve chiamare nextResolve() o includere una proprietà shortCircuit impostata su true nel suo ritorno. |
v17.1.0, v16.14.0 | Aggiunto il supporto per le asserzioni di importazione. |
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1.2 - Candidato al rilascio (versione asincrona) Stabilità: 1.1 - Sviluppo attivo (versione sincrona)
specifier
<stringa>context
<Oggetto>conditions
<stringa[]> Condizioni di esportazione delpackage.json
rilevanteimportAttributes
<Oggetto> Un oggetto le cui coppie chiave-valore rappresentano gli attributi per il modulo da importareparentURL
<stringa> | <undefined> Il modulo che importa questo, o undefined se questo è il punto di ingresso di Node.js
nextResolve
<Funzione> L'hookresolve
successivo nella catena, o l'hookresolve
predefinito di Node.js dopo l'ultimo hookresolve
fornito dall'utenteRestituisce: <Oggetto> | <Promise> La versione asincrona accetta sia un oggetto contenente le seguenti proprietà, sia una
Promise
che si risolverà in tale oggetto. La versione sincrona accetta solo un oggetto restituito in modo sincrono.format
<stringa> | <null> | <undefined> Un suggerimento per l'hook di caricamento (potrebbe essere ignorato)'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'
importAttributes
<Oggetto> | <undefined> Gli attributi di importazione da utilizzare durante la memorizzazione nella cache del modulo (opzionale; se escluso, verrà utilizzato l'input)shortCircuit
<undefined> | <booleano> Un segnale che indica che questo hook intende terminare la catena di hookresolve
. Predefinito:false
url
<stringa> 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 una chiamata require
. Può opzionalmente restituire un formato (come 'module'
) come suggerimento per l'hook load
. Se viene specificato un formato, l'hook load
è in definitiva responsabile della fornitura del valore format
finale (ed è libero di ignorare il suggerimento fornito da resolve
); se resolve
fornisce un format
, è richiesto un hook load
personalizzato anche solo per passare il valore all'hook load
predefinito di Node.js.
Gli attributi del 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 abbinare le condizioni di esportazione del pacchetto per questa richiesta di risoluzione. Possono essere utilizzate per cercare mappature condizionali altrove o per modificare l'elenco quando si chiama 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 di risoluzione dello specificatore di modulo Node.js predefinito quando si chiama defaultResolve
, l'array context.conditions
passato ad 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 identificatori, esegui una logica personalizzata per la risoluzione.
// Restituisci sempre un oggetto della forma {url: <stringa>}.
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, sta aggiungendo 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 il
// risoluzione predefinita di Node.js se questo è l'ultimo caricatore specificato dall'utente.
return nextResolve(specifier);
}
// Versione sincrona accettata da module.registerHooks().
function resolve(specifier, context, nextResolve) {
// Simile alla resolve() asincrona sopra, poiché quella non ha
// qualsiasi 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 il concatenamento 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 - Candidato al rilascio (versione asincrona) Stabilità: 1.1 - Sviluppo attivo (versione sincrona)
url
<stringa> L'URL restituito dalla catenaresolve
context
<Oggetto>conditions
<stringa[]> Condizioni di esportazione del relativopackage.json
format
<stringa> | <null> | <undefined> Il formato opzionalmente fornito dalla catena di hookresolve
importAttributes
<Oggetto>
nextLoad
<Funzione> L'hookload
successivo nella catena, o l'hookload
predefinito di Node.js dopo l'ultimo hookload
fornito dall'utente.Restituisce: <Oggetto> | <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
<stringa>shortCircuit
<undefined> | <booleano> Un segnale che indica che questo hook intende terminare la catena di hookload
. Predefinito:false
source
<stringa> | <ArrayBuffer> | <TypedArray> L'origine per Node.js da valutare
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 restituito da load |
---|---|---|
'builtin' | Carica un modulo integrato 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 integrato (core) di Node.js. |
Avvertenza nell'hook asincrono load
Quando si utilizza l'hook asincrono load
, omettere vs fornire un source
per 'commonjs'
ha effetti molto diversi:
- Quando viene fornito un
source
, tutte le chiamaterequire
da questo modulo saranno elaborate dal caricatore ESM con gli hookresolve
eload
registrati; tutte le chiamaterequire.resolve
da questo modulo saranno elaborate dal caricatore ESM con gli hookresolve
registrati; sarà disponibile solo un sottoinsieme dell'API CommonJS (ad es. nienterequire.extensions
, nienterequire.cache
, nienterequire.resolve.paths
) e le modifiche tramite monkey-patching sul caricatore di moduli CommonJS non verranno applicate. - Se
source
èundefined
onull
, verrà gestito 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 sincrono load
, 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 interna asincrona load
di Node.js, 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 opterebbe per l'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 neanche all'hook sincrono load
, nel qual caso il source
restituito contiene il codice sorgente caricato dall'hook successivo, indipendentemente dal formato del modulo.
- L'oggetto
ArrayBuffer
specifico è unSharedArrayBuffer
. - L'oggetto
TypedArray
specifico è 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) { // Alcune condizioni
/*
Per alcuni o tutti gli URL, eseguire una logica personalizzata per il recupero dell'origine.
Restituire sempre un oggetto della forma {
format: <string>,
source: <string|buffer>,
}.
*/
return {
format,
shortCircuit: true,
source: '...',
};
}
// Rimanda all'hook successivo nella catena.
return nextLoad(url);
}
// Versione sincrona accettata da module.registerHooks().
function load(url, context, nextLoad) {
// Simile al load() asincrono sopra, poiché questo non ha
// alcuna logica asincrona.
}
In uno scenario più avanzato, questo può anche essere utilizzato per trasformare un'origine non supportata in una supportata (vedi Esempi sotto).
Esempi
I vari hook di personalizzazione del modulo possono essere utilizzati insieme per ottenere ampie personalizzazioni dei comportamenti di caricamento e valutazione del codice di Node.js.
Importazione da HTTPS
L'hook seguente registra hook per abilitare il supporto rudimentale per tali identificatori. Sebbene questo possa sembrare un miglioramento significativo della funzionalità principale di Node.js, ci sono notevoli svantaggi nell'utilizzo 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 sulla 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 codice 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 per il modulo all'URL in main.mjs
.
Transpilazione
Le sorgenti che sono in formati che Node.js non capisce possono essere convertite in JavaScript usando l'hook load
.
Questo è meno performante della transpilazione dei file di origine prima di eseguire Node.js; gli hook del transpiler dovrebbero 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 allo stesso modo di un file .js
// nella stessa posizione. Per determinare come Node.js interpreterebbe un file .js arbitrario,
// cerca nel file system il file package.json padre 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 a 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 "Brought to you by Node.js version #{version}"
# scream.coffee {#screamcoffee}
export scream = (str) -> str.toUpperCase()
Con i precedenti moduli degli hook, 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 è stato 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 definivano hook load
. Questo è un esempio di un hook resolve
. Questo modulo di 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!');
Eseguendo 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 delle source map. Questa cache viene popolata quando l'analisi delle source map è abilitata e le direttive di inclusione delle source map vengono trovate nel footer di un modulo.
Per abilitare l'analisi delle 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 source map,undefined
altrimenti.
path
è il percorso risolto per il file per il quale deve essere recuperata una source map 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 di 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 opzionale 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 nel sorgente generatocolumnOffset
<number> L'offset del numero di colonna a base zero nel sorgente generato- 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 nel sorgente generato
- generatedColumn: <number> L'offset di colonna dell'inizio dell'intervallo nel sorgente generato
- originalSource: <string> Il nome del file della sorgente originale, come riportato nella 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 raw così come appare nella SourceMap, basato su offset a base zero, non su 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 columnNumber come riportato dagli stack di Error e dagli oggetti CallSite, utilizzare sourceMap.findOrigin(lineNumber, columnNumber)
sourceMap.findOrigin(lineNumber, columnNumber)
lineNumber
<number> Il numero di riga in base 1 del sito di chiamata nella sorgente generatacolumnNumber
<number> Il numero di colonna in base 1 del sito di chiamata nella sorgente generata- Restituisce: <Object>
Dato un lineNumber
e un columnNumber
in base 1 da un sito di chiamata nella sorgente generata, trova la posizione corrispondente del sito di chiamata nella 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 della sorgente originale, come riportato nella SourceMap
- lineNumber: <number> Il lineNumber in base 1 del sito di chiamata corrispondente nella sorgente originale
- columnNumber: <number> Il columnNumber in base 1 del sito di chiamata corrispondente nella sorgente originale