Skip to content

Modules : API node:module

Ajouté 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]

VersionModifications
v23.5.0La liste contient désormais également les modules à préfixe uniquement.
v9.3.0, v8.10.0, v6.13.0Ajouté 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 l' encapsuleur de module. Pour y accéder, nécessite le module Module :

js
// module.mjs
// Dans un module ECMAScript
import { builtinModules as builtin } from 'node:module'
js
// module.cjs
// Dans un module CommonJS
const builtin = require('node:module').builtinModules

module.createRequire(filename)

Ajouté 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 d'URL de fichier ou une chaîne de chemin absolu.
  • Retourne : <require> Fonction require
js
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é dans : v23.2.0

[Stable : 1 - Expérimental]

Stable : 1 Stabilité : 1.1 - Développement actif

  • specifier <string> | <URL> Le spécificateur pour le module dont le fichier package.json doit être récupéré. Lorsque vous passez un spécificateur nu, le fichier package.json à la racine du package est renvoyé. Lorsque vous passez un spécificateur relatif ou un spécificateur absolu, le fichier package.json parent le plus proche est renvoyé.
  • base <string> | <URL> L'emplacement absolu (chaîne d'URL de fichier ou chemin FS) du module contenant. Pour CJS, utilisez __filename (pas __dirname) ; pour ESM, utilisez import.meta.url. Vous n'avez pas besoin de le passer si specifier est un spécificateur absolu.
  • Retourne : <string> | <undefined> Un chemin si le fichier package.json est trouvé. Lorsque startLocation est un package, le fichier package.json racine du package ; lorsqu'il est relatif ou non résolu, le fichier package.json le plus proche de startLocation.
text
/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'
js
// /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 lorsque vous passez un spécificateur absolu à la place :
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'
// Lorsque vous passez un spécificateur absolu, vous pouvez obtenir un résultat différent si le
// module résolu se trouve dans un sous-dossier qui possède un fichier `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'
js
// /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 lorsque vous passez un spécificateur absolu à la place :
findPackageJSON(pathToFileURL(path.join(__dirname, '..')))

findPackageJSON('some-package', __filename)
// '/path/to/project/packages/bar/node_modules/some-package/package.json'
// Lorsque vous passez un spécificateur absolu, vous pouvez obtenir un résultat différent si le
// module résolu se trouve dans un sous-dossier qui possède un fichier `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
  • Retourne : <boolean> retourne true si le module est intégré, sinon retourne false
js
import { isBuiltin } from 'node:module'
isBuiltin('node:fs') // true
isBuiltin('fs') // true
isBuiltin('wss') // false

module.register(specifier[, parentURL][, options])

[Historique]

VersionModifications
v20.8.0, v18.19.0Ajout de la prise en charge des instances d'URL WHATWG.
v20.6.0, v18.19.0Ajouté 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 ; cela doit être 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ésoudre specifier par rapport à une URL de base, telle que import.meta.url, vous pouvez passer cette URL ici. Par défaut : 'data:'
  • options <Object>
    • parentURL <string> | <URL> Si vous souhaitez résoudre specifier par rapport à une URL de base, telle que import.meta.url, vous pouvez passer cette URL ici. Cette propriété est ignorée si parentURL est fourni comme deuxième argument. Par défaut : 'data:'
    • data <any> Toute valeur JavaScript arbitraire et clonable à transmettre au hook initialize.
    • transferList <Object[]> objets transférables à transmettre au hook initialize.

Enregistrez un module qui exporte des hooks qui personnalisent le comportement de résolution et de chargement des modules Node.js. Voir Hooks de personnalisation.

module.registerHooks(options)

Ajouté dans : v23.5.0

[Stable : 1 - Expérimental]

Stable : 1 Stabilité : 1.1 - Développement actif

Enregistrez les crochets qui personnalisent la résolution et le comportement de chargement des modules Node.js. Voir Crochets de personnalisation.

module.stripTypeScriptTypes(code[, options])

Ajouté dans : v23.2.0

[Stable : 1 - Expérimental]

