モジュール: `node:module` API#

`Module` オブジェクト#

`Module` のインスタンスを操作する際に一般的に使用されるユーティリティメソッドを提供します。これは、CommonJS モジュールでよく見られる `module` 変数です。`import 'node:module'` または `require('node:module')` を介してアクセスできます。

`module.builtinModules`#

Node.js によって提供されるすべてのモジュールの名前のリストです。サードパーティによって保守されているモジュールかどうかを確認するために使用できます。

このコンテキストでの `module` は、モジュールラッパーによって提供されるオブジェクトと同じではありません。アクセスするには、`Module` モジュールを require します。

// module.mjs
// In an ECMAScript module
import { builtinModules as builtin } from 'node:module';// module.cjs
// In a CommonJS module
const builtin = require('node:module').builtinModules;

`module.createRequire(filename)`#

  • `filename` <string> | <URL> require 関数の構築に使用されるファイル名。ファイルURLオブジェクト、ファイルURL文字列、または絶対パス文字列でなければなりません。
  • 戻り値: <require> require 関数
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);

// sibling-module.js is a CommonJS module.
const siblingModule = require('./sibling-module'); 

`module.isBuiltin(moduleName)`#

  • `moduleName` <string> モジュールの名前
  • 戻り値: <boolean> モジュールがビルトインの場合true、そうでない場合falseを返します。
import { isBuiltin } from 'node:module';
isBuiltin('node:fs'); // true
isBuiltin('fs'); // true
isBuiltin('wss'); // false 

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

安定性: 1.2 - リリース候補

  • `specifier` <string> | <URL> 登録するカスタマイズフック。`import()` に渡される文字列と同じである必要がありますが、相対的な場合は `parentURL` を基準に解決されます。
  • `parentURL` <string> | <URL> `specifier` を `import.meta.url` のようなベース URL を基準に解決する場合は、ここでその URL を渡すことができます。`parentURL` が2番目の引数として渡された場合、このプロパティは無視されます。**デフォルト:** `'data:'`
  • `options` <Object>
    • `parentURL` <string> | <URL> `specifier` を `import.meta.url` のようなベース URL を基準に解決する場合は、ここでその URL を渡すことができます。2番目の引数として `parentURL` が指定されている場合は、このプロパティは無視されます。**デフォルト:** `'data:'`
    • `data` <any> `initialize` フックに渡す任意の複製可能な JavaScript の値。
    • `transferList` <Object[]> 転送可能なオブジェクト を `initialize` フックに渡します。

Node.js のモジュール解決と読み込みの動作をカスタマイズするフックをエクスポートするモジュールを登録します。カスタマイズフックを参照してください。

`module.syncBuiltinESMExports()`#

`module.syncBuiltinESMExports()` メソッドは、ビルトイン ES モジュール のすべてのライブバインディングを CommonJS エクスポートのプロパティと一致するように更新します。ES モジュール からエクスポートされた名前を追加または削除することはありません。

const fs = require('node:fs');
const assert = require('node:assert');
const { syncBuiltinESMExports } = require('node:module');

fs.readFile = newAPI;

delete fs.readFileSync;

function newAPI() {
  // ...
}

fs.newAPI = newAPI;

syncBuiltinESMExports();

import('node:fs').then((esmFS) => {
  // It syncs the existing readFile property with the new value
  assert.strictEqual(esmFS.readFile, newAPI);
  // readFileSync has been deleted from the required fs
  assert.strictEqual('readFileSync' in fs, false);
  // syncBuiltinESMExports() does not remove readFileSync from esmFS
  assert.strictEqual('readFileSync' in esmFS, true);
  // syncBuiltinESMExports() does not add names
  assert.strictEqual(esmFS.newAPI, undefined);
}); 

カスタマイズフック#

安定性: 1.2 - リリース候補

有効化#

