Modules : API node:module
Ajoutée dans : v0.3.7
L'objet Module
Fournit des méthodes utilitaires générales lors de l'interaction avec des instances de Module
, la variable module
souvent vue dans les modules CommonJS. Accessible via import 'node:module'
ou require('node:module')
.
module.builtinModules
[Historique]
Version | Modifications |
---|---|
v23.5.0 | La liste contient désormais également les modules avec préfixe uniquement. |
v9.3.0, v8.10.0, v6.13.0 | Ajoutée dans : v9.3.0, v8.10.0, v6.13.0 |
Une liste des noms de tous les modules fournis par Node.js. Peut être utilisée pour vérifier si un module est maintenu par un tiers ou non.
module
dans ce contexte n'est pas le même objet que celui fourni par le wrapper de module. Pour y accéder, require le module Module
:
// module.mjs
// Dans un module ECMAScript
import { builtinModules as builtin } from 'node:module';
// module.cjs
// Dans un module CommonJS
const builtin = require('node:module').builtinModules;
module.createRequire(filename)
Ajoutée dans : v12.2.0
filename
<string> | <URL> Nom de fichier à utiliser pour construire la fonction require. Doit être un objet URL de fichier, une chaîne URL de fichier ou une chaîne de chemin absolu.- Retourne : <require> Fonction Require
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
// sibling-module.js est un module CommonJS.
const siblingModule = require('./sibling-module');
module.findPackageJSON(specifier[, base])
Ajoutée dans : v23.2.0
[Stable: 1 - Expérimental]
Stable: 1 Stabilité : 1.1 - Développement actif
specifier
<string> | <URL> Le spécificateur du module dont lepackage.json
doit être récupéré. Lors du passage d'un spécificateur nu, lepackage.json
à la racine du package est retourné. Lors du passage d'un spécificateur relatif ou d'un spécificateur absolu, lepackage.json
parent le plus proche est retourné.base
<string> | <URL> L'emplacement absolu (chaîne d'URLfile :
ou chemin FS) du module conteneur. Pour CJS, utilisez__filename
(pas__dirname
!) ; pour ESM, utilisezimport.meta.url
. Vous n'avez pas besoin de le passer sispecifier
est unabsolute specifier
.- Retourne : <string> | <undefined> Un chemin si le
package.json
est trouvé. LorsquestartLocation
est un package, lepackage.json
racine du package ; lorsquestartLocation
est relatif ou non résolu, lepackage.json
le plus proche destartLocation
.
/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'
// Même résultat lors du passage d'un spécificateur absolu :
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'
// Lors du passage d'un spécificateur absolu, vous pourriez obtenir un résultat différent si le
// module résolu se trouve dans un sous-dossier qui a un `package.json` imbriqué.
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'
// Même résultat lors du passage d'un spécificateur absolu :
findPackageJSON(pathToFileURL(path.join(__dirname, '..')));
findPackageJSON('some-package', __filename);
// '/path/to/project/packages/bar/node_modules/some-package/package.json'
// Lors du passage d'un spécificateur absolu, vous pourriez obtenir un résultat différent si le
// module résolu se trouve dans un sous-dossier qui a un `package.json` imbriqué.
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)
Ajouté dans : v18.6.0, v16.17.0
moduleName
<string> Nom du module- Renvoie : <boolean> renvoie true si le module est intégré, sinon renvoie false
import { isBuiltin } from 'node:module';
isBuiltin('node:fs'); // true
isBuiltin('fs'); // true
isBuiltin('wss'); // false
module.register(specifier[, parentURL][, options])
[Historique]
Version | Modifications |
---|---|
v20.8.0, v18.19.0 | Ajout de la prise en charge des instances d'URL WHATWG. |
v20.6.0, v18.19.0 | Ajouté dans : v20.6.0, v18.19.0 |
[Stable: 1 - Expérimental]
Stable: 1 Stabilité : 1.2 - Version candidate
specifier
<string> | <URL> Hooks de personnalisation à enregistrer ; il doit s'agir de la même chaîne qui serait passée àimport()
, sauf que si elle est relative, elle est résolue par rapport àparentURL
.parentURL
<string> | <URL> Si vous souhaitez résoudrespecifier
par rapport à une URL de base, telle queimport.meta.url
, vous pouvez passer cette URL ici. Par défaut :'data:'
options
<Object>parentURL
<string> | <URL> Si vous souhaitez résoudrespecifier
par rapport à une URL de base, telle queimport.meta.url
, vous pouvez passer cette URL ici. Cette propriété est ignorée siparentURL
est fourni comme deuxième argument. Par défaut :'data:'
data
<any> Toute valeur JavaScript arbitraire et clonable à passer dans le hookinitialize
.transferList
<Object[]> objets transférables à passer dans le hookinitialize
.
Enregistre un module qui exporte des hooks qui personnalisent la résolution de module Node.js et le comportement de chargement. Voir Hooks de personnalisation.
module.registerHooks(options)
Ajouté dans : v23.5.0
options
<Object>load
<Function> | <undefined> Voir load hook. Par défaut :undefined
.resolve
<Function> | <undefined> Voir resolve hook. Par défaut :undefined
.
Enregistre les hooks qui personnalisent la résolution et le comportement de chargement des modules Node.js. Voir Customization hooks.
module.stripTypeScriptTypes(code[, options])
Ajouté dans : v23.2.0
code
<string> Le code dont il faut supprimer les annotations de type.options
<Object>mode
<string> Par défaut :'strip'
. Les valeurs possibles sont :'strip'
Supprimer uniquement les annotations de type sans effectuer la transformation des fonctionnalités TypeScript.'transform'
Supprimer les annotations de type et transformer les fonctionnalités TypeScript en JavaScript.sourceMap
<boolean> Par défaut :false
. Uniquement lorsquemode
est'transform'
, sitrue
, une source map sera générée pour le code transformé.sourceUrl
<string> Spécifie l'URL source utilisée dans la source map.
Retourne : <string> Le code avec les annotations de type supprimées.
module.stripTypeScriptTypes()
supprime les annotations de type du code TypeScript. Il peut être utilisé pour supprimer les annotations de type du code TypeScript avant de l'exécuter avecvm.runInContext()
ouvm.compileFunction()
. Par défaut, il génère une erreur si le code contient des fonctionnalités TypeScript qui nécessitent une transformation, telles queEnums
, voir type-stripping pour plus d'informations. Lorsque le mode est'transform'
, il transforme également les fonctionnalités TypeScript en JavaScript, voir transform TypeScript features pour plus d'informations. Lorsque le mode est'strip'
, les source maps ne sont pas générées, car les emplacements sont préservés. SisourceMap
est fourni, lorsqu'il est en mode'strip'
, une erreur sera générée.
ATTENTION : La sortie de cette fonction ne doit pas être considérée comme stable entre les versions de Node.js, en raison des modifications apportées à l'analyseur 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;
Si sourceUrl
est fourni, il sera ajouté en tant que commentaire à la fin de la sortie :
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;
Lorsque mode
est 'transform'
, le code est transformé en 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()
Ajoutée dans : v12.12.0
La méthode module.syncBuiltinESMExports()
met à jour toutes les liaisons actives pour les Modules ES intégrés afin de correspondre aux propriétés des exports CommonJS. Elle n'ajoute ni ne supprime les noms exportés des Modules ES.
const fs = require('node:fs');
const assert = require('node:assert');
const { syncBuiltinESMExports } = require('node:module');
fs.readFile = newAPI;
delete fs.readFileSync;
function newAPI() {
// ...
}
fs.newAPI = newAPI;
syncBuiltinESMExports();
import('node:fs').then((esmFS) => {
// Il synchronise la propriété readFile existante avec la nouvelle valeur
assert.strictEqual(esmFS.readFile, newAPI);
// readFileSync a été supprimé du fs requis
assert.strictEqual('readFileSync' in fs, false);
// syncBuiltinESMExports() ne supprime pas readFileSync de esmFS
assert.strictEqual('readFileSync' in esmFS, true);
// syncBuiltinESMExports() n'ajoute pas de noms
assert.strictEqual(esmFS.newAPI, undefined);
});
Cache de compilation de module
[Historique]
Version | Modifications |
---|---|
v22.8.0 | ajout des API JavaScript initiales pour l'accès à l'exécution. |
v22.1.0 | Ajoutée dans : v22.1.0 |
Le cache de compilation de module peut être activé soit en utilisant la méthode module.enableCompileCache()
, soit la variable d'environnement NODE_COMPILE_CACHE=dir
. Une fois activé, chaque fois que Node.js compile un module CommonJS ou un module ECMAScript, il utilise le cache de code V8 sur disque, persistant dans le répertoire spécifié, pour accélérer la compilation. Cela peut ralentir le premier chargement d'un graphe de modules, mais les chargements suivants du même graphe de modules peuvent être considérablement accélérés si le contenu des modules ne change pas.
Pour nettoyer le cache de compilation généré sur le disque, supprimez simplement le répertoire du cache. Le répertoire du cache sera recréé la prochaine fois que le même répertoire sera utilisé pour le stockage du cache de compilation. Pour éviter de remplir le disque avec un cache obsolète, il est recommandé d'utiliser un répertoire sous os.tmpdir()
. Si le cache de compilation est activé par un appel à module.enableCompileCache()
sans spécifier le répertoire, Node.js utilise la variable d'environnement NODE_COMPILE_CACHE=dir
si elle est définie, ou par défaut path.join(os.tmpdir(), 'node-compile-cache')
sinon. Pour localiser le répertoire du cache de compilation utilisé par une instance Node.js en cours d'exécution, utilisez module.getCompileCacheDir()
.
Actuellement, lors de l'utilisation du cache de compilation avec la couverture de code JavaScript V8, la couverture collectée par V8 peut être moins précise dans les fonctions qui sont désérialisées à partir du cache de code. Il est recommandé de désactiver cela lors de l'exécution de tests pour générer une couverture précise.
Le cache de compilation de module activé peut être désactivé par la variable d'environnement NODE_DISABLE_COMPILE_CACHE=1
. Cela peut être utile lorsque le cache de compilation entraîne des comportements inattendus ou indésirables (par exemple, une couverture de test moins précise).
Le cache de compilation généré par une version de Node.js ne peut pas être réutilisé par une version différente de Node.js. Le cache généré par différentes versions de Node.js sera stocké séparément si le même répertoire de base est utilisé pour conserver le cache, afin qu'ils puissent coexister.
Pour le moment, lorsque le cache de compilation est activé et qu'un module est chargé à nouveau, le cache de code est généré à partir du code compilé immédiatement, mais ne sera écrit sur le disque que lorsque l'instance Node.js est sur le point de quitter. Ceci est sujet à changement. La méthode module.flushCompileCache()
peut être utilisée pour s'assurer que le cache de code accumulé est vidé sur le disque au cas où l'application souhaite générer d'autres instances Node.js et leur permettre de partager le cache bien avant que le parent ne quitte.
module.constants.compileCacheStatus
Ajouté dans : v22.8.0
Les constantes suivantes sont renvoyées comme champ status
dans l'objet retourné par module.enableCompileCache()
pour indiquer le résultat de la tentative d'activation du cache de compilation de module.
Constante | Description |
---|---|
ENABLED | Node.js a activé le cache de compilation avec succès. Le répertoire utilisé pour stocker le cache de compilation sera renvoyé dans le champ directory de l'objet renvoyé. |
ALREADY_ENABLED | Le cache de compilation a déjà été activé, soit par un appel précédent à module.enableCompileCache() , soit par la variable d'environnement NODE_COMPILE_CACHE=dir . Le répertoire utilisé pour stocker le cache de compilation sera renvoyé dans le champ directory de l'objet renvoyé. |
FAILED | Node.js ne parvient pas à activer le cache de compilation. Cela peut être dû à un manque d'autorisation d'utiliser le répertoire spécifié, ou à divers types d'erreurs de système de fichiers. Le détail de l'échec sera renvoyé dans le champ message de l'objet renvoyé. |
DISABLED | Node.js ne peut pas activer le cache de compilation car la variable d'environnement NODE_DISABLE_COMPILE_CACHE=1 a été définie. |
module.enableCompileCache([cacheDir])
Ajouté dans : v22.8.0
cacheDir
<string> | <undefined> Chemin d'accès optionnel pour spécifier le répertoire où le cache de compilation sera stocké/récupéré.- Renvoie : <Object>
status
<integer> L'une des valeurs demodule.constants.compileCacheStatus
message
<string> | <undefined> Si Node.js ne peut pas activer le cache de compilation, ceci contient le message d'erreur. Défini uniquement sistatus
estmodule.constants.compileCacheStatus.FAILED
.directory
<string> | <undefined> Si le cache de compilation est activé, ceci contient le répertoire où le cache de compilation est stocké. Défini uniquement sistatus
estmodule.constants.compileCacheStatus.ENABLED
oumodule.constants.compileCacheStatus.ALREADY_ENABLED
.
Active le cache de compilation de module dans l'instance Node.js actuelle.
Si cacheDir
n'est pas spécifié, Node.js utilisera soit le répertoire spécifié par la variable d'environnement NODE_COMPILE_CACHE=dir
si elle est définie, soit utilisera path.join(os.tmpdir(), 'node-compile-cache')
sinon. Pour les cas d'utilisation généraux, il est recommandé d'appeler module.enableCompileCache()
sans spécifier le cacheDir
, afin que le répertoire puisse être remplacé par la variable d'environnement NODE_COMPILE_CACHE
si nécessaire.
Étant donné que le cache de compilation est censé être une optimisation silencieuse qui n'est pas nécessaire au fonctionnement de l'application, cette méthode est conçue pour ne pas lever d'exception lorsque le cache de compilation ne peut pas être activé. Au lieu de cela, elle renverra un objet contenant un message d'erreur dans le champ message
pour faciliter le débogage. Si le cache de compilation est activé avec succès, le champ directory
dans l'objet renvoyé contient le chemin d'accès au répertoire où le cache de compilation est stocké. Le champ status
dans l'objet renvoyé serait l'une des valeurs module.constants.compileCacheStatus
pour indiquer le résultat de la tentative d'activation du cache de compilation de module.
Cette méthode n'affecte que l'instance Node.js actuelle. Pour l'activer dans les threads worker enfants, soit appelez également cette méthode dans les threads worker enfants, soit définissez la valeur de process.env.NODE_COMPILE_CACHE
sur le répertoire du cache de compilation afin que le comportement puisse être hérité dans les worker enfants. Le répertoire peut être obtenu soit à partir du champ directory
renvoyé par cette méthode, soit avec module.getCompileCacheDir()
.
module.flushCompileCache()
Ajouté dans : v23.0.0
Vide le cache de compilation du module accumulé à partir des modules déjà chargés dans l'instance Node.js actuelle sur le disque. Cette fonction est renvoyée une fois que toutes les opérations du système de fichiers de vidage sont terminées, qu'elles réussissent ou non. S'il y a des erreurs, cela échouera silencieusement, car les échecs du cache de compilation ne devraient pas interférer avec le fonctionnement réel de l'application.
module.getCompileCacheDir()
Ajouté dans : v22.8.0
- Renvoie : <string> | <undefined> Chemin vers le répertoire cache de compilation du module s'il est activé, ou
undefined
sinon.
Hooks de personnalisation
[Historique]
Version | Modifications |
---|---|
v23.5.0 | Ajout du support pour les hooks synchrones et in-thread. |
v20.6.0, v18.19.0 | Ajout du hook initialize pour remplacer globalPreload . |
v18.6.0, v16.17.0 | Ajout du support pour l'enchaînement des chargeurs. |
v16.12.0 | Suppression de getFormat , getSource , transformSource et globalPreload ; ajout du hook load et du hook getGlobalPreload . |
v8.8.0 | Ajouté dans : v8.8.0 |
[Stable: 1 - Expérimental]
Stable: 1 Stable: 1.2 - Candidat à la publication (version asynchrone) Stabilité : 1.1 - Développement actif (version synchrone)
Il existe deux types de hooks de personnalisation de module actuellement pris en charge :
Activation
La résolution et le chargement des modules peuvent être personnalisés par :
Les hooks peuvent être enregistrés avant l'exécution du code de l'application en utilisant l'indicateur --import
ou --require
:
node --import ./register-hooks.js ./my-app.js
node --require ./register-hooks.js ./my-app.js
// register-hooks.js
// Ce fichier ne peut être require()-ed que s'il ne contient pas de await de premier niveau.
// Utilisez module.register() pour enregistrer des hooks asynchrones dans un thread dédié.
import { register } from 'node:module';
register('./hooks.mjs', import.meta.url);
// register-hooks.js
const { register } = require('node:module');
const { pathToFileURL } = require('node:url');
// Utilisez module.register() pour enregistrer des hooks asynchrones dans un thread dédié.
register('./hooks.mjs', pathToFileURL(__filename));
// Utilisez module.registerHooks() pour enregistrer les hooks synchrones dans le thread principal.
import { registerHooks } from 'node:module';
registerHooks({
resolve(specifier, context, nextResolve) { /* implémentation */ },
load(url, context, nextLoad) { /* implémentation */ },
});
// Utilisez module.registerHooks() pour enregistrer les hooks synchrones dans le thread principal.
const { registerHooks } = require('node:module');
registerHooks({
resolve(specifier, context, nextResolve) { /* implémentation */ },
load(url, context, nextLoad) { /* implémentation */ },
});
Le fichier passé à --import
ou --require
peut également être une exportation d'une dépendance :
node --import some-package/register ./my-app.js
node --require some-package/register ./my-app.js
Où some-package
a un champ "exports"
définissant l'exportation /register
à mapper à un fichier qui appelle register()
, comme l'exemple register-hooks.js
suivant.
L'utilisation de --import
ou --require
garantit que les hooks sont enregistrés avant l'importation des fichiers d'application, y compris le point d'entrée de l'application et pour tous les threads de travail par défaut également.
Alternativement, register()
et registerHooks()
peuvent être appelés à partir du point d'entrée, bien que import()
dynamique doit être utilisé pour tout code ESM qui doit être exécuté après l'enregistrement des hooks.
import { register } from 'node:module';
register('http-to-https', import.meta.url);
// Parce qu'il s'agit d'un `import()` dynamique, les hooks `http-to-https` s'exécuteront
// pour gérer `./my-app.js` et tous les autres fichiers qu'il importe ou nécessite.
await import('./my-app.js');
const { register } = require('node:module');
const { pathToFileURL } = require('node:url');
register('http-to-https', pathToFileURL(__filename));
// Parce qu'il s'agit d'un `import()` dynamique, les hooks `http-to-https` s'exécuteront
// pour gérer `./my-app.js` et tous les autres fichiers qu'il importe ou nécessite.
import('./my-app.js');
Les hooks de personnalisation s'exécuteront pour tous les modules chargés plus tard que l'enregistrement et les modules auxquels ils font référence via import
et le require
intégré. La fonction require
créée par les utilisateurs à l'aide de module.createRequire()
ne peut être personnalisée que par les hooks synchrones.
Dans cet exemple, nous enregistrons les hooks http-to-https
, mais ils ne seront disponibles que pour les modules importés ultérieurement, dans ce cas, my-app.js
et tout ce qu'il référence via import
ou require
intégré dans les dépendances CommonJS.
Si le import('./my-app.js')
avait plutôt été un import './my-app.js'
statique, l'application aurait déjà été chargée avant que les hooks http-to-https
ne soient enregistrés. Cela est dû à la spécification des modules ES, où les importations statiques sont évaluées d'abord à partir des feuilles de l'arbre, puis en remontant vers le tronc. Il peut y avoir des importations statiques dans my-app.js
, qui ne seront pas évaluées tant que my-app.js
n'aura pas été importé dynamiquement.
Si des hooks synchrones sont utilisés, import
, require
et l'utilisateur require
créé à l'aide de createRequire()
sont pris en charge.
import { registerHooks, createRequire } from 'node:module';
registerHooks({ /* implémentation des hooks synchrones */ });
const require = createRequire(import.meta.url);
// Les hooks synchrones affectent import, require() et la fonction require() utilisateur
// créée via createRequire().
await import('./my-app.js');
require('./my-app-2.js');
const { register, registerHooks } = require('node:module');
const { pathToFileURL } = require('node:url');
registerHooks({ /* implémentation des hooks synchrones */ });
const userRequire = createRequire(__filename);
// Les hooks synchrones affectent import, require() et la fonction require() utilisateur
// créée via createRequire().
import('./my-app.js');
require('./my-app-2.js');
userRequire('./my-app-3.js');
Enfin, si tout ce que vous voulez faire est d'enregistrer des hooks avant l'exécution de votre application et que vous ne voulez pas créer un fichier séparé à cet effet, vous pouvez passer une URL data:
à --import
:
node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("http-to-https", pathToFileURL("./"));' ./my-app.js
Chaînage
Il est possible d'appeler register
plus d'une fois :
// 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');
Dans cet exemple, les hooks enregistrés formeront des chaînes. Ces chaînes s'exécutent de la dernière entrée à la première sortie (LIFO). Si foo.mjs
et bar.mjs
définissent tous les deux un hook resolve
, ils seront appelés comme suit (notez la lecture de droite à gauche) : valeur par défaut de Node ← ./foo.mjs
← ./bar.mjs
(en commençant par ./bar.mjs
, puis ./foo.mjs
, puis la valeur par défaut de Node.js). Il en va de même pour tous les autres hooks.
Les hooks enregistrés affectent également register
lui-même. Dans cet exemple, bar.mjs
sera résolu et chargé via les hooks enregistrés par foo.mjs
(car les hooks de foo
auront déjà été ajoutés à la chaîne). Cela permet de faire des choses comme écrire des hooks dans des langages autres que JavaScript, à condition que les hooks enregistrés plus tôt soient transpilés en JavaScript.
La méthode register
ne peut pas être appelée depuis le module qui définit les hooks.
Le chaînage de registerHooks
fonctionne de manière similaire. Si des hooks synchrones et asynchrones sont mélangés, les hooks synchrones sont toujours exécutés avant le démarrage des hooks asynchrones, c'est-à-dire que, dans le dernier hook synchrone en cours d'exécution, son hook suivant inclut l'invocation des hooks asynchrones.
// 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);
Communication avec les hooks de personnalisation de module
Les hooks asynchrones s'exécutent sur un thread dédié, séparé du thread principal qui exécute le code de l'application. Cela signifie que la modification des variables globales n'affectera pas les autres threads, et que des canaux de messages doivent être utilisés pour communiquer entre les threads.
La méthode register
peut être utilisée pour transmettre des données à un hook initialize
. Les données transmises au hook peuvent inclure des objets transférables comme des ports.
import { register } from 'node:module';
import { MessageChannel } from 'node:worker_threads';
// Cet exemple montre comment un canal de messages peut être utilisé pour
// communiquer avec les hooks, en envoyant `port2` aux hooks.
const { port1, port2 } = new MessageChannel();
port1.on('message', (msg) => {
console.log(msg);
});
port1.unref();
register('./my-hooks.mjs', {
parentURL: import.meta.url,
data: { number: 1, port: port2 },
transferList: [port2],
});
const { register } = require('node:module');
const { pathToFileURL } = require('node:url');
const { MessageChannel } = require('node:worker_threads');
// Cet exemple montre comment un canal de messages peut être utilisé pour
// communiquer avec les hooks, en envoyant `port2` aux hooks.
const { port1, port2 } = new MessageChannel();
port1.on('message', (msg) => {
console.log(msg);
});
port1.unref();
register('./my-hooks.mjs', {
parentURL: pathToFileURL(__filename),
data: { number: 1, port: port2 },
transferList: [port2],
});
Les hooks de module synchrones sont exécutés sur le même thread que celui où le code de l'application est exécuté. Ils peuvent directement modifier les variables globales du contexte auquel le thread principal accède.
Hooks
Hooks asynchrones acceptés par module.register()
La méthode register
peut être utilisée pour enregistrer un module qui exporte un ensemble de hooks. Les hooks sont des fonctions qui sont appelées par Node.js pour personnaliser la résolution de module et le processus de chargement. Les fonctions exportées doivent avoir des noms et des signatures spécifiques, et elles doivent être exportées en tant qu'exports nommés.
export async function initialize({ number, port }) {
// Reçoit les données de `register`.
}
export async function resolve(specifier, context, nextResolve) {
// Prend un spécificateur `import` ou `require` et le résout en une URL.
}
export async function load(url, context, nextLoad) {
// Prend une URL résolue et renvoie le code source à évaluer.
}
Les hooks asynchrones sont exécutés dans un thread séparé, isolé du thread principal où le code de l'application est exécuté. Cela signifie qu'il s'agit d'un realm différent. Le thread des hooks peut être terminé par le thread principal à tout moment, donc ne comptez pas sur les opérations asynchrones (comme console.log
) pour se terminer. Ils sont hérités par défaut dans les workers enfants.
Hooks synchrones acceptés par module.registerHooks()
Ajouté dans : v23.5.0
[Stable: 1 - Expérimental]
Stable: 1 Stabilité: 1.1 - Développement actif
La méthode module.registerHooks()
accepte les fonctions de hook synchrones. initialize()
n’est pas pris en charge ni nécessaire, car l’implémenteur de hook peut simplement exécuter le code d’initialisation directement avant l’appel à module.registerHooks()
.
function resolve(specifier, context, nextResolve) {
// Prend un identifiant `import` ou `require` et le résout en une URL.
}
function load(url, context, nextLoad) {
// Prend une URL résolue et renvoie le code source à évaluer.
}
Les hooks synchrones sont exécutés dans le même thread et le même realm où les modules sont chargés. Contrairement aux hooks asynchrones, ils ne sont pas hérités par défaut dans les threads de worker enfants, bien que si les hooks sont enregistrés à l’aide d’un fichier préchargé par --import
ou --require
, les threads de worker enfants peuvent hériter des scripts préchargés via l’héritage process.execArgv
. Consultez la documentation de Worker
pour plus de détails.
Dans les hooks synchrones, les utilisateurs peuvent s’attendre à ce que console.log()
se termine de la même manière qu’ils s’attendent à ce que console.log()
dans le code du module se termine.
Conventions des hooks
Les hooks font partie d’une chaîne, même si cette chaîne ne comprend qu’un seul hook personnalisé (fourni par l’utilisateur) et le hook par défaut, qui est toujours présent. Les fonctions de hook s’imbriquent : chacune doit toujours renvoyer un objet simple, et le chaînage se produit à la suite de chaque fonction appelant next\<hookName\>()
, qui est une référence au hook du chargeur suivant (dans l’ordre LIFO).
Un hook qui renvoie une valeur dépourvue d’une propriété requise déclenche une exception. Un hook qui renvoie sans appeler next\<hookName\>()
et sans renvoyer shortCircuit: true
déclenche également une exception. Ces erreurs visent à éviter les ruptures involontaires dans la chaîne. Renvoyez shortCircuit: true
d’un hook pour signaler que la chaîne se termine intentionnellement à votre hook.
initialize()
Ajouté dans : v20.6.0, v18.19.0
[Stable : 1 - Expérimental]
Stable : 1 Stable : 1.2 - Candidat à la publication
data
<any> Les données deregister(loader, import.meta.url, { data })
.
Le hook initialize
est uniquement accepté par register
. registerHooks()
ne le prend pas en charge et n’en a pas besoin, car l’initialisation effectuée pour les hooks synchrones peut être exécutée directement avant l’appel à registerHooks()
.
Le hook initialize
permet de définir une fonction personnalisée qui s’exécute dans le thread des hooks lorsque le module des hooks est initialisé. L’initialisation a lieu lorsque le module des hooks est enregistré via register
.
Ce hook peut recevoir des données d’un appel register
, y compris des ports et d’autres objets transférables. La valeur de retour de initialize
peut être une <Promise>, auquel cas elle sera attendue avant que l’exécution du thread d’application principal ne reprenne.
Code de personnalisation du module :
// path-to-my-hooks.js
export async function initialize({ number, port }) {
port.postMessage(`increment: ${number + 1}`);
}
Code de l’appelant :
import assert from 'node:assert';
import { register } from 'node:module';
import { MessageChannel } from 'node:worker_threads';
// Cet exemple montre comment un canal de messages peut être utilisé pour communiquer
// entre le thread principal (d’application) et les hooks exécutés sur le thread
// des hooks, en envoyant `port2` au 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');
// Cet exemple montre comment un canal de messages peut être utilisé pour communiquer
// entre le thread principal (d’application) et les hooks exécutés sur le thread
// des hooks, en envoyant `port2` au 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)
[Historique]
Version | Modifications |
---|---|
v23.5.0 | Ajout du support pour les hooks synchrones et in-thread. |
v21.0.0, v20.10.0, v18.19.0 | La propriété context.importAssertions est remplacée par context.importAttributes . L'utilisation de l'ancien nom est toujours prise en charge et émettra un avertissement expérimental. |
v18.6.0, v16.17.0 | Ajout du support pour chaîner les hooks de résolution. Chaque hook doit soit appeler nextResolve() soit inclure une propriété shortCircuit définie sur true dans son retour. |
v17.1.0, v16.14.0 | Ajout du support des assertions d'importation. |
[Stable: 1 - Expérimental]
Stable: 1 Stabilité : 1.2 - Candidat à la publication (version asynchrone) Stabilité : 1.1 - Développement actif (version synchrone)
specifier
<string>context
<Object>conditions
<string[]> Conditions d'exportation dupackage.json
pertinentimportAttributes
<Object> Un objet dont les paires clé-valeur représentent les attributs du module à importerparentURL
<string> | <undefined> Le module important celui-ci, ou undefined si c'est le point d'entrée de Node.js
nextResolve
<Function> Le hookresolve
suivant dans la chaîne, ou le hookresolve
par défaut de Node.js après le dernier hookresolve
fourni par l'utilisateurRetourne : <Object> | <Promise> La version asynchrone prend soit un objet contenant les propriétés suivantes, soit une
Promise
qui sera résolue en un tel objet. La version synchrone n'accepte qu'un objet renvoyé de manière synchrone.format
<string> | <null> | <undefined> Un indice pour le hook de chargement (il peut être ignoré)'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'
importAttributes
<Object> | <undefined> Les attributs d'importation à utiliser lors de la mise en cache du module (facultatif ; s'il est exclu, l'entrée sera utilisée)shortCircuit
<undefined> | <boolean> Un signal indiquant que ce hook a l'intention de terminer la chaîne de hooksresolve
. Par défaut :false
url
<string> L'URL absolue à laquelle cette entrée est résolue
La chaîne de hooks resolve
est chargée d'indiquer à Node.js où trouver et comment mettre en cache une instruction ou une expression import
donnée, ou un appel require
. Il peut éventuellement renvoyer un format (tel que 'module'
) comme indication au hook load
. Si un format est spécifié, le hook load
est en fin de compte responsable de la fourniture de la valeur finale de format
(et il est libre d'ignorer l'indication fournie par resolve
) ; si resolve
fournit un format
, un hook load
personnalisé est requis, même si ce n'est que pour transmettre la valeur au hook load
par défaut de Node.js.
Les attributs de type d'importation font partie de la clé de cache pour enregistrer les modules chargés dans le cache de modules interne. Le hook resolve
est chargé de renvoyer un objet importAttributes
si le module doit être mis en cache avec des attributs différents de ceux présents dans le code source.
La propriété conditions
dans context
est un tableau de conditions qui seront utilisées pour faire correspondre les conditions d'exportation de package pour cette demande de résolution. Ils peuvent être utilisés pour rechercher des mappages conditionnels ailleurs ou pour modifier la liste lors de l'appel à la logique de résolution par défaut.
Les conditions d'exportation de package actuelles sont toujours dans le tableau context.conditions
transmis au hook. Pour garantir le comportement de résolution du spécificateur de module Node.js par défaut lors de l'appel à defaultResolve
, le tableau context.conditions
qui lui est transmis doit inclure tous les éléments du tableau context.conditions
initialement transmis au hook resolve
.
// Version asynchrone acceptée par module.register().
export async function resolve(specifier, context, nextResolve) {
const { parentURL = null } = context;
if (Math.random() > 0.5) { // Une certaine condition.
// Pour certains ou tous les spécificateurs, effectuez une logique personnalisée pour la résolution.
// Renvoie toujours un objet de la forme {url: <string>}.
return {
shortCircuit: true,
url: parentURL ?
new URL(specifier, parentURL).href :
new URL(specifier).href,
};
}
if (Math.random() < 0.5) { // Une autre condition.
// Lors de l'appel de `defaultResolve`, les arguments peuvent être modifiés. Dans ce
// cas, il ajoute une autre valeur pour faire correspondre les exportations conditionnelles.
return nextResolve(specifier, {
...context,
conditions: [...context.conditions, 'another-condition'],
});
}
// S'en remettre au hook suivant dans la chaîne, qui serait la
// résolution par défaut de Node.js s'il s'agit du dernier chargeur spécifié par l'utilisateur.
return nextResolve(specifier);
}
// Version synchrone acceptée par module.registerHooks().
function resolve(specifier, context, nextResolve) {
// Similaire à la résolution asynchrone() ci-dessus, car celle-ci n'a pas
// toute logique asynchrone.
}
load(url, context, nextLoad)
[Historique]
Version | Modifications |
---|---|
v23.5.0 | Ajout de la prise en charge de la version synchrone et in-thread. |
v20.6.0 | Ajout de la prise en charge de source avec le format commonjs . |
v18.6.0, v16.17.0 | Ajout de la prise en charge de l'enchaînement des hooks de chargement. Chaque hook doit soit appeler nextLoad() soit inclure une propriété shortCircuit définie sur true dans son retour. |
[Stable: 1 - Expérimental]
Stable: 1 Stabilité: 1.2 - Version candidate (version asynchrone) Stabilité: 1.1 - Développement actif (version synchrone)
url
<string> L'URL renvoyée par la chaîneresolve
context
<Object>conditions
<string[]> Conditions d'export dupackage.json
concernéformat
<string> | <null> | <undefined> Le format éventuellement fourni par la chaîne de hookresolve
importAttributes
<Object>
nextLoad
<Function> Le hookload
suivant dans la chaîne, ou le hookload
par défaut de Node.js après le dernier hookload
fourni par l'utilisateur.Renvoie : <Object> | <Promise> La version asynchrone prend soit un objet contenant les propriétés suivantes, soit une
Promise
qui sera résolue en un tel objet. La version synchrone n'accepte qu'un objet renvoyé de manière synchrone.format
<string>shortCircuit
<undefined> | <boolean> Un signal indiquant que ce hook a l'intention de mettre fin à la chaîne de hooksload
. Par défaut :false
source
<string> | <ArrayBuffer> | <TypedArray> La source que Node.js doit évaluer
Le hook load
fournit un moyen de définir une méthode personnalisée pour déterminer comment une URL doit être interprétée, récupérée et analysée. Il est également chargé de valider les attributs d'importation.
La valeur finale de format
doit être l'une des suivantes :
format | Description | Types acceptables pour source renvoyés par load |
---|---|---|
'builtin' | Charger un module intégré de Node.js | Non applicable |
'commonjs' | Charger un module CommonJS de Node.js | { string , ArrayBuffer , TypedArray , null , undefined } |
'json' | Charger un fichier JSON | { string , ArrayBuffer , TypedArray } |
'module' | Charger un module ES | { string , ArrayBuffer , TypedArray } |
'wasm' | Charger un module WebAssembly | { ArrayBuffer , TypedArray } |
La valeur de source est ignorée pour le type 'builtin' car il n'est actuellement pas possible de remplacer la valeur d'un module intégré (core) de Node.js. |
Mise en garde concernant le hook asynchrone load
Lors de l'utilisation du hook asynchrone load
, l'omission ou la fourniture d'une source
pour 'commonjs'
a des effets très différents :
- Lorsqu'une
source
est fournie, tous les appelsrequire
de ce module seront traités par le chargeur ESM avec les hooksresolve
etload
enregistrés ; tous les appelsrequire.resolve
de ce module seront traités par le chargeur ESM avec les hooksresolve
enregistrés ; seule un sous-ensemble de l'API CommonJS sera disponible (par exemple, pas derequire.extensions
, pas derequire.cache
, pas derequire.resolve.paths
) et le monkey-patching sur le chargeur de module CommonJS ne s'appliquera pas. - Si
source
n'est pas définie ou estnull
, elle sera gérée par le chargeur de module CommonJS et les appelsrequire
/require.resolve
ne passeront pas par les hooks enregistrés. Ce comportement pour unesource
nulle est temporaire — à l'avenir, unesource
nulle ne sera pas prise en charge.
Ces mises en garde ne s'appliquent pas au hook synchrone load
, auquel cas l'ensemble complet des API CommonJS disponibles pour les modules CommonJS personnalisés, et require
/require.resolve
passent toujours par les hooks enregistrés.
L'implémentation interne asynchrone load
de Node.js, qui est la valeur de next
pour le dernier hook de la chaîne load
, renvoie null
pour source
lorsque format
est 'commonjs'
pour assurer la rétrocompatibilité. Voici un exemple de hook qui choisirait d'utiliser le comportement non défini par défaut :
import { readFile } from 'node:fs/promises';
// Version asynchrone acceptée par module.register(). Ce correctif n'est pas nécessaire
// pour la version synchrone acceptée par 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;
}
Cela ne s'applique pas non plus au hook synchrone load
, auquel cas la source
renvoyée contient le code source chargé par le hook suivant, quel que soit le format du module.
- L'objet
ArrayBuffer
spécifique est unSharedArrayBuffer
. - L'objet
TypedArray
spécifique est unUint8Array
.
Si la valeur source d'un format textuel (c'est-à-dire 'json'
, 'module'
) n'est pas une chaîne de caractères, elle est convertie en une chaîne à l'aide de util.TextDecoder
.
Le hook load
fournit un moyen de définir une méthode personnalisée pour récupérer le code source d'une URL résolue. Cela permettrait à un chargeur d'éviter potentiellement de lire des fichiers sur le disque. Il pourrait également être utilisé pour mapper un format non reconnu à un format pris en charge, par exemple yaml
à module
.
// Version asynchrone acceptée par module.register().
export async function load(url, context, nextLoad) {
const { format } = context;
if (Math.random() > 0.5) { // Une condition quelconque
/*
Pour certaines ou toutes les URLs, effectuez une logique personnalisée pour récupérer la source.
Retournez toujours un objet de la forme {
format: <string>,
source: <string|buffer>,
}.
*/
return {
format,
shortCircuit: true,
source: '...',
};
}
// Déférez au hook suivant de la chaîne.
return nextLoad(url);
}
// Version synchrone acceptée par module.registerHooks().
function load(url, context, nextLoad) {
// Similaire à la fonction asynchrone load() ci-dessus, car celle-ci ne contient pas
// de logique asynchrone.
}
Dans un scénario plus avancé, cela peut également être utilisé pour transformer une source non prise en charge en une source prise en charge (voir Exemples ci-dessous).
Exemples
Les différents points d'ancrage de personnalisation des modules peuvent être utilisés ensemble pour réaliser des personnalisations de grande envergure des comportements de chargement et d'évaluation du code Node.js.
Importer depuis HTTPS
Le point d'ancrage ci-dessous enregistre des points d'ancrage pour activer une prise en charge rudimentaire de tels spécificateurs. Bien que cela puisse sembler être une amélioration significative de la fonctionnalité principale de Node.js, l'utilisation réelle de ces points d'ancrage présente des inconvénients importants : les performances sont beaucoup plus lentes que le chargement de fichiers à partir du disque, il n'y a pas de mise en cache et il n'y a aucune sécurité.
// https-hooks.mjs
import { get } from 'node:https';
export function load(url, context, nextLoad) {
// Pour que JavaScript soit chargé sur le réseau, nous devons le récupérer et
// le retourner.
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({
// Cet exemple suppose que tout le code JavaScript fourni par le réseau est du code de module ES
// code.
format: 'module',
shortCircuit: true,
source: data,
}));
}).on('error', (err) => reject(err));
});
}
// Laisser Node.js gérer toutes les autres URL.
return nextLoad(url);
}
// main.mjs
import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js';
console.log(VERSION);
Avec le module de points d'ancrage précédent, l'exécution de node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./https-hooks.mjs"));' ./main.mjs
affiche la version actuelle de CoffeeScript selon le module à l'URL dans main.mjs
.
Transpilation
Les sources qui sont dans des formats que Node.js ne comprend pas peuvent être converties en JavaScript à l'aide du load
hook.
Ceci est moins performant que la transpilation des fichiers sources avant d'exécuter Node.js ; les points d'ancrage de transpilation ne doivent être utilisés qu'à des fins de développement et de test.
Version asynchrone
// 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)) {
// Les fichiers CoffeeScript peuvent être soit CommonJS soit des modules ES, donc nous voulons que
// tout fichier CoffeeScript soit traité par Node.js de la même manière qu'un fichier .js
// au même emplacement. Pour déterminer comment Node.js interpréterait un fichier .js arbitraire,
// recherchez dans le système de fichiers le fichier package.json parent le plus proche
// et lisez son champ "type".
const format = await getPackageType(url);
const { source: rawSource } = await nextLoad(url, { ...context, format });
// Ce hook convertit le code source CoffeeScript en code source JavaScript
// pour tous les fichiers CoffeeScript importés.
const transformedSource = coffeescript.compile(rawSource.toString(), url);
return {
format,
shortCircuit: true,
source: transformedSource,
};
}
// Laisser Node.js gérer toutes les autres URLs.
return nextLoad(url);
}
async function getPackageType(url) {
// `url` n'est qu'un chemin de fichier lors de la première itération lorsqu'on lui passe
// l'url résolue depuis le hook load()
// un chemin de fichier réel depuis load() contiendra une extension de fichier car elle est
// requise par la spécification
// cette simple vérification de vérité pour savoir si `url` contient une extension de fichier
// fonctionnera pour la plupart des projets mais ne couvre pas certains cas limites (tels que
// les fichiers sans extension ou une url se terminant par un espace de fin)
const isFilePath = !!extname(url);
// S'il s'agit d'un chemin de fichier, obtenir le répertoire dans lequel il se trouve
const dir = isFilePath ?
dirname(fileURLToPath(url)) :
url;
// Composer un chemin de fichier vers un package.json dans le même répertoire,
// qui peut exister ou non
const packagePath = resolvePath(dir, 'package.json');
// Essayer de lire le package.json possiblement inexistant
const type = await readFile(packagePath, { encoding: 'utf8' })
.then((filestring) => JSON.parse(filestring).type)
.catch((err) => {
if (err?.code !== 'ENOENT') console.error(err);
});
// Si package.json existait et contenait un champ `type` avec une valeur, voilà
if (type) return type;
// Sinon, (si ce n'est pas à la racine) continuer à vérifier le répertoire supérieur suivant
// Si à la racine, arrêter et renvoyer false
return dir.length > 1 && getPackageType(resolvePath(dir, '..'));
}
Version synchrone
// 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 });
Exécution des hooks
# main.coffee {#maincoffee}
import { scream } from './scream.coffee'
console.log scream 'hello, world'
import { version } from 'node:process'
console.log "Propulsé par Node.js version #{version}"
# scream.coffee {#screamcoffee}
export scream = (str) -> str.toUpperCase()
Avec les modules de hooks précédents, l'exécution de node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee
ou node --import ./coffeescript-sync-hooks.mjs ./main.coffee
fait que main.coffee
est transformé en JavaScript après que son code source est chargé depuis le disque, mais avant que Node.js ne l'exécute ; et ainsi de suite pour tous les fichiers .coffee
, .litcoffee
ou .coffee.md
référencés via les instructions import
de n'importe quel fichier chargé.
Cartes d'importation
Les deux exemples précédents définissaient des hooks load
. Voici un exemple de hook resolve
. Ce module de hooks lit un fichier import-map.json
qui définit quels identificateurs remplacer par d'autres URL (il s'agit d'une implémentation très simpliste d'un petit sous-ensemble de la spécification "cartes d'importation").
Version asynchrone
// 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);
}
Version synchrone
// 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 });
Utilisation des hooks
Avec ces fichiers :
// main.js
import 'a-module';
// import-map.json
{
"imports": {
"a-module": "./some-module.js"
}
}
// some-module.js
console.log('some module!');
L'exécution de node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./import-map-hooks.js"));' main.js
ou node --import ./import-map-sync-hooks.js main.js
doit afficher some module!
.
Prise en charge de Source Map v3
Ajoutée dans : v13.7.0, v12.17.0
[Stable: 1 - Expérimental]
Stable: 1 Stabilité : 1 - Expérimental
Assistants pour interagir avec le cache de la carte source. Ce cache est rempli lorsque l'analyse de la carte source est activée et que des directives d'inclusion de carte source sont trouvées dans le pied de page d'un module.
Pour activer l'analyse des cartes sources, Node.js doit être exécuté avec l'indicateur --enable-source-maps
ou avec la couverture du code activée en définissant NODE_V8_COVERAGE=dir
.
// module.mjs
// Dans un module ECMAScript
import { findSourceMap, SourceMap } from 'node:module';
// module.cjs
// Dans un module CommonJS
const { findSourceMap, SourceMap } = require('node:module');
module.findSourceMap(path)
Ajouté dans: v13.7.0, v12.17.0
path
<string>- Retourne: <module.SourceMap> | <undefined> Retourne
module.SourceMap
si une source map est trouvée,undefined
sinon.
path
est le chemin résolu du fichier pour lequel une source map correspondante doit être récupérée.
Class: module.SourceMap
Ajouté dans: v13.7.0, v12.17.0
new SourceMap(payload[, { lineLengths }])
{#new-sourcemappayload-{-linelengths-}}
payload
<Object>lineLengths
<number[]>
Crée une nouvelle instance sourceMap
.
payload
est un objet avec des clés correspondant au format Source map v3 :
file
: <string>version
: <number>sources
: <string[]>sourcesContent
: <string[]>names
: <string[]>mappings
: <string>sourceRoot
: <string>
lineLengths
est un tableau optionnel de la longueur de chaque ligne dans le code généré.
sourceMap.payload
- Retourne: <Object>
Getter pour la payload utilisée pour construire l'instance SourceMap
.
sourceMap.findEntry(lineOffset, columnOffset)
lineOffset
<number> L'offset du numéro de ligne (indexé à zéro) dans la source généréecolumnOffset
<number> L'offset du numéro de colonne (indexé à zéro) dans la source générée- Retourne : <Object>
Étant donné un offset de ligne et un offset de colonne dans le fichier source généré, renvoie un objet représentant la plage SourceMap dans le fichier original si elle est trouvée, ou un objet vide sinon.
L'objet renvoyé contient les clés suivantes :
- generatedLine : <number> L'offset de ligne du début de la plage dans la source générée
- generatedColumn : <number> L'offset de colonne du début de la plage dans la source générée
- originalSource : <string> Le nom de fichier de la source originale, tel qu'indiqué dans le SourceMap
- originalLine : <number> L'offset de ligne du début de la plage dans la source originale
- originalColumn : <number> L'offset de colonne du début de la plage dans la source originale
- name : <string>
La valeur renvoyée représente la plage brute telle qu'elle apparaît dans le SourceMap, basée sur des offsets indexés à zéro, et non des numéros de ligne et de colonne indexés à 1 tels qu'ils apparaissent dans les messages d'erreur et les objets CallSite.
Pour obtenir les numéros de ligne et de colonne indexés à 1 correspondants à partir d'un lineNumber et d'un columnNumber tels qu'ils sont rapportés par les piles d'erreurs et les objets CallSite, utilisez sourceMap.findOrigin(lineNumber, columnNumber)
sourceMap.findOrigin(lineNumber, columnNumber)
lineNumber
<number> Le numéro de ligne, indexé à partir de 1, de l’emplacement d’appel dans la source généréecolumnNumber
<number> Le numéro de colonne, indexé à partir de 1, de l’emplacement d’appel dans la source générée- Retourne : <Object>
Étant donné un lineNumber
et un columnNumber
indexés à partir de 1 à partir d’un emplacement d’appel dans la source générée, trouver l’emplacement d’appel correspondant dans la source originale.
Si le lineNumber
et le columnNumber
fournis ne sont pas trouvés dans une source map, un objet vide est retourné. Sinon, l’objet retourné contient les clés suivantes :
- name : <string> | <undefined> Le nom de la plage dans la source map, si elle a été fournie
- fileName : <string> Le nom du fichier de la source originale, tel qu’indiqué dans la SourceMap
- lineNumber : <number> Le lineNumber, indexé à partir de 1, de l’emplacement d’appel correspondant dans la source originale
- columnNumber : <number> Le columnNumber, indexé à partir de 1, de l’emplacement d’appel correspondant dans la source originale