Skip to content

Module: node:module API

Hinzugefügt in: v0.3.7

Das Module-Objekt

Stellt allgemeine Dienstprogrammmethoden bereit, wenn mit Instanzen von Module interagiert wird, der Variable module, die häufig in CommonJS-Modulen zu sehen ist. Zugriff über import 'node:module' oder require('node:module').

module.builtinModules

[Verlauf]

VersionÄnderungen
v23.5.0Die Liste enthält jetzt auch nur Präfix-Module.
v9.3.0, v8.10.0, v6.13.0Hinzugefügt in: v9.3.0, v8.10.0, v6.13.0

Eine Liste der Namen aller von Node.js bereitgestellten Module. Kann verwendet werden, um zu überprüfen, ob ein Modul von einem Drittanbieter gewartet wird oder nicht.

module in diesem Kontext ist nicht dasselbe Objekt, das vom Modul-Wrapper bereitgestellt wird. Um darauf zuzugreifen, benötigen Sie das Module-Modul:

js
// module.mjs
// In einem ECMAScript-Modul
import { builtinModules as builtin } from 'node:module'
js
// module.cjs
// In einem CommonJS-Modul
const builtin = require('node:module').builtinModules

module.createRequire(filename)

Hinzugefügt in: v12.2.0

  • filename <string> | <URL> Dateiname, der zum Erstellen der require-Funktion verwendet werden soll. Muss ein File-URL-Objekt, eine File-URL-Zeichenkette oder eine absolute Pfadzeichenkette sein.
  • Gibt zurück: <require> Require-Funktion
js
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)

// sibling-module.js ist ein CommonJS-Modul.
const siblingModule = require('./sibling-module')

module.findPackageJSON(specifier[, base])

Hinzugefügt in: v23.2.0

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.1 - Aktive Entwicklung

  • specifier <string> | <URL> Der Spezifikator für das Modul, dessen package.json abgerufen werden soll. Beim Übergeben eines nackten Spezifikators wird die package.json an der Wurzel des Pakets zurückgegeben. Beim Übergeben eines relativen Spezifikators oder eines absoluten Spezifikators wird die nächstgelegene übergeordnete package.json zurückgegeben.
  • base <string> | <URL> Der absolute Speicherort (file: URL-Zeichenkette oder FS-Pfad) des enthaltenden Moduls. Verwenden Sie für CJS __filename (nicht __dirname!); für ESM verwenden Sie import.meta.url. Sie müssen es nicht übergeben, wenn specifier ein absoluter Spezifikator ist.
  • Gibt zurück: <string> | <undefined> Ein Pfad, wenn die package.json gefunden wird. Wenn startLocation ein Paket ist, die root package.json des Pakets; wenn relativ oder aufgelöst, die nächstgelegene package.json zu 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'
// Gleiches Ergebnis bei Übergabe eines absoluten Spezifikators:
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'
// Bei Übergabe eines absoluten Spezifikators erhalten Sie möglicherweise ein anderes Ergebnis, wenn sich das
// aufgelöste Modul in einem Unterordner befindet, der verschachtelte `package.json` enthält.
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'
// Gleiches Ergebnis bei Übergabe eines absoluten Spezifikators:
findPackageJSON(pathToFileURL(path.join(__dirname, '..')))

findPackageJSON('some-package', __filename)
// '/path/to/project/packages/bar/node_modules/some-package/package.json'
// Bei Übergabe eines absoluten Spezifikators erhalten Sie möglicherweise ein anderes Ergebnis, wenn sich das
// aufgelöste Modul in einem Unterordner befindet, der verschachtelte `package.json` enthält.
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)

Hinzugefügt in: v18.6.0, v16.17.0

  • moduleName <string> Name des Moduls
  • Rückgabewert: <boolean> Gibt true zurück, wenn das Modul eingebaut ist, andernfalls false.
js
import { isBuiltin } from 'node:module'
isBuiltin('node:fs') // true
isBuiltin('fs') // true
isBuiltin('wss') // false

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

[Verlauf]

VersionÄnderungen
v20.8.0, v18.19.0Unterstützung für WHATWG URL-Instanzen hinzugefügt.
v20.6.0, v18.19.0Hinzugefügt in: v20.6.0, v18.19.0

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.2 - Release Candidate

  • specifier <string> | <URL> Anpassungshandler, die registriert werden sollen; dies sollte die gleiche Zeichenkette sein, die an import() übergeben würde, außer dass sie, wenn sie relativ ist, relativ zu parentURL aufgelöst wird.
  • parentURL <string> | <URL> Wenn Sie specifier relativ zu einer Basis-URL, wie z. B. import.meta.url, auflösen möchten, können Sie diese URL hier übergeben. Standardwert: 'data:'
  • options <Object>
    • parentURL <string> | <URL> Wenn Sie specifier relativ zu einer Basis-URL, wie z. B. import.meta.url, auflösen möchten, können Sie diese URL hier übergeben. Diese Eigenschaft wird ignoriert, wenn parentURL als zweites Argument angegeben wird. Standardwert: 'data:'
    • data <any> Ein beliebiger, klonbarer JavaScript-Wert, der an den initialize-Hook übergeben werden soll.
    • transferList <Object[]> übertragbare Objekte, die an den initialize-Hook übergeben werden sollen.

Registrieren Sie ein Modul, das Hooks exportiert, die das Modul-Auflösungs- und Ladeverhalten von Node.js anpassen. Siehe Anpassungshandler.

module.registerHooks(options)

Hinzugefügt in: v23.5.0

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.1 - Aktive Entwicklung

Registriert Hooks, die die Node.js Modul-Auflösung und das Ladeverhalten anpassen. Siehe Anpassungs-Hooks.

module.stripTypeScriptTypes(code[, options])

Hinzugefügt in: v23.2.0

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.1 - Aktive Entwicklung

  • code <string> Der Code, dem Typannotationen entzogen werden sollen.

  • options <Object>

    • mode <string> Standardwert: 'strip'. Mögliche Werte sind:

    • 'strip' Entfernt nur Typannotationen, ohne die Transformation von TypeScript-Features durchzuführen.

    • 'transform' Entfernt Typannotationen und transformiert TypeScript-Features nach JavaScript.

    • sourceMap <boolean> Standardwert: false. Nur wenn mode 'transform' ist, wird bei true eine Source Map für den transformierten Code generiert.

    • sourceUrl <string> Gibt die in der Source Map verwendete Quell-URL an.

  • Rückgabewert: <string> Der Code ohne Typannotationen. module.stripTypeScriptTypes() entfernt Typannotationen aus TypeScript-Code. Er kann verwendet werden, um Typannotationen aus TypeScript-Code zu entfernen, bevor er mit vm.runInContext() oder vm.compileFunction() ausgeführt wird. Standardmäßig wird ein Fehler ausgegeben, wenn der Code TypeScript-Features enthält, die eine Transformation benötigen, wie z. B. Enums; siehe Typ-Stripping für weitere Informationen. Wenn mode 'transform' ist, werden auch TypeScript-Features in JavaScript transformiert; siehe TypeScript-Features transformieren für weitere Informationen. Wenn mode 'strip' ist, werden keine Source Maps generiert, da die Positionen beibehalten werden. Wenn sourceMap angegeben wird, wird bei mode 'strip' ein Fehler ausgegeben.