モジュールの解決と読み込みは、フックのセットをエクスポートするファイルを登録することでカスタマイズできます。これは、`--import` フラグを使用してアプリケーションコードの前に実行できる `node:module` の `register` メソッドを使用して行うことができます。

node --import ./register-hooks.js ./my-app.js 
// register-hooks.js
import { register } from 'node:module';

register('./hooks.mjs', import.meta.url);// register-hooks.js
const { register } = require('node:module');
const { pathToFileURL } = require('node:url');

register('./hooks.mjs', pathToFileURL(__filename));

`--import` に渡されるファイルは、依存関係からのエクスポートでもかまいません。

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

ここで、`some-package` には ` "exports"` フィールドがあり、` /register` エクスポートを `register()` を呼び出すファイルにマッピングします。次の `register-hooks.js` の例のように。

`--import` を使用すると、アプリケーションファイル(アプリケーションのエントリポイントを含む)がインポートされる前にフックが登録されます。あるいは、エントリポイントから `register` を呼び出すこともできますが、フックが登録された後に実行されるコードには、動的な `import()` を使用する必要があります。

import { register } from 'node:module';

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

// Because this is a dynamic `import()`, the `http-to-https` hooks will run
// to handle `./my-app.js` and any other files it imports or requires.
await import('./my-app.js');const { register } = require('node:module');
const { pathToFileURL } = require('node:url');

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

// Because this is a dynamic `import()`, the `http-to-https` hooks will run
// to handle `./my-app.js` and any other files it imports or requires.
import('./my-app.js');

この例では、`http-to-https` フックを登録していますが、これはその後でインポートされたモジュール(この場合は `my-app.js` と、それが `import`(およびオプションで `require`)を介して参照するすべて)に対してのみ使用できます。`import('./my-app.js')` が静的な `import './my-app.js'` だった場合、アプリは `http-to-https` フックが登録される *前* に既にロードされています。これは、静的インポートが最初にツリーの葉から評価され、次に幹に戻る ES モジュール仕様によるものです。`my-app.js` 内には静的インポートがある可能性があり、これは `my-app.js` が動的にインポートされるまで評価されません。

`my-app.js` は CommonJS でもかまいません。カスタマイズフックは、それが `import`(およびオプションで `require`)を介して参照するすべてのモジュールに対して実行されます。

最後に、アプリの実行前にフックを登録するだけで、その目的で別のファイルを作成したくない場合は、`--import` に `data:` URL を渡すことができます。

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

チェーン化#

`register` を複数回呼び出すことができます。

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

register('./foo.mjs', import.meta.url);
register('./bar.mjs', import.meta.url);
await import('./my-app.mjs');// entrypoint.cjs
const { register } = require('node:module');
const { pathToFileURL } = require('node:url');

const parentURL = pathToFileURL(__filename);
register('./foo.mjs', parentURL);
register('./bar.mjs', parentURL);
import('./my-app.mjs');

この例では、登録されたフックはチェーンを形成します。これらのチェーンは、後入れ先出し (LIFO) で実行されます。`foo.mjs` と `bar.mjs` の両方が `resolve` フックを定義している場合、次のように呼び出されます(右から左に注意してください)。Node.js のデフォルト ← `./foo.mjs` ← `./bar.mjs`(`./bar.mjs` から開始し、次に `./foo.mjs`、最後に Node.js のデフォルト)。これは他のすべてのフックにも当てはまります。

登録されたフックは `register` 自体にも影響します。この例では、`bar.mjs` は `foo.mjs` によって登録されたフックを介して解決およびロードされます(`foo` のフックは既にチェーンに追加されているため)。これにより、以前登録されたフックが JavaScript にトランスパイルされる限り、JavaScript 以外の言語でフックを作成することが可能になります。

`register` メソッドは、フックを定義するモジュール内からは呼び出すことができません。

モジュールカスタマイズフックとの通信#