Stable : 1 Stabilité : 1.1 - Développement actif

  • code <chaîne de caractères> Le code duquel supprimer les annotations de type.

  • options <Objet>

    • mode <chaîne de caractères> Défaut : 'strip'. Les valeurs possibles sont :

    • 'strip' Supprime uniquement les annotations de type sans effectuer la transformation des fonctionnalités TypeScript.

    • 'transform' Supprime les annotations de type et transforme les fonctionnalités TypeScript en JavaScript.

    • sourceMap <booléen> Défaut : false. Seulement lorsque mode est 'transform', si true, une source map sera générée pour le code transformé.

    • sourceUrl <chaîne de caractères> Spécifie l'URL source utilisée dans la source map.

  • Retourne : <chaîne de caractères> 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 avec vm.runInContext() ou vm.compileFunction(). Par défaut, il lèvera une erreur si le code contient des fonctionnalités TypeScript qui nécessitent une transformation telles que les Enums, voir suppression de type pour plus d'informations. Lorsque le mode est 'transform', il transforme également les fonctionnalités TypeScript en JavaScript, voir transformer les fonctionnalités TypeScript pour plus d'informations. Lorsque le mode est 'strip', les source maps ne sont pas générées, car les emplacements sont conservés. Si sourceMap est fourni, lorsque le mode est 'strip', une erreur sera levée.

AVERTISSEMENT : 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.

js
import { stripTypeScriptTypes } from 'node:module'
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code)
console.log(strippedCode)
// Affiche : const a         = 1;
js
const { stripTypeScriptTypes } = require('node:module')
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code)
console.log(strippedCode)
// Affiche : const a         = 1;

Si sourceUrl est fourni, il sera utilisé et ajouté en tant que commentaire à la fin de la sortie :

js
import { stripTypeScriptTypes } from 'node:module'
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' })
console.log(strippedCode)
// Affiche : const a         = 1\n\n//# sourceURL=source.ts;
js
const { stripTypeScriptTypes } = require('node:module')
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' })
console.log(strippedCode)
// Affiche : const a         = 1\n\n//# sourceURL=source.ts;

Lorsque mode est 'transform', le code est transformé en JavaScript :

js
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)
// Affiche :
// var MathUtil;
// (function(MathUtil) {
//     MathUtil.add = (a, b)=>a + b;
// })(MathUtil || (MathUtil = {}));
// # sourceMappingURL=data:application/json;base64, ...
js
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)
// Affiche :
// var MathUtil;
// (function(MathUtil) {
//     MathUtil.add = (a, b)=>a + b;
// })(MathUtil || (MathUtil = {}));
// # sourceMappingURL=data:application/json;base64, ...

module.syncBuiltinESMExports()

Ajouté dans : v12.12.0

La méthode module.syncBuiltinESMExports() met à jour toutes les liaisons actives pour les modules ES intégrés /api/esm afin qu’elles correspondent aux propriétés des exportations CommonJS /api/modules. Elle n’ajoute ni ne supprime de noms exportés des modules ES /api/esm.