WARNUNG: Die Ausgabe dieser Funktion sollte aufgrund von Änderungen im TypeScript-Parser nicht als stabil über Node.js-Versionen hinweg betrachtet werden.

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

Wenn sourceUrl angegeben wird, wird er als Kommentar am Ende der Ausgabe angehängt:

js
import { stripTypeScriptTypes } from 'node:module'
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' })
console.log(strippedCode)
// Gibt aus: 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)
// Gibt aus: const a         = 1\n\n//# sourceURL=source.ts;

Wenn mode 'transform' ist, wird der Code in JavaScript transformiert:

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

module.syncBuiltinESMExports()

Hinzugefügt in: v12.12.0

Die Methode module.syncBuiltinESMExports() aktualisiert alle aktiven Bindungen für integrierte ES-Module, um den Eigenschaften der CommonJS-Exporte zu entsprechen. Sie fügt keine exportierten Namen zu den ES-Modulen hinzu und entfernt auch keine.

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 => {
  // Es synchronisiert die bestehende readFile-Eigenschaft mit dem neuen Wert
  assert.strictEqual(esmFS.readFile, newAPI)
  // readFileSync wurde aus dem benötigten fs gelöscht
  assert.strictEqual('readFileSync' in fs, false)
  // syncBuiltinESMExports() entfernt readFileSync nicht aus esmFS
  assert.strictEqual('readFileSync' in esmFS, true)
  // syncBuiltinESMExports() fügt keine Namen hinzu
  assert.strictEqual(esmFS.newAPI, undefined)
})

Modul-Kompilierungs-Cache

[Verlauf]

VersionÄnderungen
v22.8.0Hinzufügen initialer JavaScript-APIs für den Laufzeitzugriff.
v22.1.0Hinzugefügt in: v22.1.0

Der Modul-Kompilierungs-Cache kann entweder mit der Methode module.enableCompileCache() oder der Umgebungsvariablen NODE_COMPILE_CACHE=dir aktiviert werden. Nach der Aktivierung verwendet Node.js bei der Kompilierung eines CommonJS- oder ECMAScript-Moduls den auf der Festplatte persistenten V8-Code-Cache im angegebenen Verzeichnis, um die Kompilierung zu beschleunigen. Dies kann das erste Laden eines Modulgraphen verlangsamen, aber nachfolgende Ladungen desselben Modulgraphen können eine erhebliche Beschleunigung erfahren, wenn sich der Inhalt der Module nicht ändert.

Um den generierten Kompilierungs-Cache auf der Festplatte zu bereinigen, entfernen Sie einfach das Cache-Verzeichnis. Das Cache-Verzeichnis wird beim nächsten Gebrauch desselben Verzeichnisses für die Kompilierungs-Cache-Speicherung neu erstellt. Um zu vermeiden, dass die Festplatte mit veraltetem Cache gefüllt wird, empfiehlt es sich, ein Verzeichnis unter os.tmpdir() zu verwenden. Wenn der Kompilierungs-Cache durch einen Aufruf von module.enableCompileCache() ohne Angabe des Verzeichnisses aktiviert wird, verwendet Node.js die Umgebungsvariable NODE_COMPILE_CACHE=dir, falls diese gesetzt ist, oder standardmäßig path.join(os.tmpdir(), 'node-compile-cache'). Um das von einer laufenden Node.js-Instanz verwendete Kompilierungs-Cache-Verzeichnis zu finden, verwenden Sie module.getCompileCacheDir().

Derzeit kann bei Verwendung des Kompilierungs-Caches mit V8 JavaScript Code Coverage die von V8 erfasste Abdeckung in Funktionen, die aus dem Code-Cache deserialisiert werden, weniger präzise sein. Es wird empfohlen, dies beim Ausführen von Tests zu deaktivieren, um eine präzise Abdeckung zu generieren.

Der aktivierte Modul-Kompilierungs-Cache kann durch die Umgebungsvariable NODE_DISABLE_COMPILE_CACHE=1 deaktiviert werden. Dies kann nützlich sein, wenn der Kompilierungs-Cache zu unerwarteten oder unerwünschten Verhaltensweisen führt (z. B. weniger präzise Testabdeckung).

Ein von einer Node.js-Version generierter Kompilierungs-Cache kann nicht von einer anderen Node.js-Version wiederverwendet werden. Caches, die von verschiedenen Node.js-Versionen generiert wurden, werden separat gespeichert, wenn dasselbe Basisverzeichnis zum Persistentmachen des Caches verwendet wird, sodass sie koexistieren können.

Derzeit wird, wenn der Kompilierungs-Cache aktiviert ist und ein Modul neu geladen wird, der Code-Cache sofort aus dem kompilierten Code generiert, aber erst beim Beenden der Node.js-Instanz auf die Festplatte geschrieben. Dies kann sich ändern. Die Methode module.flushCompileCache() kann verwendet werden, um sicherzustellen, dass der angesammelte Code-Cache auf die Festplatte geschrieben wird, falls die Anwendung andere Node.js-Instanzen erzeugen und diese den Cache lange vor dem Beenden des übergeordneten Prozesses gemeinsam nutzen lassen möchte.

module.constants.compileCacheStatus

Hinzugefügt in: v22.8.0

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.1 - Aktive Entwicklung

Die folgenden Konstanten werden als status-Feld in dem von module.enableCompileCache() zurückgegebenen Objekt zurückgegeben, um das Ergebnis des Versuchs, den Module-Compile-Cache zu aktivieren, anzugeben.

KonstanteBeschreibung
ENABLEDNode.js hat den Compile-Cache erfolgreich aktiviert. Das Verzeichnis, in dem der Compile-Cache gespeichert wird, wird im Feld directory des zurückgegebenen Objekts zurückgegeben.
ALREADY_ENABLEDDer Compile-Cache wurde bereits zuvor aktiviert, entweder durch einen vorherigen Aufruf von module.enableCompileCache() oder durch die Umgebungsvariable NODE_COMPILE_CACHE=dir. Das Verzeichnis, in dem der Compile-Cache gespeichert wird, wird im Feld directory des zurückgegebenen Objekts zurückgegeben.
FAILEDNode.js kann den Compile-Cache nicht aktivieren. Dies kann durch fehlende Berechtigung zur Verwendung des angegebenen Verzeichnisses oder verschiedene Arten von Dateisystemfehlern verursacht werden. Die Details des Fehlers werden im Feld message des zurückgegebenen Objekts zurückgegeben.
DISABLEDNode.js kann den Compile-Cache nicht aktivieren, da die Umgebungsvariable NODE_DISABLE_COMPILE_CACHE=1 gesetzt wurde.