モジュールカスタマイズフックは、アプリケーションコードを実行するメインスレッドとは別の専用スレッドで実行されます。つまり、グローバル変数を変更しても他のスレッドには影響せず、スレッド間で通信するにはメッセージチャネルを使用する必要があります。

`register` メソッドを使用して、`initialize` フックにデータを渡すことができます。フックに渡されるデータには、ポートなどの転送可能なオブジェクトを含めることができます。

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

// This example demonstrates how a message channel can be used to
// communicate with the hooks, by sending `port2` to the hooks.
const { port1, port2 } = new MessageChannel();

port1.on('message', (msg) => {
  console.log(msg);
});

register('./my-hooks.mjs', {
  parentURL: import.meta.url,
  data: { number: 1, port: port2 },
  transferList: [port2],
});const { register } = require('node:module');
const { pathToFileURL } = require('node:url');
const { MessageChannel } = require('node:worker_threads');

// This example showcases how a message channel can be used to
// communicate with the hooks, by sending `port2` to the hooks.
const { port1, port2 } = new MessageChannel();

port1.on('message', (msg) => {
  console.log(msg);
});

register('./my-hooks.mjs', {
  parentURL: pathToFileURL(__filename),
  data: { number: 1, port: port2 },
  transferList: [port2],
});

フック#

`register` メソッドを使用して、フックのセットをエクスポートするモジュールを登録できます。フックは、Node.js によって呼び出されてモジュールの解決と読み込みプロセスをカスタマイズする関数です。エクスポートされた関数は、特定の名前とシグネチャを持つ必要があり、名前付きエクスポートとしてエクスポートする必要があります。

export async function initialize({ number, port }) {
  // Receives data from `register`.
}

export async function resolve(specifier, context, nextResolve) {
  // Take an `import` or `require` specifier and resolve it to a URL.
}

export async function load(url, context, nextLoad) {
  // Take a resolved URL and return the source code to be evaluated.
} 

フックは チェーン の一部であり、そのチェーンがカスタム(ユーザー提供)フックと常に存在するデフォルトフックの1つだけから構成されている場合でもそうです。フック関数はネストされます。それぞれが常にプレーンオブジェクトを返す必要があり、チェーン化は各関数が `next<hookName>()` を呼び出すことによって行われます。これは、後続のローダーのフック(LIFO順)への参照です。

必須プロパティが欠落している値を返すフックは、例外をトリガーします。next<hookName>() を呼び出さずに *かつ* shortCircuit: true を返さないフックも、例外をトリガーします。これらのエラーは、チェーンの意図しない中断を防ぐためのものです。フックでチェーンが意図的に終了することを示すには、shortCircuit: true を返してください。

フックは、アプリケーションコードが実行されるメインスレッドから分離された別スレッドで実行されます。つまり、異なるレルムです。フックスレッドは、メインスレッドによっていつでも終了される可能性があるため、非同期操作(console.logなど)の完了に依存しないでください。

initialize()#

安定性: 1.2 - リリース候補

  • data <任意> register(loader, import.meta.url, { data })からのデータ。

initializeフックは、フックモジュールが初期化されたときにフックスレッドで実行されるカスタム関数を定義する方法を提供します。初期化は、register を介してフックモジュールが登録されたときに発生します。

このフックは、ポートやその他の転送可能なオブジェクトを含む、register の呼び出しからデータを受信できます。initializeの戻り値は<Promise>にすることができ、その場合、メインアプリケーションスレッドの実行が再開される前に待機されます。

モジュールカスタマイズコード

// path-to-my-hooks.js

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

呼び出し元コード

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

// This example showcases how a message channel can be used to communicate
// between the main (application) thread and the hooks running on the hooks
// thread, by sending `port2` to the `initialize` hook.
const { port1, port2 } = new MessageChannel();

port1.on('message', (msg) => {
  assert.strictEqual(msg, 'increment: 2');
});