js
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 => {
  // Elle 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 des modules

[Historique]

VersionModifications
v22.8.0Ajout des API JavaScript initiales pour l’accès au runtime.
v22.1.0Ajouté dans : v22.1.0

Le cache de compilation des modules peut être activé soit à l’aide de la méthode module.enableCompileCache() soit de 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 utilisera le cache de code V8 persistant sur disque 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, il suffit de supprimer 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 utilisera 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 désérialisées à partir du cache de code. Il est recommandé de désactiver cette option lors de l’exécution des tests pour générer une couverture précise.

Le cache de compilation des modules activé peut être désactivé par la variable d’environnement NODE_DISABLE_COMPILE_CACHE=1. Cela peut être utile lorsque le cache de compilation conduit à 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 persister 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 se terminer. Ceci est sujet à changement. La méthode module.flushCompileCache() peut être utilisée pour garantir 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 la fin du processus parent.

module.constants.compileCacheStatus

Ajouté dans : v22.8.0

[Stable : 1 - Expérimental]

Stable : 1 Stabilité : 1.1 - Développement actif

Les constantes suivantes sont renvoyées comme champ status dans l’objet renvoyé par module.enableCompileCache() pour indiquer le résultat de la tentative d’activation du cache de compilation de module.

ConstanteDescription
ENABLEDNode.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_ENABLEDLe cache de compilation a déjà été activé auparavant, 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é.
FAILEDNode.js ne parvient pas à activer le cache de compilation. Cela peut être dû à un manque d’autorisation pour 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é.
DISABLEDNode.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

[Stable : 1 - Expérimental]

Stable : 1 Stabilité : 1.1 - Développement actif

  • cacheDir <string> | <undefined> Chemin facultatif pour spécifier le répertoire où le cache de compilation sera stocké/récupéré.
  • Retourne : <Object>
    • status <integer> Une des valeurs module.constants.compileCacheStatus
    • message <string> | <undefined> Si Node.js ne peut pas activer le cache de compilation, ceci contient le message d’erreur. Uniquement défini si status est module.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é. Uniquement défini si status est module.constants.compileCacheStatus.ENABLED ou module.constants.compileCacheStatus.ALREADY_ENABLED.

Activer 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 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 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 de l’objet renvoyé contient le chemin d’accès au répertoire où le cache de compilation est stocké. Le champ status de l’objet renvoyé sera 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, appelez également cette méthode dans les threads worker enfants, ou définissez la valeur process.env.NODE_COMPILE_CACHE sur le répertoire du cache de compilation afin que le comportement puisse être hérité dans les workers 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

[Stable : 1 - Expérimental]

Stable : 1 Stabilité : 1.1 - Développement actif

Vide le cache de compilation des modules accumulé à partir des modules déjà chargés dans l’instance Node.js actuelle sur le disque. Cette opération se termine une fois que toutes les opérations du système de fichiers sont terminées, qu’elles réussissent ou non. En cas d’erreur, l’opération échouera silencieusement, car les manques de cache de compilation ne doivent pas interférer avec le fonctionnement réel de l’application.

module.getCompileCacheDir()

Ajouté dans : v22.8.0

[Stable : 1 - Expérimental]

Stable : 1 Stabilité : 1.1 - Développement actif

Hooks de personnalisation

[Historique]

VersionModifications
v23.5.0Ajout de la prise en charge des hooks synchrones et en thread.
v20.6.0, v18.19.0Ajout du hook initialize pour remplacer globalPreload.
v18.6.0, v16.17.0Ajout de la prise en charge de l’enchaînement des chargeurs.
v16.12.0Suppression de getFormat, getSource, transformSource et globalPreload ; ajout du hook load et du hook getGlobalPreload.
v8.8.0Ajouté dans : v8.8.0

[Stable : 1 - Expérimental]

Stable : 1 Stabilité : 1.2 - Candidate à 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 à l’aide de l’indicateur --import ou --require :

bash
node --import ./register-hooks.js ./my-app.js
node --require ./register-hooks.js ./my-app.js
js
// register-hooks.js
// Ce fichier ne peut être require()-é que s’il ne contient pas d’instruction await de niveau supérieur.
// Utilisez module.register() pour enregistrer les hooks asynchrones dans un thread dédié.
import { register } from 'node:module'
register('./hooks.mjs', import.meta.url)
js
// register-hooks.js
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
// Utilisez module.register() pour enregistrer les hooks asynchrones dans un thread dédié.
register('./hooks.mjs', pathToFileURL(__filename))
js
// 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 */
  },
})
js
// 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 :

bash
node --import some-package/register ./my-app.js
node --require some-package/register ./my-app.js

some-package possède un champ "exports" définissant l’exportation /register pour mapper vers 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 de tous les fichiers de l’application, y compris le point d’entrée de l’application et pour tous les threads de travail par défaut.

Alternativement, register() et registerHooks() peuvent être appelés à partir du point d’entrée, bien qu’import() dynamique doive être utilisé pour tout code ESM qui doit être exécuté après l’enregistrement des hooks.

js
import { register } from 'node:module'

register('http-to-https', import.meta.url)

// Étant donné 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')
js
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')

register('http-to-https', pathToFileURL(__filename))

// Étant donné 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 après 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 à quoi il fait référence via import ou require intégré dans les dépendances CommonJS.

Si import('./my-app.js') avait été un import './my-app.js' statique, l’application aurait déjà été chargée avant l’enregistrement des hooks http-to-https. Ceci est dû à la spécification des modules ES, où les importations statiques sont évaluées à partir des feuilles de l’arbre d’abord, puis 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 require utilisateur créé à l’aide de createRequire() sont pris en charge.

js
import { registerHooks, createRequire } from 'node:module'

registerHooks({
  /* implémentation des hooks synchrones */
})