module.enableCompileCache([cacheDir])

Hinzugefügt in: v22.8.0

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.1 - Aktive Entwicklung

  • cacheDir <string> | <undefined> Optionaler Pfad zur Angabe des Verzeichnisses, in dem der Compile-Cache gespeichert/abgerufen wird.
  • Rückgabewert: <Object>
    • status <integer> Einer der module.constants.compileCacheStatus
    • message <string> | <undefined> Wenn Node.js den Compile-Cache nicht aktivieren kann, enthält dies die Fehlermeldung. Nur gesetzt, wenn status module.constants.compileCacheStatus.FAILED ist.
    • directory <string> | <undefined> Wenn der Compile-Cache aktiviert ist, enthält dies das Verzeichnis, in dem der Compile-Cache gespeichert ist. Nur gesetzt, wenn status module.constants.compileCacheStatus.ENABLED oder module.constants.compileCacheStatus.ALREADY_ENABLED ist.

Aktiviert den Module-Compile-Cache in der aktuellen Node.js-Instanz.

Wenn cacheDir nicht angegeben ist, verwendet Node.js entweder das durch die Umgebungsvariable NODE_COMPILE_CACHE=dir angegebene Verzeichnis, falls es gesetzt ist, oder verwendet andernfalls path.join(os.tmpdir(), 'node-compile-cache'). Für allgemeine Anwendungsfälle wird empfohlen, module.enableCompileCache() ohne Angabe von cacheDir aufzurufen, sodass das Verzeichnis bei Bedarf durch die Umgebungsvariable NODE_COMPILE_CACHE überschrieben werden kann.

Da der Compile-Cache eine stille Optimierung sein soll, die nicht erforderlich ist, damit die Anwendung funktionsfähig ist, ist diese Methode so konzipiert, dass keine Ausnahme ausgelöst wird, wenn der Compile-Cache nicht aktiviert werden kann. Stattdessen wird ein Objekt zurückgegeben, das eine Fehlermeldung im Feld message zur Unterstützung der Fehlersuche enthält. Wenn der Compile-Cache erfolgreich aktiviert wird, enthält das Feld directory im zurückgegebenen Objekt den Pfad zu dem Verzeichnis, in dem der Compile-Cache gespeichert ist. Das Feld status im zurückgegebenen Objekt ist einer der module.constants.compileCacheStatus-Werte, um das Ergebnis des Versuchs, den Module-Compile-Cache zu aktivieren, anzugeben.

Diese Methode wirkt sich nur auf die aktuelle Node.js-Instanz aus. Um sie in untergeordneten Worker-Threads zu aktivieren, rufen Sie diese Methode auch in untergeordneten Worker-Threads auf oder setzen Sie den Wert process.env.NODE_COMPILE_CACHE auf das Compile-Cache-Verzeichnis, damit das Verhalten an die untergeordneten Worker vererbt werden kann. Das Verzeichnis kann entweder aus dem Feld directory abgerufen werden, das von dieser Methode zurückgegeben wird, oder mit module.getCompileCacheDir().

module.flushCompileCache()

Hinzugefügt in: v23.0.0

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.1 - Aktive Entwicklung

Leert den Module-Kompilier-Cache, der sich aus bereits in der aktuellen Node.js-Instanz geladenen Modulen angesammelt hat, auf die Festplatte. Die Funktion gibt erst zurück, nachdem alle Schreibvorgänge auf dem Dateisystem abgeschlossen sind, unabhängig davon, ob sie erfolgreich waren oder nicht. Bei Fehlern wird dies stillschweigend ignoriert, da fehlende Einträge im Kompilier-Cache den eigentlichen Betrieb der Anwendung nicht beeinträchtigen sollten.

module.getCompileCacheDir()

Hinzugefügt in: v22.8.0

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.1 - Aktive Entwicklung

Anpassungshooks

[Verlauf]

VersionÄnderungen
v23.5.0Unterstützung für synchrone und In-Thread-Hooks hinzugefügt.
v20.6.0, v18.19.0initialize-Hook hinzugefügt, um globalPreload zu ersetzen.
v18.6.0, v16.17.0Unterstützung für das Verketten von Loadern hinzugefügt.
v16.12.0getFormat, getSource, transformSource und globalPreload entfernt; load-Hook und getGlobalPreload-Hook hinzugefügt.
v8.8.0Hinzugefügt in: v8.8.0

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.2 - Release Candidate (asynchrone Version) Stabilität: 1.1 - Aktive Entwicklung (synchrone Version)

Es werden derzeit zwei Arten von Modulanpassungshooks unterstützt:

Aktivieren

Modulauflösung und -laden können angepasst werden durch:

Die Hooks können registriert werden, bevor der Anwendungscode ausgeführt wird, indem das Flag --import oder --require verwendet wird:

bash
node --import ./register-hooks.js ./my-app.js
node --require ./register-hooks.js ./my-app.js
js
// register-hooks.js
// Diese Datei kann nur require()-ed werden, wenn sie kein Top-Level-await enthält.
// Verwenden Sie module.register(), um asynchrone Hooks in einem dedizierten Thread zu registrieren.
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')
// Verwenden Sie module.register(), um asynchrone Hooks in einem dedizierten Thread zu registrieren.
register('./hooks.mjs', pathToFileURL(__filename))
js
// Verwenden Sie module.registerHooks(), um synchrone Hooks im Hauptthread zu registrieren.
import { registerHooks } from 'node:module'
registerHooks({
  resolve(specifier, context, nextResolve) {
    /* Implementierung */
  },
  load(url, context, nextLoad) {
    /* Implementierung */
  },
})
js
// Verwenden Sie module.registerHooks(), um synchrone Hooks im Hauptthread zu registrieren.
const { registerHooks } = require('node:module')
registerHooks({
  resolve(specifier, context, nextResolve) {
    /* Implementierung */
  },
  load(url, context, nextLoad) {
    /* Implementierung */
  },
})

Die an --import oder --require übergebene Datei kann auch ein Export aus einer Abhängigkeit sein:

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

Wobei some-package ein Feld "exports" hat, das den /register-Export auf eine Datei abbildet, die register() aufruft, wie das folgende Beispiel register-hooks.js.