register('./path-to-my-hooks.js', {
  parentURL: import.meta.url,
  data: { number: 1, port: port2 },
  transferList: [port2],
});const assert = require('node:assert');
const { register } = require('node:module');
const { pathToFileURL } = require('node:url');
const { MessageChannel } = require('node:worker_threads');

// This example showcases how a message channel can be used to communicate
// between the main (application) thread and the hooks running on the hooks
// thread, by sending `port2` to the `initialize` hook.
const { port1, port2 } = new MessageChannel();

port1.on('message', (msg) => {
  assert.strictEqual(msg, 'increment: 2');
});

register('./path-to-my-hooks.js', {
  parentURL: pathToFileURL(__filename),
  data: { number: 1, port: port2 },
  transferList: [port2],
});
resolve(specifier, context, nextResolve)#

安定性: 1.2 - リリース候補

  • specifier <文字列>
  • context <オブジェクト>
    • conditions <文字列配列> 関連するpackage.jsonのエクスポート条件
    • importAttributes <オブジェクト> モジュールのインポート属性を表すキーと値のペアを持つオブジェクト
    • parentURL <文字列> | <未定義> このモジュールをインポートするモジュール、またはこれがNode.jsのエントリポイントの場合は未定義
  • nextResolve <関数> チェーン内の次のresolveフック、または最後のユーザー指定のresolveフック後のNode.jsのデフォルトresolveフック
  • 戻り値: <オブジェクト> | <Promise>
    • format <文字列> | <null> | <未定義> ロードフックへのヒント(無視される可能性があります)'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'
    • importAttributes <オブジェクト> | <未定義> モジュールをキャッシュする際に使用するインポート属性(オプション。省略した場合は入力値が使用されます)
    • shortCircuit <未定義> | <ブール値> このフックがresolveフックのチェーンを終了することを示すシグナル。デフォルト: false
    • url <文字列> この入力値が解決される絶対URL

警告 Promiseと非同期関数の返却をサポートしていますが、resolveへの呼び出しはメインスレッドをブロックする可能性があり、パフォーマンスに影響を与える可能性があります。

resolveフックチェーンは、与えられたimport文または式、またはrequire呼び出しを見つける場所と、どのようにキャッシュするかをNode.jsに伝える役割を担っています。オプションで、フォーマット(例:'module')をloadフックへのヒントとして返すことができます。フォーマットが指定されている場合、最終的なformat値を提供するのはloadフックの責任であり(resolveから提供されたヒントを無視することもできます)、resolveformatを提供する場合、Node.jsのデフォルトloadフックに値を渡すためだけでも、カスタムloadフックが必要です。

インポートタイプの属性は、読み込まれたモジュールを内部モジュールキャッシュに保存するためのキャッシュキーの一部です。モジュールをソースコードとは異なる属性でキャッシュする必要がある場合、resolveフックはimportAttributesオブジェクトを返す必要があります。

context内のconditionsプロパティは、この解決要求に適用されるパッケージエクスポート条件の条件の配列です。これらは、他の場所で条件付きマッピングを検索したり、デフォルトの解決ロジックを呼び出す際にリストを変更するために使用できます。

現在のパッケージエクスポート条件は、常にフックに渡されるcontext.conditions配列にあります。defaultResolveを呼び出す際に、 *Node.jsモジュール指定子のデフォルトの解決動作* を保証するには、それに渡されるcontext.conditions配列に、元のresolveフックに渡されたcontext.conditions配列の *すべての* 要素を含める *必要があります*。