const require = createRequire(import.meta.url)

// Les hooks synchrones affectent les fonctions import, require() et require utilisateur
// créées via createRequire().
await import('./my-app.js')
require('./my-app-2.js')
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 les fonctions import, require() et require utilisateur
// créées 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 souhaitez pas créer de fichier séparé à cet effet, vous pouvez passer une URL data: à --import :

bash
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 plusieurs fois :

js
// entrypoint.mjs
import { register } from 'node:module'

register('./foo.mjs', import.meta.url)
register('./bar.mjs', import.meta.url)
await import('./my-app.mjs')
js
// 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 selon le principe LIFO (Last-In, First-Out). Si foo.mjs et bar.mjs définissent tous deux un hook resolve, ils seront appelés comme suit (notez l'ordre de droite à gauche) : défaut de Node ← ./foo.mjs ← ./bar.mjs (en commençant par ./bar.mjs, puis ./foo.mjs, puis le défaut de Node.js). Ceci s'applique à 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 créer des hooks dans des langages autres que JavaScript, à condition que les hooks enregistrés précédemment 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 en premier avant que les hooks asynchrones ne commencent à s'exécuter, c'est-à-dire que dans le dernier hook synchrone exécuté, son hook suivant inclut l'invocation des hooks asynchrones.

js
// 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)
js
// 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 mutation 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 passer des données à un hook initialize. Les données passées au hook peuvent inclure des objets transférables comme les ports.

js
import { register } from 'node:module'
import { MessageChannel } from 'node:worker_threads'

// Cet exemple montre comment un canal de message 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],
})
js
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
const { MessageChannel } = require('node:worker_threads')

// Cet exemple montre comment un canal de message 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 s'exécutent sur le même thread que celui où le code de l'application est exécuté. Ils peuvent directement modifier les globales du contexte accédé par le thread principal.

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 appelées par Node.js pour personnaliser le processus de résolution et de chargement des modules. Les fonctions exportées doivent avoir des noms et des signatures spécifiques, et elles doivent être exportées comme exportations nommées.

js
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 retourne le code source à évaluer.
}

Les hooks asynchrones s'exécutent dans un thread séparé, isolé du thread principal où s'exécute le code de l'application. Cela signifie qu'il s'agit d'un realm différent. Le thread des hooks peut être terminé à tout moment par le thread principal, donc ne dépendez pas des 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 hook synchrones. initialize() n’est ni prise en charge ni nécessaire, car l’implémenteur du hook peut simplement exécuter le code d’initialisation directement avant l’appel à module.registerHooks().

js
function resolve(specifier, context, nextResolve) {
  // Prend un spécificateur `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 worker enfants, bien que si les hooks sont enregistrés à l’aide d’un fichier préchargé par --import ou --require, les threads worker enfants peuvent hériter des scripts préchargés via l’héritage process.execArgv. Voir 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() se termine dans le code du module.

Conventions des hooks

Les hooks font partie d’une chaîne, même si cette chaîne ne consiste qu’en un seul hook personnalisé (fourni par l’utilisateur) et le hook par défaut, qui est toujours présent. Les fonctions hook sont imbriquées : chacune doit toujours renvoyer un objet simple, et la chaîne se produit du fait que chaque fonction appelle 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 à empêcher les interruptions involontaires de la chaîne. Renvoyez shortCircuit : true depuis 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 Stabilité : 1.2 - Candidate à la publication

  • data <any> Les données de register(loader, import.meta.url, { data }).

Le hook initialize n’est accepté que 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 fournit un moyen 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 provenant 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 principal de l’application ne reprenne.

Code de personnalisation du module :

js
// path-to-my-hooks.js

export async function initialize({ number, port }) {
  port.postMessage(`increment: ${number + 1}`)
}

Code de l’appelant :

js
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 (application) et les hooks s’exécutant 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],
})
js
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 (application) et les hooks s’exécutant 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]

VersionModifications
v23.5.0Ajout de la prise en charge des hooks synchrones et dans le thread.
v21.0.0, v20.10.0, v18.19.0La 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.0Ajout de la prise en charge de l'enchaînement des hooks resolve. Chaque hook doit appeler nextResolve() ou inclure une propriété shortCircuit définie sur true dans son retour.
v17.1.0, v16.14.0Ajout de la prise en charge des assertions d'importation.