Die Verwendung von --import oder --require stellt sicher, dass die Hooks registriert werden, bevor irgendwelche Anwendungsdateien importiert werden, einschließlich des Einstiegspunkts der Anwendung und standardmäßig auch für alle Worker-Threads.

Alternativ können register() und registerHooks() vom Einstiegspunkt aus aufgerufen werden, obwohl dynamic import() für jeden ESM-Code verwendet werden muss, der nach der Registrierung der Hooks ausgeführt werden soll.

js
import { register } from 'node:module'

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

// Da dies ein dynamisches `import()` ist, werden die `http-to-https`-Hooks ausgeführt,
// um `./my-app.js` und alle anderen Dateien zu verarbeiten, die es importiert oder benötigt.
await import('./my-app.js')
js
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')

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

// Da dies ein dynamisches `import()` ist, werden die `http-to-https`-Hooks ausgeführt,
// um `./my-app.js` und alle anderen Dateien zu verarbeiten, die es importiert oder benötigt.
import('./my-app.js')

Anpassungshooks werden für alle Module ausgeführt, die später als die Registrierung geladen werden und die Module, auf die sie über import und das integrierte require verweisen. Die require-Funktion, die von Benutzern mit module.createRequire() erstellt wurde, kann nur durch die synchronen Hooks angepasst werden.

In diesem Beispiel registrieren wir die http-to-https-Hooks, aber sie sind nur für später importierte Module verfügbar – in diesem Fall my-app.js und alles, worauf es über import oder das integrierte require in CommonJS-Abhängigkeiten verweist.

Wenn import('./my-app.js') stattdessen ein statisches import './my-app.js' gewesen wäre, wäre die App bereits geladen worden, bevor die http-to-https-Hooks registriert wurden. Dies liegt an der ES-Modul-Spezifikation, bei der statische Importe zuerst von den Blättern des Baums ausgewertet werden und dann zurück zum Stamm. Es kann statische Importe innerhalb von my-app.js geben, die erst ausgewertet werden, wenn my-app.js dynamisch importiert wird.

Wenn synchrone Hooks verwendet werden, werden sowohl import, require als auch die vom Benutzer erstellte require-Funktion mit createRequire() unterstützt.

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

registerHooks({
  /* Implementierung synchroner Hooks */
})

const require = createRequire(import.meta.url)

// Die synchronen Hooks beeinflussen import, require() und die vom Benutzer erstellte require()-Funktion
// über createRequire().
await import('./my-app.js')
require('./my-app-2.js')
js
const { register, registerHooks } = require('node:module')
const { pathToFileURL } = require('node:url')

registerHooks({
  /* Implementierung synchroner Hooks */
})

const userRequire = createRequire(__filename)

// Die synchronen Hooks beeinflussen import, require() und die vom Benutzer erstellte require()-Funktion
// über createRequire().
import('./my-app.js')
require('./my-app-2.js')
userRequire('./my-app-3.js')

Wenn Sie schließlich nur Hooks registrieren möchten, bevor Ihre App ausgeführt wird, und dafür keine separate Datei erstellen möchten, können Sie eine data:-URL an --import übergeben:

bash
node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("http-to-https", pathToFileURL("./"));' ./my-app.js

Verkettung

Es ist möglich, register mehrmals aufzurufen:

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')

In diesem Beispiel bilden die registrierten Hooks Ketten. Diese Ketten werden Last-in, First-out (LIFO) ausgeführt. Wenn sowohl foo.mjs als auch bar.mjs einen resolve-Hook definieren, werden sie wie folgt aufgerufen (beachten Sie die Reihenfolge von rechts nach links): Node.js-Standard ← ./foo.mjs./bar.mjs (beginnend mit ./bar.mjs, dann ./foo.mjs, dann dem Node.js-Standard). Das Gleiche gilt für alle anderen Hooks.

Die registrierten Hooks beeinflussen auch register selbst. In diesem Beispiel wird bar.mjs über die von foo.mjs registrierten Hooks aufgelöst und geladen (da die Hooks von foo bereits der Kette hinzugefügt wurden). Dies ermöglicht Dinge wie das Schreiben von Hooks in Nicht-JavaScript-Sprachen, solange zuvor registrierte Hooks in JavaScript transpiliert werden.

Die register-Methode kann nicht aus dem Modul aufgerufen werden, das die Hooks definiert.

Die Verkettung von registerHooks funktioniert ähnlich. Wenn synchrone und asynchrone Hooks gemischt werden, werden die synchronen Hooks immer zuerst ausgeführt, bevor die asynchronen Hooks mit der Ausführung beginnen, d. h. im letzten ausgeführten synchronen Hook beinhaltet der nächste Hook den Aufruf der asynchronen Hooks.

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

const hook1 = {
  /* Implementierung der Hooks */
}
const hook2 = {
  /* Implementierung der Hooks */
}
// hook2 wird vor hook1 ausgeführt.
registerHooks(hook1)
registerHooks(hook2)
js
// entrypoint.cjs
const { registerHooks } = require('node:module')

const hook1 = {
  /* Implementierung der Hooks */
}
const hook2 = {
  /* Implementierung der Hooks */
}
// hook2 wird vor hook1 ausgeführt.
registerHooks(hook1)
registerHooks(hook2)

Kommunikation mit Modul-Anpassungs-Hooks

Asynchrone Hooks werden in einem dedizierten Thread ausgeführt, getrennt vom Hauptthread, der den Anwendungscode ausführt. Das bedeutet, dass die Veränderung globaler Variablen die anderen Threads nicht beeinflusst und Nachrichtenkanäle verwendet werden müssen, um zwischen den Threads zu kommunizieren.

Die Methode register kann verwendet werden, um Daten an einen initialize-Hook zu übergeben. Die an den Hook übergebenen Daten können übertragbare Objekte wie Ports enthalten.

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

// Dieses Beispiel zeigt, wie ein Nachrichtenkanal verwendet werden kann, um
// mit den Hooks zu kommunizieren, indem `port2` an die Hooks gesendet wird.
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')

// Dieses Beispiel zeigt, wie ein Nachrichtenkanal verwendet werden kann, um
// mit den Hooks zu kommunizieren, indem `port2` an die Hooks gesendet wird.
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],
})

Synchrone Modul-Hooks werden im selben Thread ausgeführt, in dem der Anwendungscode ausgeführt wird. Sie können die globalen Variablen des vom Hauptthread zugreifbaren Kontexts direkt verändern.

Hooks

Asynchrone Hooks, die von module.register() akzeptiert werden

Die Methode register kann verwendet werden, um ein Modul zu registrieren, das einen Satz von Hooks exportiert. Die Hooks sind Funktionen, die von Node.js aufgerufen werden, um die Modul-Auflösung und den Ladevorgang anzupassen. Die exportierten Funktionen müssen bestimmte Namen und Signaturen haben und müssen als benannte Exporte exportiert werden.