export async function resolve(specifier, context, nextResolve) {
  const { parentURL = null } = context;

  if (Math.random() > 0.5) { // Some condition.
    // For some or all specifiers, do some custom logic for resolving.
    // Always return an object of the form {url: <string>}.
    return {
      shortCircuit: true,
      url: parentURL ?
        new URL(specifier, parentURL).href :
        new URL(specifier).href,
    };
  }

  if (Math.random() < 0.5) { // Another condition.
    // When calling `defaultResolve`, the arguments can be modified. In this
    // case it's adding another value for matching conditional exports.
    return nextResolve(specifier, {
      ...context,
      conditions: [...context.conditions, 'another-condition'],
    });
  }

  // Defer to the next hook in the chain, which would be the
  // Node.js default resolve if this is the last user-specified loader.
  return nextResolve(specifier);
} 
load(url, context, nextLoad)#

安定性: 1.2 - リリース候補

loadフックは、URLの解釈、取得、解析方法を決定するカスタムメソッドを定義する方法を提供します。また、インポートアサーションの検証も担当します。

formatの最終値は次のいずれかである必要があります。

format説明loadによって返されるsourceの許容される型
'builtin'Node.js組み込みモジュールのロード適用外
'commonjs'Node.js CommonJSモジュールのロード{ 文字列ArrayBufferTypedArraynullundefined }
'json'JSONファイルのロード{ 文字列ArrayBufferTypedArray }
'module'ESモジュールのロード{ 文字列ArrayBufferTypedArray }
'wasm'WebAssemblyモジュールのロード{ ArrayBufferTypedArray }

現在、Node.js組み込み(コア)モジュールの値を置き換えることができないため、型'builtin'ではsourceの値は無視されます。

'commonjs'sourceを省略することと提供することには、非常に異なる効果があります。

  • sourceが提供されると、このモジュールからのすべてのrequire呼び出しは、登録されたresolveおよびloadフックを使用してESMローダーによって処理されます。このモジュールからのすべてのrequire.resolve呼び出しは、登録されたresolveフックを使用してESMローダーによって処理されます。CommonJS APIの一部分のみが使用可能になります(例:require.extensionsrequire.cacherequire.resolve.pathsはありません)。また、CommonJSモジュールローダーに対するモンキーパッチングは適用されません。
  • sourceが未定義またはnullの場合、CommonJSモジュールローダーによって処理され、require/require.resolve呼び出しは登録されたフックを通過しません。nullishsourceに対するこの動作は一時的なものです。将来、nullishsourceはサポートされなくなります。

node--experimental-default-type=commonjsで実行されると、Node.js内部のload実装(loadチェーンの最後のフックのnextの値)は、後方互換性のためにformat'commonjs'の場合、sourcenullを返します。 こちらが、デフォルト以外の動作を使用するための例となるフックです。

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

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;
} 

警告: ESM loadフックとCommonJSモジュールからの名前空間付きエクスポートは互換性がありません。これらを一緒に使用しようとすると、インポートから空のオブジェクトが返されます。これは将来対処される可能性があります。

これらの型はすべて、ECMAScriptで定義されたクラスに対応しています。

テキストベースのフォーマット(つまり、'json''module')のsource値が文字列でない場合、util.TextDecoderを使用して文字列に変換されます。

loadフックは、解決済みのURLのソースコードを取得するためのカスタムメソッドを定義する方法を提供します。これにより、ローダーはディスクからのファイル読み取りを回避できる可能性があります。また、認識されないフォーマットをサポートされているフォーマットにマッピングするためにも使用できます(例:yamlmoduleに)。

export async function load(url, context, nextLoad) {
  const { format } = context;

  if (Math.random() > 0.5) { // Some condition
    /*
      For some or all URLs, do some custom logic for retrieving the source.
      Always return an object of the form {
        format: <string>,
        source: <string|buffer>,
      }.
    */
    return {
      format,
      shortCircuit: true,
      source: '...',
    };
  }

  // Defer to the next hook in the chain.
  return nextLoad(url);
} 

より高度なシナリオでは、サポートされていないソースをサポートされているソースに変換するためにも使用できます(下記のを参照)。

#

さまざまなモジュールカスタマイズフックを組み合わせて使用することで、Node.jsのコード読み込みと評価動作を幅広くカスタマイズできます。