[Stable : 1 - Expérimental]

Stable : 1 Stabilité : 1.2 - Version candidate (version asynchrone) Stabilité : 1.1 - Développement actif (version synchrone)

  • specifier <string>

  • context <Object>

    • conditions <string[]> Conditions d'exportation du fichier package.json pertinent
    • importAttributes <Object> Un objet dont les paires clé-valeur représentent les attributs du module à importer
    • parentURL <string> | <undefined> Le module important celui-ci, ou indéfini s'il s'agit du point d'entrée Node.js
  • nextResolve <Function> Le hook resolve suivant dans la chaîne, ou le hook resolve par défaut de Node.js après le dernier hook resolve fourni par l'utilisateur

  • Retourne : <Object> | <Promise> La version asynchrone prend soit un objet contenant les propriétés suivantes, soit une Promise qui résoudra un tel objet. La version synchrone n'accepte qu'un objet retourné 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'ils sont exclus, l'entrée sera utilisée)
    • shortCircuit <undefined> | <boolean> Un signal indiquant que ce hook a l'intention de terminer la chaîne de hooks resolve. Défaut : false
    • url <string> L'URL absolue vers laquelle cette entrée est résolue

La chaîne de hooks resolve est responsable d'indiquer à Node.js où trouver et comment mettre en cache une instruction ou une expression import donnée, ou un appel require. Elle peut éventuellement retourner un format (tel que 'module') comme indice pour le hook load. Si un format est spécifié, le hook load est en fin de compte responsable de fournir la valeur finale du format (et il est libre d'ignorer l'indice fourni par resolve) ; si resolve fournit un format, un hook load personnalisé est nécessaire, ne serait-ce 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 responsable du retour d'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 des packages pour cette requête de résolution. Elles peuvent être utilisées pour rechercher des mappages conditionnels ailleurs ou pour modifier la liste lors de l'appel de la logique de résolution par défaut.

Les conditions d'exportation des packages actuelles sont toujours dans le tableau context.conditions passé dans le hook. Pour garantir le comportement de résolution du spécificateur de module Node.js par défaut lors de l'appel de defaultResolve, le tableau context.conditions qui lui est passé doit inclure tous les éléments du tableau context.conditions initialement passé dans le hook resolve.

js
// Version asynchrone acceptée par module.register().
export async function resolve(specifier, context, nextResolve) {
  const { parentURL = null } = context

  if (Math.random() > 0.5) {
    // Une condition.
    // Pour certains ou tous les spécificateurs, effectuez une logique personnalisée pour la résolution.
    // Retournez 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.
    // Lorsque vous appelez `defaultResolve`, les arguments peuvent être modifiés. Dans ce
    // cas, cela ajoute une autre valeur pour la correspondance des exportations conditionnelles.
    return nextResolve(specifier, {
      ...context,
      conditions: [...context.conditions, 'another-condition'],
    })
  }

  // Déléguer au hook suivant dans la chaîne, qui serait le
  // hook resolve par défaut de Node.js s'il s'agit du dernier chargeur spécifié par l'utilisateur.
  return nextResolve(specifier)
}
js
// Version synchrone acceptée par module.registerHooks().
function resolve(specifier, context, nextResolve) {
  // Semblable à la fonction resolve() asynchrone ci-dessus, car celle-ci n'a pas
  // de logique asynchrone.
}

load(url, context, nextLoad)

[Historique]

VersionModifications
v23.5.0Ajout de la prise en charge des versions synchrones et en thread.
v20.6.0Ajout de la prise en charge de source avec le format commonjs.
v18.6.0, v16.17.0Ajout 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 - Candidate à la sortie (version asynchrone) Stabilité : 1.1 - Développement actif (version synchrone)

  • url <string> L'URL renvoyée par la chaîne resolve

  • context <Object>

  • nextLoad <Function> Le hook load suivant dans la chaîne, ou le hook load par défaut de Node.js après le dernier hook load fourni par l'utilisateur

  • Retourne : <Object> | <Promise> La version asynchrone prend soit un objet contenant les propriétés suivantes, soit une Promise qui résoudra en un tel objet. La version synchrone n'accepte qu'un objet retourné de manière synchrone.

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 :