js
export async function initialize({ number, port }) {
  // Empfängt Daten von `register`.
}

export async function resolve(specifier, context, nextResolve) {
  // Nimmt einen `import` oder `require`-Specifier entgegen und löst ihn zu einer URL auf.
}

export async function load(url, context, nextLoad) {
  // Nimmt eine aufgelöste URL entgegen und gibt den Quellcode zurück, der ausgewertet werden soll.
}

Asynchrone Hooks werden in einem separaten Thread ausgeführt, isoliert vom Hauptthread, in dem der Anwendungscode läuft. Das bedeutet, dass es sich um ein anderes Realm handelt. Der Hook-Thread kann vom Hauptthread jederzeit beendet werden, daher sollten Sie nicht von asynchronen Operationen (wie console.log) abhängig sein, die abgeschlossen werden. Sie werden standardmäßig an untergeordnete Worker vererbt.

Von module.registerHooks() akzeptierte synchrone Hooks

Hinzugefügt in: v23.5.0

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.1 - Aktive Entwicklung

Die Methode module.registerHooks() akzeptiert synchrone Hook-Funktionen. initialize() wird weder unterstützt noch benötigt, da der Hook-Implementierer den Initialisierungscode einfach direkt vor dem Aufruf von module.registerHooks() ausführen kann.

js
function resolve(specifier, context, nextResolve) {
  // Nimmt einen `import`- oder `require`-Specifier und löst ihn zu einer URL auf.
}

function load(url, context, nextLoad) {
  // Nimmt eine aufgelöste URL und gibt den Quellcode zurück, der ausgewertet werden soll.
}

Synchrone Hooks werden im selben Thread und im selben Realm ausgeführt, in dem die Module geladen werden. Im Gegensatz zu den asynchronen Hooks werden sie standardmäßig nicht an untergeordnete Worker-Threads vererbt. Wenn die Hooks jedoch mithilfe einer von --import oder --require vorab geladenen Datei registriert werden, können untergeordnete Worker-Threads die vorab geladenen Skripte über die process.execArgv-Vererbung erben. Siehe die Dokumentation von Worker für Details.

In synchronen Hooks können Benutzer erwarten, dass console.log() auf die gleiche Weise abgeschlossen wird, wie sie es von console.log() in Modulcode erwarten.

Konventionen von Hooks

Hooks sind Teil einer Kette, selbst wenn diese Kette nur aus einem benutzerdefinierten Hook und dem standardmäßigen Hook besteht, der immer vorhanden ist. Hook-Funktionen sind verschachtelt: Jede muss immer ein einfaches Objekt zurückgeben, und die Verkettung erfolgt dadurch, dass jede Funktion next\<hookName\>() aufruft, welches ein Verweis auf den Hook des nachfolgenden Loaders ist (in LIFO-Reihenfolge).

Ein Hook, der einen Wert zurückgibt, dem eine erforderliche Eigenschaft fehlt, löst eine Ausnahme aus. Ein Hook, der zurückgibt, ohne next\<hookName\>() und ohne shortCircuit: true zurückzugeben, löst ebenfalls eine Ausnahme aus. Diese Fehler sollen dazu beitragen, unbeabsichtigte Unterbrechungen der Kette zu verhindern. Geben Sie shortCircuit: true von einem Hook zurück, um zu signalisieren, dass die Kette absichtlich an Ihrem Hook endet.

initialize()

Hinzugefügt in: v20.6.0, v18.19.0

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.2 - Release Candidate

  • data <any> Die Daten aus register(loader, import.meta.url, { data }).

Der initialize-Hook wird nur von register akzeptiert. registerHooks() unterstützt ihn nicht und benötigt ihn auch nicht, da die Initialisierung für synchrone Hooks direkt vor dem Aufruf von registerHooks() ausgeführt werden kann.

Der initialize-Hook bietet eine Möglichkeit, eine benutzerdefinierte Funktion zu definieren, die im Hooks-Thread ausgeführt wird, wenn das Hooks-Modul initialisiert wird. Die Initialisierung erfolgt, wenn das Hooks-Modul über register registriert wird.

Dieser Hook kann Daten von einem register-Aufruf empfangen, einschließlich Ports und anderer übertragbarer Objekte. Der Rückgabewert von initialize kann ein <Promise> sein. In diesem Fall wird er abgewartet, bevor die Ausführung des Hauptanwendungstreads fortgesetzt wird.

Modul-Anpassungscode:

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

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

Aufrufender Code:

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

// Dieses Beispiel zeigt, wie ein MessageChannel verwendet werden kann, um
// zwischen dem Haupt-(Anwendungs-)Thread und den im Hooks-Thread laufenden Hooks
// zu kommunizieren, indem `port2` an den `initialize`-Hook gesendet wird.
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')

// Dieses Beispiel zeigt, wie ein MessageChannel verwendet werden kann, um
// zwischen dem Haupt-(Anwendungs-)Thread und den im Hooks-Thread laufenden Hooks
// zu kommunizieren, indem `port2` an den `initialize`-Hook gesendet wird.
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)

[Historie]

VersionÄnderungen
v23.5.0Unterstützung für synchrone und In-Thread-Hooks hinzugefügt.
v21.0.0, v20.10.0, v18.19.0Die Eigenschaft context.importAssertions wurde durch context.importAttributes ersetzt. Die Verwendung des alten Namens wird weiterhin unterstützt und löst eine experimentelle Warnung aus.
v18.6.0, v16.17.0Unterstützung für das Verketten von Resolve-Hooks hinzugefügt. Jeder Hook muss entweder nextResolve() aufrufen oder eine shortCircuit-Eigenschaft enthalten, die auf true gesetzt ist.
v17.1.0, v16.14.0Unterstützung für Import-Assertions hinzugefügt.

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.2 - Release Candidate (asynchrone Version) Stabilität: 1.1 - Aktive Entwicklung (synchrone Version)

  • specifier <string>

  • context <Object>

    • conditions <string[]> Exportbedingungen der relevanten package.json
    • importAttributes <Object> Ein Objekt, dessen Schlüssel-Wert-Paare die Attribute für das zu importierende Modul darstellen
    • parentURL <string> | <undefined> Das Modul, das dieses importiert, oder undefiniert, wenn dies der Node.js-Einstiegspunkt ist
  • nextResolve <Function> Der nachfolgende resolve-Hook in der Kette oder der Node.js-Standard-resolve-Hook nach dem letzten benutzerdefinierten resolve-Hook

  • Rückgabewert: <Object> | <Promise> Die asynchrone Version akzeptiert entweder ein Objekt mit den folgenden Eigenschaften oder ein Promise, das zu einem solchen Objekt auflöst. Die synchrone Version akzeptiert nur ein synchron zurückgegebenes Objekt.

    • format <string> | <null> | <undefined> Ein Hinweis für den Load-Hook (kann ignoriert werden) 'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'
    • importAttributes <Object> | <undefined> Die Importattribute, die beim Zwischenspeichern des Moduls verwendet werden sollen (optional; wenn weggelassen, wird die Eingabe verwendet)
    • shortCircuit <undefined> | <boolean> Ein Signal, dass dieser Hook beabsichtigt, die Kette der resolve-Hooks zu beenden. Standard: false
    • url <string> Die absolute URL, zu der diese Eingabe auflöst