HTTPSからのインポート#

現在のNode.jsでは、https://で始まる指定子は実験的です(HTTPSとHTTPインポートを参照)。

以下のフックは、そのような指定子に対する基本的なサポートを有効にするフックを登録します。これはNode.jsコア機能への大きな改善のように見えるかもしれませんが、これらのフックを実際に使用することには大きな欠点があります。パフォーマンスはディスクからのファイル読み込みよりもはるかに遅く、キャッシングがなく、セキュリティもありません。

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

export function load(url, context, nextLoad) {
  // For JavaScript to be loaded over the network, we need to fetch and
  // return it.
  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({
          // This example assumes all network-provided JavaScript is ES module
          // code.
          format: 'module',
          shortCircuit: true,
          source: data,
        }));
      }).on('error', (err) => reject(err));
    });
  }

  // Let Node.js handle all other URLs.
  return nextLoad(url);
} 
// main.mjs
import { VERSION } from 'https://coffeescript.dokyumento.jp/browser-compiler-modern/coffeescript.js';

console.log(VERSION); 

上記のフックモジュールを使用すると、node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./https-hooks.mjs"));' ./main.mjsを実行すると、main.mjsのURLにあるモジュールに従って、CoffeeScriptの現在のバージョンが出力されます。

トランスパイル#

Node.jsが理解できない形式のソースは、loadフックを使用してJavaScriptに変換できます。

これは、Node.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 files can be either CommonJS or ES modules, so we want any
    // CoffeeScript file to be treated by Node.js the same as a .js file at the
    // same location. To determine how Node.js would interpret an arbitrary .js
    // file, search up the file system for the nearest parent package.json file
    // and read its "type" field.
    const format = await getPackageType(url);

    const { source: rawSource } = await nextLoad(url, { ...context, format });
    // This hook converts CoffeeScript source code into JavaScript source code
    // for all imported CoffeeScript files.
    const transformedSource = coffeescript.compile(rawSource.toString(), url);

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

  // Let Node.js handle all other URLs.
  return nextLoad(url);
}

async function getPackageType(url) {
  // `url` is only a file path during the first iteration when passed the
  // resolved url from the load() hook
  // an actual file path from load() will contain a file extension as it's
  // required by the spec
  // this simple truthy check for whether `url` contains a file extension will
  // work for most projects but does not cover some edge-cases (such as
  // extensionless files or a url ending in a trailing space)
  const isFilePath = !!extname(url);
  // If it is a file path, get the directory it's in
  const dir = isFilePath ?
    dirname(fileURLToPath(url)) :
    url;
  // Compose a file path to a package.json in the same directory,
  // which may or may not exist
  const packagePath = resolvePath(dir, 'package.json');
  // Try to read the possibly nonexistent package.json
  const type = await readFile(packagePath, { encoding: 'utf8' })
    .then((filestring) => JSON.parse(filestring).type)
    .catch((err) => {
      if (err?.code !== 'ENOENT') console.error(err);
    });
  // If package.json existed and contained a `type` field with a value, voilà
  if (type) return type;
  // Otherwise, (if not at the root) continue checking the next directory up
  // If at the root, stop and return false
  return dir.length > 1 && getPackageType(resolvePath(dir, '..'));
} 
# main.coffee
import { scream } from './scream.coffee'
console.log scream 'hello, world'

import { version } from 'node:process'
console.log "Brought to you by Node.js version #{version}" 
# scream.coffee
export scream = (str) -> str.toUpperCase() 

上記のフックモジュールを使用すると、node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffeeを実行すると、ディスクからソースコードが読み込まれた後、Node.jsが実行する前にmain.coffeeがJavaScriptに変換されます。そして、読み込まれたファイルのimportステートメントを介して参照されるすべての.coffee.litcoffee、または.coffee.mdファイルについても同様です。

インポートマップ#