formatDescriptionTypes acceptables pour source retournés par load
'builtin'Charger un module intégré Node.jsNon applicable
'commonjs'Charger un module Node.js CommonJS{ 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é (principal) Node.js.

Avertissement concernant le hook load asynchrone

Lors de l'utilisation du hook load asynchrone, l'omission ou la fourniture d'une source pour 'commonjs' a des effets très différents :

  • Lorsqu'une source est fournie, tous les appels require de ce module seront traités par le chargeur ESM avec les hooks resolve et load enregistrés ; tous les appels require.resolve de ce module seront traités par le chargeur ESM avec les hooks resolve enregistrés ; seul un sous-ensemble de l'API CommonJS sera disponible (par exemple, pas de require.extensions, pas de require.cache, pas de require.resolve.paths) et le monkey-patching sur le chargeur de module CommonJS ne s'appliquera pas.
  • Si source est indéfini ou null, il sera géré par le chargeur de module CommonJS et les appels require/require.resolve ne passeront pas par les hooks enregistrés. Ce comportement pour une source nulle est temporaire — à l'avenir, une source nulle ne sera pas prise en charge.

Ces avertissements ne s'appliquent pas au hook load synchrone, 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 de 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 des raisons de compatibilité descendante. Voici un exemple de hook qui opterait pour l'utilisation du comportement non par défaut :

js
import { readFile } from 'node:fs/promises'

// Version asynchrone acceptée par module.register(). Cette correction 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 load synchrone, auquel cas la source renvoyée contient le code source chargé par le hook suivant, quel que soit le format du module.

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 chaîne de caractères à 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 la lecture de fichiers sur le disque. Il pourrait également être utilisé pour mapper un format non reconnu sur un format pris en charge, par exemple yaml sur module.

js
// Version asynchrone acceptée par module.register().
export async function load(url, context, nextLoad) {
  const { format } = context

  if (Math.random() > 0.5) {
    // Une condition
    /*
      Pour certaines ou toutes les URL, 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éléguer au hook suivant de la chaîne.
  return nextLoad(url)
}
js
// Version synchrone acceptée par module.registerHooks().
function load(url, context, nextLoad) {
  // Semblable au load() asynchrone ci-dessus, car celui-ci n'a 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 hooks de personnalisation des modules peuvent être utilisés ensemble pour réaliser des personnalisations étendues des comportements de chargement et d'évaluation du code Node.js.

Importation depuis HTTPS

Le hook ci-dessous enregistre des hooks pour activer une prise en charge rudimentaire de ces spécificateurs. Bien que cela puisse sembler une amélioration significative des fonctionnalités principales de Node.js, l'utilisation réelle de ces hooks 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 pas de sécurité.

js
// https-hooks.mjs
import { get } from 'node:https'

export function load(url, context, nextLoad) {
  // Pour que JavaScript puisse être chargé sur le réseau, nous devons le récupérer et
  // le renvoyer.
  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 JavaScript fourni par le réseau est un 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)
}
js
// main.mjs
import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js'

console.log(VERSION)

Avec le module de hooks 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 hook load.

Ceci est moins performant que la transpilation des fichiers source avant l'exécution de Node.js ; les hooks de transpilation ne doivent être utilisés qu'à des fins de développement et de test.

Version asynchrone
js
// 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 des modules CommonJS ou 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 URL.
  return nextLoad(url)
}

async function getPackageType(url) {
  // `url` n'est qu'un chemin de fichier lors de la première itération lorsqu'il est passé
  // l'URL résolue du hook load()
  // un chemin de fichier réel de load() contiendra une extension de fichier car elle est
  // requise par la spécification
  // cette simple vérification booléenne 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)
  const isFilePath = !!extname(url)
  // Si c'est 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 éventuellement 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 suivant
  // Si à la racine, arrêter et retourner false
  return dir.length > 1 && getPackageType(resolvePath(dir, '..'))
}
Version synchrone
js
// 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

coffee
# 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}"
coffee
# scream.coffee {#screamcoffee}
export scream = (str) -> str.toUpperCase()

Avec les modules 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 entraîne la conversion de main.coffee en JavaScript après le chargement de son code source à partir du disque, mais avant son exécution par Node.js ; et ainsi de suite pour tous les fichiers .coffee, .litcoffee ou .coffee.md référencés via des instructions import de tout fichier chargé.

Cartes d'importation

Les deux exemples précédents définissaient des hooks load. Ceci est un exemple de hook resolve. Ce hook lit un fichier import-map.json qui définit les spécificateurs à remplacer par d'autres URL (il s'agit d'une implémentation très simpliste d'un petit sous-ensemble de la spécification des "cartes d'importation").

Version asynchrone
js
// 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
js
// 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 :

js
// main.js
import 'a-module'
json
// import-map.json
{
  "imports": {
    "a-module": "./some-module.js"
  }
}
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!.

Support des maps de sources v3

Ajouté dans : v13.7.0, v12.17.0

[Stable : 1 - Expérimental]

Stable : 1 Stabilité : 1 - Expérimental

Aides pour interagir avec le cache de map de sources. Ce cache est rempli lorsque l'analyse de la map de sources est activée et que des directives d'inclusion de map de sources sont trouvées dans le pied de page d'un module.

Pour activer l'analyse de la map de sources, Node.js doit être exécuté avec l'indicateur --enable-source-maps, ou avec la couverture de code activée en définissant NODE_V8_COVERAGE=dir.

js
// module.mjs
// Dans un module ECMAScript
import { findSourceMap, SourceMap } from 'node:module'
js
// module.cjs
// Dans un module CommonJS
const { findSourceMap, SourceMap } = require('node:module')

module.findSourceMap(path)

Ajouté dans : v13.7.0, v12.17.0

path est le chemin résolu du fichier pour lequel une source map correspondante doit être récupérée.

Classe : module.SourceMap

Ajouté dans : v13.7.0, v12.17.0

new SourceMap(payload[, { lineLengths }]) {#new-sourcemappayload-{-linelengths-}}

Crée une nouvelle instance sourceMap.

payload est un objet avec des clés correspondant au format Source map v3 :

lineLengths est un tableau optionnel de la longueur de chaque ligne dans le code généré.

sourceMap.payload

Accesseur pour la charge utile utilisée pour construire l'instance SourceMap.

sourceMap.findEntry(lineOffset, columnOffset)

  • lineOffset <number> Le décalage du numéro de ligne (à partir de zéro) dans la source générée
  • columnOffset <number> Le décalage du numéro de colonne (à partir de zéro) dans la source générée
  • Retourne : <Object>

Étant donné un décalage de ligne et un décalage de colonne dans le fichier source généré, retourne un objet représentant la plage SourceMap dans le fichier original si trouvé, ou un objet vide sinon.

L'objet retourné contient les clés suivantes :

  • generatedLine: <number> Le décalage de ligne du début de la plage dans la source générée
  • generatedColumn: <number> Le décalage de colonne du début de la plage dans la source générée
  • originalSource: <string> Le nom du fichier de la source originale, tel que rapporté dans la SourceMap
  • originalLine: <number> Le décalage de ligne du début de la plage dans la source originale
  • originalColumn: <number> Le décalage de colonne du début de la plage dans la source originale
  • name: <string>

La valeur retournée représente la plage brute telle qu'elle apparaît dans la SourceMap, basée sur des décalages à partir de zéro, et pas sur des numéros de ligne et de colonne à partir de 1 comme ils apparaissent dans les messages d'erreur et les objets CallSite.

Pour obtenir les numéros de ligne et de colonne à partir de 1 correspondants à partir d'un lineNumber et d'un columnNumber tels qu'ils sont rapportés par les piles d'erreur et les objets CallSite, utilisez sourceMap.findOrigin(lineNumber, columnNumber)

sourceMap.findOrigin(lineNumber, columnNumber)

  • lineNumber <number> Le numéro de ligne (indexé à partir de 1) du site d'appel dans la source générée
  • columnNumber <number> Le numéro de colonne (indexé à partir de 1) du site d'appel dans la source générée
  • Retourne : <Object>

Étant donné un lineNumber et un columnNumber (indexés à partir de 1) provenant d'un site d'appel dans la source générée, trouver l'emplacement du site 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 un nom a été fourni
  • fileName: <string> Le nom du fichier de la source originale, tel que rapporté dans la SourceMap
  • lineNumber: <number> Le lineNumber (indexé à partir de 1) du site d'appel correspondant dans la source originale
  • columnNumber: <number> Le columnNumber (indexé à partir de 1) du site d'appel correspondant dans la source originale