Die resolve-Hook-Kette ist dafür verantwortlich, Node.js mitzuteilen, wo eine gegebene import-Anweisung oder ein import-Ausdruck oder ein require-Aufruf gefunden und wie er zwischengespeichert werden soll. Sie kann optional ein Format (z. B. 'module') als Hinweis für den load-Hook zurückgeben. Wenn ein Format angegeben ist, ist der load-Hook letztendlich dafür verantwortlich, den endgültigen format-Wert bereitzustellen (und kann den von resolve bereitgestellten Hinweis ignorieren); wenn resolve ein format bereitstellt, ist ein benutzerdefinierter load-Hook erforderlich, auch wenn nur der Wert an den Node.js-Standard-load-Hook übergeben werden soll.

Importtypattribute sind Teil des Cache-Schlüssels zum Speichern geladener Module im internen Modulcache. Der resolve-Hook ist dafür verantwortlich, ein importAttributes-Objekt zurückzugeben, wenn das Modul mit anderen Attributen als im Quellcode vorhanden zwischengespeichert werden soll.

Die Eigenschaft conditions in context ist ein Array von Bedingungen, die verwendet werden, um Bedingungen für Package-Exporte für diese Auflösungsanforderung abzugleichen. Sie können verwendet werden, um bedingte Zuordnungen an anderer Stelle nachzuschlagen oder die Liste zu ändern, wenn die Standardauflösungslogik aufgerufen wird.

Die aktuellen Bedingungen für Package-Exporte befinden sich immer im context.conditions-Array, das an den Hook übergeben wird. Um das Standardverhalten der Node.js-Modul-Spezifiziererauflösung beim Aufruf von defaultResolve zu gewährleisten, muss das an diesen übergebene context.conditions-Array alle Elemente des context.conditions-Arrays enthalten, das ursprünglich an den resolve-Hook übergeben wurde.

js
// Asynchrone Version, die von module.register() akzeptiert wird.
export async function resolve(specifier, context, nextResolve) {
  const { parentURL = null } = context

  if (Math.random() > 0.5) {
    // Eine Bedingung.
    // Für einige oder alle Spezifizierer wird eine benutzerdefinierte Logik für die Auflösung durchgeführt.
    // Immer ein Objekt der Form {url: <string>} zurückgeben.
    return {
      shortCircuit: true,
      url: parentURL ? new URL(specifier, parentURL).href : new URL(specifier).href,
    }
  }

  if (Math.random() < 0.5) {
    // Eine weitere Bedingung.
    // Beim Aufruf von `defaultResolve` können die Argumente geändert werden. In diesem
    // Fall wird ein weiterer Wert zum Abgleichen bedingter Exporte hinzugefügt.
    return nextResolve(specifier, {
      ...context,
      conditions: [...context.conditions, 'another-condition'],
    })
  }

  // An den nächsten Hook in der Kette weiterleiten, der der
  // Node.js-Standard-resolve wäre, wenn dies der letzte benutzerdefinierte Loader ist.
  return nextResolve(specifier)
}
js
// Synchrone Version, die von module.registerHooks() akzeptiert wird.
function resolve(specifier, context, nextResolve) {
  // Ähnlich wie die asynchrone resolve()-Funktion oben, da diese keine
  // asynchrone Logik aufweist.
}

load(url, context, nextLoad)

[Verlauf]

VersionÄnderungen
v23.5.0Unterstützung für synchrone und In-Thread-Version hinzugefügt.
v20.6.0Unterstützung für source mit dem Format commonjs hinzugefügt.
v18.6.0, v16.17.0Unterstützung für das Verketten von Load-Hooks hinzugefügt. Jeder Hook muss entweder nextLoad() aufrufen oder eine shortCircuit-Eigenschaft enthalten, die auf true gesetzt ist.

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1.2 - Release Candidate (asynchrone Version) Stabilität: 1.1 - Aktive Entwicklung (synchrone Version)

  • url <string> Die von der resolve-Kette zurückgegebene URL

  • context <Object>

  • nextLoad <Function> Der nachfolgende load-Hook in der Kette oder der Node.js-Standard-load-Hook nach dem letzten benutzerdefinierten load-Hook

  • Rückgabewert: <Object> | <Promise> Die asynchrone Version nimmt entweder ein Objekt mit den folgenden Eigenschaften oder ein Promise an, das zu einem solchen Objekt auflöst. Die synchrone Version akzeptiert nur ein synchron zurückgegebenes Objekt.

Der load-Hook bietet eine Möglichkeit, eine benutzerdefinierte Methode zur Bestimmung der Interpretation, des Abrufs und der Analyse einer URL zu definieren. Er ist auch für die Validierung der Importattribute zuständig.

Der endgültige Wert von format muss einer der folgenden sein:

formatBeschreibungAkzeptable Typen für source zurückgegeben von load
'builtin'Laden eines integrierten Node.js-ModulsNicht zutreffend
'commonjs'Laden eines Node.js CommonJS-Moduls{ string , ArrayBuffer , TypedArray , null , undefined }
'json'Laden einer JSON-Datei{ string , ArrayBuffer , TypedArray }
'module'Laden eines ES-Moduls{ string , ArrayBuffer , TypedArray }
'wasm'Laden eines WebAssembly-Moduls{ ArrayBuffer , TypedArray }

Der Wert von source wird für den Typ 'builtin' ignoriert, da es derzeit nicht möglich ist, den Wert eines integrierten (Kern-)Moduls von Node.js zu ersetzen.

Vorbehalt beim asynchronen load-Hook