前の2つの例ではloadフックを定義しました。これはresolveフックの例です。このフックモジュールは、どの指定子を他のURLに上書きするかを定義するimport-map.jsonファイルを読み取ります(これは「インポートマップ」仕様の小さなサブセットの非常に単純な実装です)。

// 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);
} 

これらのファイルを使用します。

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

node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./import-map-hooks.js"));' main.jsを実行すると、some module!が出力されます。

ソースマップv3サポート#

安定性: 1 - 実験的

ソースマップキャッシュとの対話のためのヘルパー。このキャッシュは、ソースマップの解析が有効になっており、モジュールのフッターにソースマップインクルードディレクティブが見つかった場合に設定されます。

ソースマップの解析を有効にするには、Node.jsを--enable-source-mapsフラグ付きで実行するか、NODE_V8_COVERAGE=dirを設定してコードカバレッジを有効にする必要があります。

// module.mjs
// In an ECMAScript module
import { findSourceMap, SourceMap } from 'node:module';// module.cjs
// In a CommonJS module
const { findSourceMap, SourceMap } = require('node:module');

module.findSourceMap(path)#

pathは、対応するソースマップを取得する必要があるファイルの解決済みパスです。

クラス: module.SourceMap#

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

新しいsourceMapインスタンスを作成します。

payloadは、ソースマップv3形式に一致するキーを持つオブジェクトです。

lineLengthsは、生成されたコードの各行の長さのオプションの配列です。

sourceMap.payload#

SourceMapインスタンスの構築に使用されたペイロードのゲッターです。

sourceMap.findEntry(lineOffset, columnOffset)#
  • lineOffset <number> 生成されたソースの0ベースの行番号オフセット
  • columnOffset <number> 生成されたソースの0ベースの列番号オフセット
  • 戻り値: <オブジェクト>

生成されたソースファイルの行オフセットと列オフセットを指定すると、見つかった場合は元のファイルのSourceMap範囲を表すオブジェクトを、見つからない場合は空のオブジェクトを返します。

返されるオブジェクトには、次のキーが含まれています。

  • generatedLine: <number> 生成されたソースの範囲の先頭の行オフセット
  • generatedColumn: <number> 生成されたソースの範囲の先頭の列オフセット
  • originalSource: <string> SourceMapに報告されている元のソースのファイル名
  • originalLine: <number> 元のソースの範囲の先頭の行オフセット
  • originalColumn: <number> 元のソースの範囲の先頭の列オフセット
  • name: <string>

返された値は、エラーメッセージとCallSiteオブジェクトに表示されるように、1ベースの行番号と列番号ではなく、0ベースのオフセットに基づいたSourceMapに表示される生の範囲を表します。

エラースタックとCallSiteオブジェクトによって報告されるlineNumberとcolumnNumberから対応する1ベースの行番号と列番号を取得するには、sourceMap.findOrigin(lineNumber, columnNumber)を使用します。

sourceMap.findOrigin(lineNumber, columnNumber)#
  • lineNumber <number> 生成されたソースの呼び出しサイトの1ベースの行番号
  • columnNumber <number> 生成されたソースの呼び出しサイトの1ベースの列番号
  • 戻り値: <オブジェクト>

生成されたソースの呼び出しサイトの1ベースのlineNumbercolumnNumberを指定して、元のソースの対応する呼び出しサイトの位置を見つけます。

提供されたlineNumbercolumnNumberがソースマップに見つからない場合、空のオブジェクトが返されます。それ以外の場合、返されるオブジェクトには次のキーが含まれます。

  • name: <string> | <undefined> 提供された場合、ソースマップ内の範囲の名前
  • fileName: <string> SourceMapに報告されている元のソースのファイル名
  • lineNumber: <number> 元のソースの対応する呼び出しサイトの1ベースのlineNumber
  • columnNumber: <number> 元のソースの対応する呼び出しサイトの1ベースのcolumnNumber