Bei Verwendung des asynchronen load-Hooks hat das Weglassen bzw. Bereitstellen einer source für 'commonjs' sehr unterschiedliche Auswirkungen:

  • Wenn eine source bereitgestellt wird, werden alle require-Aufrufe aus diesem Modul vom ESM-Loader mit registrierten resolve- und load-Hooks verarbeitet; alle require.resolve-Aufrufe aus diesem Modul werden vom ESM-Loader mit registrierten resolve-Hooks verarbeitet; nur ein Teil der CommonJS-API ist verfügbar (z. B. kein require.extensions, kein require.cache, kein require.resolve.paths) und das Monkey-Patching des CommonJS-Modul-Loaders findet nicht statt.
  • Wenn source undefiniert oder null ist, wird es vom CommonJS-Modul-Loader verarbeitet und require/require.resolve-Aufrufe werden nicht über die registrierten Hooks ausgeführt. Dieses Verhalten für nullish source ist temporär – zukünftig wird nullish source nicht unterstützt.

Diese Vorbehalte gelten nicht für den synchronen load-Hook, in diesem Fall steht die vollständige CommonJS-API den angepassten CommonJS-Modulen zur Verfügung, und require/require.resolve durchlaufen immer die registrierten Hooks.

Die interne asynchrone load-Implementierung von Node.js, die der Wert von next für den letzten Hook in der load-Kette ist, gibt null für source zurück, wenn format 'commonjs' ist, um die Abwärtskompatibilität zu gewährleisten. Hier ist ein Beispiel-Hook, der die Verwendung des abweichenden Verhaltens ermöglicht:

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

// Asynchrone Version, die von module.register() akzeptiert wird. Dieser Fix ist nicht
// für die synchrone Version erforderlich, die von module.registerSync() akzeptiert wird.
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
}

Dies gilt auch nicht für den synchronen load-Hook, in diesem Fall enthält die zurückgegebene source den vom nächsten Hook geladenen Quellcode, unabhängig vom Modulformat.

Wenn der Quellwert eines textbasierten Formats (d. h. 'json', 'module') keine Zeichenkette ist, wird er mithilfe von util.TextDecoder in eine Zeichenkette konvertiert.

Der load-Hook bietet eine Möglichkeit, eine benutzerdefinierte Methode zum Abrufen des Quellcodes einer aufgelösten URL zu definieren. Dies würde es einem Loader ermöglichen, das Lesen von Dateien von der Festplatte möglicherweise zu vermeiden. Er könnte auch verwendet werden, um ein nicht erkanntes Format einem unterstützten zuzuordnen, z. B. yaml zu module.

js
// Asynchrone Version, die von module.register() akzeptiert wird.
export async function load(url, context, nextLoad) {
  const { format } = context

  if (Math.random() > 0.5) {
    // Einige Bedingung
    /*
      Für einige oder alle URLs wird eine benutzerdefinierte Logik zum Abrufen der Quelle ausgeführt.
      Immer ein Objekt der Form {
        format: <string>,
        source: <string|buffer>,
      } zurückgeben.
    */
    return {
      format,
      shortCircuit: true,
      source: '...',
    }
  }

  // An den nächsten Hook in der Kette verweisen.
  return nextLoad(url)
}
js
// Synchrone Version, die von module.registerHooks() akzeptiert wird.
function load(url, context, nextLoad) {
  // Ähnlich wie die asynchrone load()-Funktion oben, da diese keine
  // asynchrone Logik enthält.
}

In einem fortgeschritteneren Szenario kann dies auch verwendet werden, um eine nicht unterstützte Quelle in eine unterstützte zu transformieren (siehe Beispiele unten).

Beispiele

Die verschiedenen Module-Anpassungshooks können kombiniert werden, um weitreichende Anpassungen des Verhaltens beim Laden und Auswerten von Node.js-Code zu erreichen.

Import von HTTPS

Der unten stehende Hook registriert Hooks, um rudimentäre Unterstützung für solche Spezifikationen zu ermöglichen. Obwohl dies wie eine signifikante Verbesserung der Node.js-Kernfunktionalität erscheinen mag, gibt es erhebliche Nachteile bei der tatsächlichen Verwendung dieser Hooks: Die Leistung ist viel langsamer als das Laden von Dateien von der Festplatte, es gibt kein Caching und keine Sicherheit.

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

export function load(url, context, nextLoad) {
  // Damit JavaScript über das Netzwerk geladen werden kann, müssen wir es abrufen und
  // zurückgeben.
  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({
            // Dieses Beispiel geht davon aus, dass alle über das Netzwerk bereitgestellten JavaScript-Codes ES-Module sind.
            format: 'module',
            shortCircuit: true,
            source: data,
          })
        )
      }).on('error', err => reject(err))
    })
  }

  // Node.js soll alle anderen URLs verarbeiten.
  return nextLoad(url)
}
js
// main.mjs
import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js'

console.log(VERSION)

Mit dem vorhergehenden Hooks-Modul gibt die Ausführung von node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./https-hooks.mjs"));' ./main.mjs die aktuelle Version von CoffeeScript gemäß dem Modul an der URL in main.mjs aus.

Transpilierung

Quellen, die in Formaten vorliegen, die Node.js nicht versteht, können mit dem load-Hook in JavaScript konvertiert werden.

Dies ist weniger performant als das Transpilieren von Quelldateien vor der Ausführung von Node.js; Transpilierungs-Hooks sollten nur für Entwicklungs- und Testzwecke verwendet werden.

Asynchrone Version
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)) {
    // CoffeeScript-Dateien können entweder CommonJS- oder ES-Module sein, daher soll jede
    // CoffeeScript-Datei von Node.js genauso behandelt werden wie eine .js-Datei am selben
    // Speicherort. Um zu bestimmen, wie Node.js eine beliebige .js-Datei interpretieren würde,
    // wird das Dateisystem nach der nächstgelegenen übergeordneten package.json-Datei durchsucht
    // und deren Feld "type" gelesen.
    const format = await getPackageType(url)

    const { source: rawSource } = await nextLoad(url, { ...context, format })
    // Dieser Hook konvertiert CoffeeScript-Quellcode in JavaScript-Quellcode
    // für alle importierten CoffeeScript-Dateien.
    const transformedSource = coffeescript.compile(rawSource.toString(), url)

    return {
      format,
      shortCircuit: true,
      source: transformedSource,
    }
  }

  // Node.js soll alle anderen URLs verarbeiten.
  return nextLoad(url)
}

async function getPackageType(url) {
  // `url` ist nur während der ersten Iteration ein Dateipfad, wenn die
  // aufgelöste URL vom load()-Hook übergeben wird
  // ein tatsächlicher Dateipfad von load() enthält eine Dateierweiterung, da dies
  // von der Spezifikation gefordert wird
  // diese einfache Wahrheitsüberprüfung, ob `url` eine Dateierweiterung enthält, wird
  // für die meisten Projekte funktionieren, deckt aber einige Randfälle nicht ab (z. B.
  // dateinamenslose Dateien oder eine URL, die mit einem Leerzeichen endet)
  const isFilePath = !!extname(url)
  // Wenn es sich um einen Dateipfad handelt, wird das zugehörige Verzeichnis abgerufen
  const dir = isFilePath ? dirname(fileURLToPath(url)) : url
  // Erstellen eines Dateipfads zu einer package.json im selben Verzeichnis,
  // die möglicherweise existiert oder nicht
  const packagePath = resolvePath(dir, 'package.json')
  // Versuch, die möglicherweise nicht vorhandene package.json zu lesen
  const type = await readFile(packagePath, { encoding: 'utf8' })
    .then(filestring => JSON.parse(filestring).type)
    .catch(err => {
      if (err?.code !== 'ENOENT') console.error(err)
    })
  // Wenn package.json existierte und ein Feld `type` mit einem Wert enthielt, voilà
  if (type) return type
  // Andernfalls (wenn nicht im Stammverzeichnis) wird die Überprüfung im nächsten Verzeichnis nach oben fortgesetzt
  // Wenn im Stammverzeichnis, wird angehalten und false zurückgegeben
  return dir.length > 1 && getPackageType(resolvePath(dir, '..'))
}
Synchronversion
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 })

Hooks ausführen

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()

Mit den vorhergehenden Hooks-Modulen führt die Ausführung von node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee oder node --import ./coffeescript-sync-hooks.mjs ./main.coffee dazu, dass main.coffee nach dem Laden des Quellcodes von der Festplatte, aber bevor Node.js ihn ausführt, in JavaScript umgewandelt wird; und so weiter für alle .coffee, .litcoffee oder .coffee.md Dateien, auf die über import Anweisungen einer geladenen Datei verwiesen wird.

Import-Maps

Die beiden vorherigen Beispiele definierten load-Hooks. Dies ist ein Beispiel für einen resolve-Hook. Dieser Hook liest eine import-map.json-Datei, die definiert, welche Specifier zu anderen URLs überschrieben werden sollen (dies ist eine sehr vereinfachte Implementierung einer kleinen Teilmenge der Spezifikation "Import Maps").

Asynchrone Version
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)
}
Synchrone Version
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 })
Verwendung der Hooks

Mit diesen Dateien:

js
// main.js
import 'a-module'
json
// import-map.json
{
  "imports": {
    "a-module": "./some-module.js"
  }
}
js
// some-module.js
console.log('some module!')

Die Ausführung von node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./import-map-hooks.js"));' main.js oder node --import ./import-map-sync-hooks.js main.js sollte some module! ausgeben.

Source Map v3 Unterstützung

Hinzugefügt in: v13.7.0, v12.17.0

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1 - Experimentell

Hilfsprogramme für die Interaktion mit dem Source Map Cache. Dieser Cache wird gefüllt, wenn das Parsen von Source Maps aktiviert ist und Source Map Include-Direktiven in der Fußzeile eines Moduls gefunden werden.

Um das Parsen von Source Maps zu aktivieren, muss Node.js mit dem Flag --enable-source-maps oder mit aktivierter Codeabdeckung durch Setzen von NODE_V8_COVERAGE=dir ausgeführt werden.

js
// module.mjs
// In einem ECMAScript-Modul
import { findSourceMap, SourceMap } from 'node:module'
js
// module.cjs
// In einem CommonJS-Modul
const { findSourceMap, SourceMap } = require('node:module')

module.findSourceMap(path)

Hinzugefügt in: v13.7.0, v12.17.0

path ist der aufgelöste Pfad für die Datei, für die eine entsprechende Source Map abgerufen werden soll.

Klasse: module.SourceMap

Hinzugefügt in: v13.7.0, v12.17.0

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

Erstellt eine neue sourceMap-Instanz.

payload ist ein Objekt mit Schlüsseln, die dem Source Map v3 Format entsprechen:

lineLengths ist ein optionales Array der Länge jeder Zeile im generierten Code.

sourceMap.payload

Getter für die Nutzlast, die zum Konstruieren der SourceMap-Instanz verwendet wird.

sourceMap.findEntry(lineOffset, columnOffset)

  • lineOffset <number> Der nullbasierte Zeilenoffset in der generierten Quelle
  • columnOffset <number> Der nullbasierte Spaltenoffset in der generierten Quelle
  • Rückgabewert: <Object>

Gibt, gegeben einen Zeilen- und Spaltenoffset in der generierten Quelldatei, ein Objekt zurück, das den SourceMap-Bereich in der ursprünglichen Datei darstellt, falls gefunden, oder ein leeres Objekt, falls nicht.

Das zurückgegebene Objekt enthält die folgenden Schlüssel:

  • generatedLine: <number> Der Zeilenoffset des Beginns des Bereichs in der generierten Quelle
  • generatedColumn: <number> Der Spaltenoffset des Beginns des Bereichs in der generierten Quelle
  • originalSource: <string> Der Dateiname der ursprünglichen Quelle, wie in der SourceMap angegeben
  • originalLine: <number> Der Zeilenoffset des Beginns des Bereichs in der ursprünglichen Quelle
  • originalColumn: <number> Der Spaltenoffset des Beginns des Bereichs in der ursprünglichen Quelle
  • name: <string>

Der Rückgabewert stellt den Rohbereich dar, wie er in der SourceMap erscheint, basierend auf nullbasierten Offsets, nicht 1-basierten Zeilen- und Spaltennummern, wie sie in Fehlermeldungen und CallSite-Objekten erscheinen.

Um die entsprechenden 1-basierten Zeilen- und Spaltennummern aus einer lineNumber und columnNumber zu erhalten, wie sie von Fehlerstacks und CallSite-Objekten gemeldet werden, verwenden Sie sourceMap.findOrigin(lineNumber, columnNumber)

sourceMap.findOrigin(lineNumber, columnNumber)

  • lineNumber <number> Die 1-basierte Zeilennummer der Aufrufstelle in der generierten Quelle
  • columnNumber <number> Die 1-basierte Spaltennummer der Aufrufstelle in der generierten Quelle
  • Rückgabewert: <Object>

Ermittelt anhand einer 1-basierten lineNumber und columnNumber von einer Aufrufstelle in der generierten Quelle die entsprechende Aufrufstellenposition in der Originalquelle.

Wenn die angegebenen lineNumber und columnNumber in keiner Quellkarte gefunden werden, wird ein leeres Objekt zurückgegeben. Andernfalls enthält das zurückgegebene Objekt die folgenden Schlüssel:

  • name: <string> | <undefined> Der Name des Bereichs in der Quellkarte, falls angegeben
  • fileName: <string> Der Dateiname der Originalquelle, wie in der SourceMap angegeben
  • lineNumber: <number> Die 1-basierte lineNumber der entsprechenden Aufrufstelle in der Originalquelle
  • columnNumber: <number> Die 1-basierte columnNumber der entsprechenden Aufrufstelle in der Originalquelle