Spec-Zone.ru › Node.js 24 LTS

Модули: node:module API

Добавлено в: v0.3.7

Объект Module

  • Тип: <Object>

Предоставляет общие служебные методы для работы с экземплярами Module; переменная module часто встречается в модулях CommonJS. Доступна через import 'node:module' или require('node:module').

module.builtinModules

История
Версия Изменения
v23.5.0

Теперь список также содержит модули, состоящие только из префиксов.

v9.3.0, v8.10.0, v6.13.0

Добавлено в: v9.3.0, v8.10.0, v6.13.0

  • Тип: <string[]>

Список имен всех модулей, предоставляемых Node.js. Его можно использовать, чтобы проверить, поддерживается ли модуль сторонним разработчиком.

module в этом контексте — не тот же объект, который предоставляется оберткой модуля. Чтобы получить к нему доступ, подключите модуль Module:

Модули JavaScript
// module.mjs
// In an ECMAScript module
import { builtinModules as builtin } from 'node:module';
CommonJS
// module.cjs
// In a CommonJS module
const builtin = require('node:module').builtinModules;

module.createRequire(filename)

Добавлено в: v12.2.0
  • 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'); copy

module.findPackageJSON(specifier[, base])

Добавлено в: v23.2.0, v22.14.0
Стабильность: 1.1 — Активная разработка
  • specifier <string> | <URL> Спецификатор модуля, package.json которого требуется получить. Если передан голый спецификатор, возвращается package.json в корне пакета. Если передан относительный спецификатор или абсолютный спецификатор, возвращается ближайший родительский package.json.
  • base <string> | <URL> Абсолютное расположение (строка URL file: или путь FS) содержащего модуля. Для CJS используйте __filename (не __dirname!); для ESM используйте import.meta.url. Передавать его не нужно, если specifier является absolute specifier.
  • Возвращает: <string> | <undefined> Путь, если найден package.json. Если specifier — пакет, возвращается корневой package.json пакета; если это относительный или неразрешенный спецификатор — ближайший package.json к specifier.

Предупреждение: Не используйте это для определения формата модуля. На это определение влияет множество факторов; поле type в package.json — наименее надежный источник (например, расширение файла имеет приоритет над ним, а хук загрузчика — над расширением).

Предупреждение: Сейчас используется только встроенный резолвер по умолчанию; если зарегистрированы хуки настройки resolve, они не повлияют на разрешение. В будущем это может измениться.

/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' copy
Модули JavaScript
// /path/to/project/packages/bar/bar.js
import { findPackageJSON } from 'node:module';

findPackageJSON('..', import.meta.url);
// '/path/to/project/package.json'
// Same result when passing an absolute specifier instead:
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'
// When passing an absolute specifier, you might get a different result if the
// resolved module is inside a subfolder that has nested `package.json`.
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'
CommonJS
// /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'
// Same result when passing an absolute specifier instead:
findPackageJSON(pathToFileURL(path.join(__dirname, '..')));

findPackageJSON('some-package', __filename);
// '/path/to/project/packages/bar/node_modules/some-package/package.json'
// When passing an absolute specifier, you might get a different result if the
// resolved module is inside a subfolder that has nested `package.json`.
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)

Добавлено в: v18.6.0, v16.17.0
  • moduleName <string> имя модуля
  • Возвращает: <boolean> возвращает true, если модуль встроенный, иначе возвращает false
import { isBuiltin } from 'node:module';
isBuiltin('node:fs'); // true
isBuiltin('fs'); // true
isBuiltin('wss'); // false copy

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

История
Версия Изменения
v23.6.1, v22.13.1, v20.18.2

Для использования этой функции при включенной модели разрешений требуется передать --allow-worker.

v20.8.0, v18.19.0

Добавлена поддержка экземпляров WHATWG URL.

v20.6.0, v18.19.0

Добавлено в: v20.6.0, v18.19.0

Стабильность: 1.1 — Активная разработка
  • specifier <string> | <URL> Хуки настройки для регистрации; это должна быть та же строка, которая передавалась бы в import(), за исключением того, что относительный путь разрешается относительно parentURL.
  • parentURL <string> | <URL> Если требуется разрешить specifier относительно базового URL, например import.meta.url, этот URL можно передать здесь. По умолчанию: 'data:'
  • options <Object>
    • parentURL <string> | <URL> Если требуется разрешить specifier относительно базового URL, например import.meta.url, этот URL можно передать здесь. Это свойство игнорируется, если parentURL передан в качестве второго аргумента. По умолчанию: 'data:'
    • data <any> Любое произвольное клонируемое значение JavaScript, передаваемое в хук initialize.
    • transferList <Object[]> передаваемые объекты, передаваемые в хук initialize.

Зарегистрируйте модуль, экспортирующий хуки, которые настраивают разрешение и загрузку модулей Node.js. См. раздел Хуки настройки.

Для использования этой функции с моделью разрешений требуется --allow-worker.

module.registerHooks(options)

История
Версия Изменения
v24.13.1

Синхронные хуки и хуки в том же потоке теперь являются кандидатами на выпуск.

v23.5.0, v22.15.0

Добавлено в: v23.5.0, v22.15.0

Стабильность: 1.2 — Кандидат на выпуск
  • options <Object>
    • load <Function> | <undefined> См. хук load. По умолчанию: undefined.
    • resolve <Function> | <undefined> См. хук resolve. По умолчанию: undefined.

Зарегистрируйте хуки, которые настраивают разрешение и загрузку модулей Node.js. См. раздел Хуки настройки.

module.stripTypeScriptTypes(code[, options])

Добавлено в: v23.2.0, v22.13.0
Стабильность: 1.2 — Кандидат на выпуск
  • code <string> Код, из которого нужно удалить аннотации типов.
  • options <Object>
    • mode <string> По умолчанию: 'strip'. Возможные значения:
      • 'strip' Удалять только аннотации типов, не преобразуя возможности TypeScript.
      • 'transform' Удалять аннотации типов и преобразовывать возможности TypeScript в JavaScript.
    • sourceMap <boolean> По умолчанию: false. Только если mode равно 'transform', при условии true для преобразованного кода будет создана карта исходного кода.
    • sourceUrl <string> Указывает URL источника, используемый в карте исходного кода.
  • Возвращает: <string> Код без аннотаций типов. module.stripTypeScriptTypes() удаляет аннотации типов из кода TypeScript. Его можно использовать для удаления аннотаций типов перед запуском кода с помощью vm.runInContext() или vm.compileFunction(). По умолчанию будет выброшена ошибка, если код содержит возможности TypeScript, требующие преобразования, например Enums; дополнительную информацию см. в разделе удаление типов. Если режим — 'transform', возможности TypeScript также преобразуются в JavaScript; дополнительную информацию см. в разделе преобразование возможностей TypeScript. Если режим — 'strip', карты исходного кода не создаются, поскольку расположение сохраняется. Если задано sourceMap, а режим — 'strip', будет выброшена ошибка.

ПРЕДУПРЕЖДЕНИЕ: Из-за изменений в парсере TypeScript результат работы этой функции не следует считать неизменным в разных версиях Node.js.

Модули JavaScript
import { stripTypeScriptTypes } from 'node:module';
const code = 'const a: number = 1;';
const strippedCode = stripTypeScriptTypes(code);
console.log(strippedCode);
// Prints: const a         = 1;
CommonJS
const { stripTypeScriptTypes } = require('node:module');
const code = 'const a: number = 1;';
const strippedCode = stripTypeScriptTypes(code);
console.log(strippedCode);
// Prints: const a         = 1;

Если задано sourceUrl, оно будет добавлено в конец результата в виде комментария:

Модули JavaScript
import { stripTypeScriptTypes } from 'node:module';
const code = 'const a: number = 1;';
const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' });
console.log(strippedCode);
// Prints: const a         = 1\n\n//# sourceURL=source.ts;
CommonJS
const { stripTypeScriptTypes } = require('node:module');
const code = 'const a: number = 1;';
const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' });
console.log(strippedCode);
// Prints: const a         = 1\n\n//# sourceURL=source.ts;

Если mode равно 'transform', код преобразуется в JavaScript:

Модули JavaScript
import { stripTypeScriptTypes } from 'node:module';
const code = `
  namespace MathUtil {
    export const add = (a: number, b: number) => a + b;
  }`;
const strippedCode = stripTypeScriptTypes(code, { mode: 'transform', sourceMap: true });
console.log(strippedCode);
// Prints:
// var MathUtil;
// (function(MathUtil) {
//     MathUtil.add = (a, b)=>a + b;
// })(MathUtil || (MathUtil = {}));
// # sourceMappingURL=data:application/json;base64, ...
CommonJS
const { stripTypeScriptTypes } = require('node:module');
const code = `
  namespace MathUtil {
    export const add = (a: number, b: number) => a + b;
  }`;
const strippedCode = stripTypeScriptTypes(code, { mode: 'transform', sourceMap: true });
console.log(strippedCode);
// Prints:
// var MathUtil;
// (function(MathUtil) {
//     MathUtil.add = (a, b)=>a + b;
// })(MathUtil || (MathUtil = {}));
// # sourceMappingURL=data:application/json;base64, ...

module.syncBuiltinESMExports()

Добавлено в: v12.12.0

Метод 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);
}); copy

Кэш компиляции модулей

История
Версия Изменения
v22.8.0

Добавлены начальные API JavaScript для доступа во время выполнения.

v22.1.0

Добавлено в: v22.1.0

Кэш компиляции модулей можно включить с помощью метода module.enableCompileCache() или переменной среды NODE_COMPILE_CACHE=dir. После включения, при компиляции Node.js модулей CommonJS, модулей ECMAScript или модулей TypeScript будет использоваться сохраненный на диске кэш кода V8 из указанного каталога, чтобы ускорить компиляцию. Это может замедлить первую загрузку графа модулей, однако последующие загрузки того же графа могут значительно ускориться, если содержимое модулей не изменилось.

Чтобы очистить созданный кэш компиляции на диске, просто удалите каталог кэша. При следующем использовании этого же каталога для хранения кэша компиляции он будет создан заново. Чтобы избежать заполнения диска устаревшим кэшем, рекомендуется использовать каталог внутри os.tmpdir(). Если кэш компиляции включен вызовом module.enableCompileCache() без указания directory, Node.js будет использовать переменную среды NODE_COMPILE_CACHE=dir, если она задана, а в противном случае — path.join(os.tmpdir(), 'node-compile-cache'). Чтобы определить каталог кэша компиляции, используемый работающим экземпляром Node.js, воспользуйтесь module.getCompileCacheDir().

Включенный кэш компиляции модулей можно отключить с помощью переменной среды NODE_DISABLE_COMPILE_CACHE=1. Это может быть полезно, если кэш компиляции приводит к неожиданному или нежелательному поведению (например, к снижению точности тестового покрытия).

В настоящее время при включенном кэше компиляции кодовый кэш для только что загруженного модуля создается сразу после компиляции кода, но записывается на диск только перед завершением работы экземпляра Node.js. Это может измениться. Метод module.flushCompileCache() позволяет гарантировать запись накопленного кодового кэша на диск, если приложению требуется запускать другие экземпляры Node.js и совместно использовать кэш задолго до завершения родительского процесса.

Переносимость кэша компиляции

По умолчанию кэш недействителен, если изменились абсолютные пути кэшируемых модулей. Чтобы кэш продолжал работать после перемещения каталога проекта, включите переносимый кэш компиляции. Это позволяет повторно использовать ранее скомпилированные модули в разных расположениях каталогов, если структура относительно каталога кэша остается неизменной. Эта возможность предоставляется по мере возможности. Если Node.js не может вычислить расположение модуля относительно каталога кэша, модуль не будет кэширован.

Переносимый режим можно включить двумя способами:

  1. С помощью параметра portable в module.enableCompileCache():

    // Non-portable cache (default): cache breaks if project is moved
    module.enableCompileCache({ directory: '/path/to/cache/storage/dir' });
    
    // Portable cache: cache works after the project is moved
    module.enableCompileCache({ directory: '/path/to/cache/storage/dir', portable: true }); copy
  2. Задать переменную среды: NODE_COMPILE_CACHE_PORTABLE=1

Ограничения кэша компиляции

В настоящее время при использовании кэша компиляции вместе с покрытием кода JavaScript в V8 собираемое V8 покрытие может быть менее точным для функций, десериализованных из кэша кода. Для получения точных данных о покрытии рекомендуется отключать эту возможность при запуске тестов.

Кэш компиляции, созданный одной версией Node.js, нельзя использовать с другой версией Node.js. Если для сохранения кэша используется один и тот же базовый каталог, кэши разных версий Node.js будут храниться отдельно и смогут сосуществовать.

module.constants.compileCacheStatus

Добавлено в: v22.8.0
Стабильность: 1.1 — Активная разработка

Следующие константы возвращаются в поле status объекта, возвращаемого методом module.enableCompileCache(), и указывают результат попытки включить кэш компиляции модулей.

Константа Описание
ENABLED Node.js успешно включил кэш компиляции. Каталог, используемый для хранения кэша компиляции, возвращается в поле directory возвращаемого объекта.
ALREADY_ENABLED Кэш компиляции уже был включен ранее — предыдущим вызовом module.enableCompileCache() или переменной среды NODE_COMPILE_CACHE=dir. Каталог, используемый для хранения кэша компиляции, возвращается в поле directory возвращаемого объекта.
FAILED Node.js не удалось включить кэш компиляции. Причиной может быть отсутствие разрешения на использование указанного каталога или различные ошибки файловой системы. Подробности сбоя возвращаются в поле message возвращаемого объекта.
DISABLED Node.js не может включить кэш компиляции, поскольку задана переменная среды NODE_DISABLE_COMPILE_CACHE=1.

module.enableCompileCache([options])

История
Версия Изменения
v24.12.0

Добавлен параметр portable для включения переносимого кэша компиляции.

v24.12.0

Не выпущенный ранее параметр path переименован в directory для единообразия.

v22.8.0

Добавлено в: v22.8.0

Стабильность: 1.1 — Активная разработка
  • options <string> | <Object> Необязательный параметр. Если передана строка, она считается options.directory.
    • directory <string> Необязательный параметр. Каталог для хранения кэша компиляции. Если он не указан, будет использоваться каталог, заданный переменной среды NODE_COMPILE_CACHE=dir, если она задана, или path.join(os.tmpdir(), 'node-compile-cache') в противном случае.
    • portable <boolean> Необязательный параметр. Если true, включается переносимый кэш компиляции, чтобы кэш можно было использовать повторно даже после перемещения каталога проекта. Эта возможность предоставляется по мере возможности. Если параметр не указан, его значение зависит от того, задана ли переменная среды NODE_COMPILE_CACHE_PORTABLE=1.
  • Возвращает: <Object>
    • status <integer> Одно из значений module.constants.compileCacheStatus
    • message <string> | <undefined> Если Node.js не удалось включить кэш компиляции, содержит сообщение об ошибке. Задается только если status равно module.constants.compileCacheStatus.FAILED.
    • directory <string> | <undefined> Если кэш компиляции включен, содержит каталог, в котором он хранится. Задается только если status равно module.constants.compileCacheStatus.ENABLED или module.constants.compileCacheStatus.ALREADY_ENABLED.

Включает кэш компиляции модулей в текущем экземпляре Node.js.

Для большинства сценариев рекомендуется вызывать module.enableCompileCache() без указания options.directory, чтобы при необходимости каталог можно было переопределить переменной среды NODE_COMPILE_CACHE.

Поскольку кэш компиляции — это оптимизация, не являющаяся критически важной, данный метод не выбрасывает исключения, если кэш компиляции не удается включить. Вместо этого он возвращает объект с сообщением об ошибке в поле message для облегчения отладки. Если кэш компиляции успешно включен, поле directory возвращаемого объекта содержит путь к каталогу, в котором хранится кэш компиляции. В поле status возвращаемого объекта будет одно из значений module.constants.compileCacheStatus, указывающее результат попытки включить кэш компиляции модулей.

Этот метод влияет только на текущий экземпляр Node.js. Чтобы включить кэш в дочерних рабочих потоках, вызовите этот метод и в них либо задайте значение process.env.NODE_COMPILE_CACHE, равное каталогу кэша компиляции, чтобы дочерние рабочие потоки унаследовали это поведение. Каталог можно получить из поля directory, возвращаемого этим методом, или с помощью module.getCompileCacheDir().

module.flushCompileCache()

Добавлено в: v23.0.0, v22.10.0
Стабильность: 1.1 — Активная разработка

Записывает на диск кэш компиляции модулей, накопленный для уже загруженных модулей в текущем экземпляре Node.js. Метод возвращает управление после завершения всех операций записи в файловую систему независимо от их результата. При возникновении ошибок они игнорируются, поскольку промахи кэша компиляции не должны мешать фактической работе приложения.

module.getCompileCacheDir()

Добавлено в: v22.8.0
Стабильность: 1.1 — Активная разработка
  • Возвращает: <string> | <undefined> Путь к каталогу кэша компиляции модулей, если он включен, или undefined в противном случае.

Хуки настройки

История
Версия Изменения
v24.13.1

Синхронные хуки и хуки в том же потоке теперь имеют статус кандидата на выпуск.

v23.5.0, v22.15.0

Добавлена поддержка синхронных хуков и хуков в том же потоке.

v20.6.0, v18.19.0

Добавлен хук initialize для замены globalPreload.

v18.6.0, v16.17.0

Добавлена поддержка цепочек загрузчиков.

v16.12.0

Удалены getFormat, getSource, transformSource и globalPreload; добавлены хук load и хук getGlobalPreload.

v8.8.0

Добавлено в: v8.8.0

В настоящее время Node.js поддерживает два типа хуков настройки модулей:

  1. module.registerHooks(options): принимает синхронные функции-хуки, которые выполняются непосредственно в потоке, где загружаются модули.
  2. module.register(specifier[, parentURL][, options]): принимает спецификатор модуля, экспортирующего асинхронные функции-хуки. Эти функции выполняются в отдельном потоке загрузчика.

Асинхронные хуки создают дополнительные накладные расходы из-за обмена данными между потоками и имеют ряд особенностей, особенно при настройке модулей CommonJS в графе модулей. В большинстве случаев для простоты рекомендуется использовать синхронные хуки с помощью module.registerHooks().

Синхронные хуки настройки

Стабильность: 1.2 — кандидат на выпуск
Регистрация синхронных хуков настройки

Чтобы зарегистрировать синхронные хуки настройки, используйте module.registerHooks(), принимающий синхронные функции-хуки, указанные непосредственно в строке.

Модули JavaScript
// register-hooks.js
import { registerHooks } from 'node:module';
registerHooks({
  resolve(specifier, context, nextResolve) { /* implementation */ },
  load(url, context, nextLoad) { /* implementation */ },
});
CommonJS
// register-hooks.js
const { registerHooks } = require('node:module');
registerHooks({
  resolve(specifier, context, nextResolve) { /* implementation */ },
  load(url, context, nextLoad) { /* implementation */ },
});
Регистрация хуков до запуска кода приложения с помощью флагов

Хуки можно зарегистрировать до запуска кода приложения с помощью флага --import или --require:

node --import ./register-hooks.js ./my-app.js
node --require ./register-hooks.js ./my-app.js copy

Спецификатор, переданный в --import или --require, также может быть получен из пакета:

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

При этом some-package содержит поле "exports", определяющее экспорт /register, который должен указывать на файл, вызывающий registerHooks(), как в примерах выше с register-hooks.js.

Использование --import или --require гарантирует регистрацию хуков до загрузки любого кода приложения, включая точку входа приложения; по умолчанию это также относится ко всем потокам-воркерам.

Программная регистрация хуков до запуска кода приложения

В качестве альтернативы registerHooks() можно вызвать из точки входа.

Если точке входа необходимо загрузить другие модули, а процесс загрузки требуется настроить, загружайте их с помощью require() или динамического import() после регистрации хуков. Не используйте статические инструкции import для загрузки модулей, требующих настройки, в том же модуле, где регистрируются хуки: статические инструкции import выполняются до запуска любого кода импортирующего модуля, включая вызов registerHooks(), независимо от положения статических инструкций import в импортирующем модуле.

Модули JavaScript
import { registerHooks } from 'node:module';

registerHooks({ /* implementation of synchronous hooks */ });

// If loaded using static import, the hooks would not be applied when loading
// my-app.mjs, because statically imported modules are all executed before its
// importer regardless of where the static import appears.
// import './my-app.mjs';

// my-app.mjs must be loaded dynamically to ensure the hooks are applied.
await import('./my-app.mjs');
CommonJS
const { registerHooks } = require('node:module');

registerHooks({ /* implementation of synchronous hooks */ });

import('./my-app.mjs');
// Or, if my-app.mjs does not have top-level await or it's a CommonJS module,
// require() can also be used:
// require('./my-app.mjs');
Регистрация хуков до запуска кода приложения с помощью URL data:

В качестве альтернативы встроенный код JavaScript можно поместить в URL data:, чтобы зарегистрировать хуки до запуска кода приложения. Например:

node --import 'data:text/javascript,import {registerHooks} from "node:module"; registerHooks(/* hooks code */);' ./my-app.js copy
Соглашения об использовании хуков и построении цепочек

Хуки являются частью цепочки, даже если она состоит всего из одного пользовательского хука и хука по умолчанию, который присутствует всегда.

Функции-хуки образуют вложенную структуру: каждая из них всегда должна возвращать обычный объект, а построение цепочки происходит, когда каждая функция вызывает next<hookName>() — ссылку на хук следующего загрузчика (в порядке LIFO).

Вызвать registerHooks() можно несколько раз:

Модули JavaScript
// entrypoint.mjs
import { registerHooks } from 'node:module';

const hook1 = { /* implementation of hooks */ };
const hook2 = { /* implementation of hooks */ };
// hook2 runs before hook1.
registerHooks(hook1);
registerHooks(hook2);
CommonJS
// entrypoint.cjs
const { registerHooks } = require('node:module');

const hook1 = { /* implementation of hooks */ };
const hook2 = { /* implementation of hooks */ };
// hook2 runs before hook1.
registerHooks(hook1);
registerHooks(hook2);

В этом примере зарегистрированные хуки образуют цепочки. Эти цепочки выполняются в порядке «последним пришёл — первым обслужен» (LIFO). Если и hook1, и hook2 определяют хук resolve, они будут вызваны следующим образом (обратите внимание на порядок справа налево: сначала hook2.resolve, затем hook1.resolve и после этого хук Node.js по умолчанию):

Хук resolve по умолчанию в Node.js ← hook1.resolve ← hook2.resolve

То же относится ко всем остальным хукам.

Если хук возвращает значение без обязательного свойства, возникает исключение. Исключение также возникает, если хук завершается, не вызвав next<hookName>() и не вернув shortCircuit: true. Эти ошибки помогают предотвратить непреднамеренное нарушение цепочки. Верните из хука shortCircuit: true, чтобы указать, что цепочка намеренно завершается на вашем хуке.

Если хук должен применяться при загрузке других модулей хуков, эти модули необходимо загружать после регистрации хука.

Функции-хуки, принимаемые module.registerHooks()
Добавлено в: v23.5.0, v22.15.0

Метод module.registerHooks() принимает следующие синхронные функции-хуки.

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

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

Синхронные хуки выполняются в том же потоке и в той же области выполнения, где загружаются модули; код функции-хука может передавать значения непосредственно используемым модулям через глобальные переменные или другие общие состояния.

В отличие от асинхронных хуков, синхронные хуки по умолчанию не наследуются дочерними потоками-воркерами. Однако, если хуки зарегистрированы с помощью файла, предварительно загруженного флагом --import или --require, дочерние потоки-воркеры могут наследовать предварительно загруженные скрипты благодаря наследованию process.execArgv. Подробнее см. в документации по Worker.

Синхронный resolve(specifier, context, nextResolve)
История
Версия Изменения
v23.5.0, v22.15.0

Добавлена поддержка синхронных хуков и хуков в том же потоке.

  • specifier <string>
  • context <Object>
    • conditions <string[]> Условия экспорта соответствующего package.json
    • importAttributes <Object> Объект, пары ключ-значение которого представляют атрибуты импортируемого модуля
    • parentURL <string> | <undefined> Модуль, импортирующий этот модуль, или undefined, если это точка входа Node.js
  • nextResolve <Function> Следующий хук resolve в цепочке или хук resolve Node.js по умолчанию после последнего пользовательского хука resolve
    • specifier <string>
    • context <Object> | <undefined> Если параметр не указан, используются значения по умолчанию. Если параметр указан, значения по умолчанию объединяются с ним, при этом приоритет имеют указанные свойства.
  • Возвращает: <Object>
    • format <string> | <null> | <undefined> Подсказка для хука load (она может быть проигнорирована). Это может быть формат модуля (например, 'commonjs' или 'module') либо произвольное значение, например 'css' или 'yaml'.
    • importAttributes <Object> | <undefined> Атрибуты импорта, используемые при кэшировании модуля (необязательно; если не указаны, будут использованы входные данные)
    • shortCircuit <undefined> | <boolean> Сигнал о том, что этот хук намерен завершить цепочку хуков resolve. По умолчанию: false
    • url <string> Абсолютный URL, в который разрешается этот входной параметр

Цепочка хуков resolve отвечает за указание Node.js, где найти и как кэшировать заданную инструкцию или выражение import либо вызов require. При необходимости она может вернуть формат (например, 'module') в качестве подсказки для хука load. Если формат указан, хук load в конечном счёте отвечает за предоставление окончательного значения format и может проигнорировать подсказку, предоставленную resolve. Если resolve возвращает format, требуется пользовательский хук load, даже если он только передаёт это значение стандартному хуку load Node.js.

Атрибуты типа импорта входят в ключ кэша, используемый для сохранения загруженных модулей во внутреннем кэше модулей. Хук resolve отвечает за возврат объекта importAttributes, если модуль следует кэшировать с атрибутами, отличающимися от указанных в исходном коде.

Свойство conditions в context представляет собой массив условий, используемых для сопоставления условий экспорта пакета для этого запроса разрешения. Их можно использовать для поиска условных соответствий в других местах или для изменения списка при вызове логики разрешения по умолчанию.

Текущие условия экспорта пакета всегда содержатся в массиве context.conditions, передаваемом хуку. Чтобы при вызове defaultResolve гарантировать поведение разрешения спецификаторов модулей Node.js по умолчанию, передаваемый ему массив context.conditions должен включать все элементы массива context.conditions, первоначально переданного хуку resolve.

import { registerHooks } from 'node:module';

function resolve(specifier, context, nextResolve) {
  // When calling `defaultResolve`, the arguments can be modified. For example,
  // to change the specifier or to add applicable export conditions.
  if (specifier.includes('foo')) {
    specifier = specifier.replace('foo', 'bar');
    return nextResolve(specifier, {
      ...context,
      conditions: [...context.conditions, 'another-condition'],
    });
  }

  // The hook can also skip default resolution and provide a custom URL.
  if (specifier === 'special-module') {
    return {
      url: 'file:///path/to/special-module.mjs',
      format: 'module',
      shortCircuit: true,  // This is mandatory if nextResolve() is not called.
    };
  }

  // If no customization is needed, 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);
}

registerHooks({ resolve }); copy
Синхронный load(url, context, nextLoad)
История
Версия Изменения
v23.5.0, v22.15.0

Добавлена поддержка синхронной версии и версии, работающей в том же потоке.

  • url <string> URL, возвращённый цепочкой resolve
  • context <Object>
    • conditions <string[]> Условия экспорта соответствующего package.json
    • format <string> | <null> | <undefined> Формат, при необходимости предоставленный цепочкой хуков resolve. В качестве входного значения может использоваться любая строка; входные значения не обязаны соответствовать списку допустимых возвращаемых значений, приведённому ниже.
    • importAttributes <Object>
  • nextLoad <Function> Следующий хук load в цепочке или хук load Node.js по умолчанию после последнего пользовательского хука load
    • url <string>
    • context <Object> | <undefined> Если параметр не указан, используются значения по умолчанию. Если параметр указан, значения по умолчанию объединяются с ним, при этом приоритет имеют указанные свойства. В поведении nextLoad по умолчанию параметр context.format обязателен, если модуль, на который указывает url, не содержит явных сведений о типе модуля.
  • Возвращает: <Object>
    • format <string> Один из допустимых форматов модулей, перечисленных ниже.
    • shortCircuit <undefined> | <boolean> Сигнал о том, что этот хук намерен завершить цепочку хуков load. По умолчанию: false
    • source <string> | <ArrayBuffer> | <TypedArray> Исходный код, который Node.js должен выполнить

Хук load позволяет определить пользовательский метод получения исходного кода для разрешённого URL. Это может позволить загрузчику избежать чтения файлов с диска. Также его можно использовать для преобразования нераспознанного формата в поддерживаемый, например yaml в module.

import { registerHooks } from 'node:module';
import { Buffer } from 'node:buffer';

function load(url, context, nextLoad) {
  // The hook can skip default loading and provide a custom source code.
  if (url === 'special-module') {
    return {
      source: 'export const special = 42;',
      format: 'module',
      shortCircuit: true,  // This is mandatory if nextLoad() is not called.
    };
  }

  // It's possible to modify the source code loaded by the next - possibly default - step,
  // for example, replacing 'foo' with 'bar' in the source code of the module.
  const result = nextLoad(url, context);
  const source = typeof result.source === 'string' ?
    result.source : Buffer.from(result.source).toString('utf8');
  return {
    source: source.replace(/foo/g, 'bar'),
    ...result,
  };
}

registerHooks({ resolve }); copy

В более сложных сценариях это также можно использовать для преобразования неподдерживаемого исходного кода в поддерживаемый (см. раздел Примеры ниже).

Допустимые итоговые форматы, возвращаемые load

Итоговое значение format должно быть одним из следующих:

format Описание Допустимые типы для source, возвращаемого load
'addon' Загрузить дополнение Node.js <null>
'builtin' Загрузить встроенный модуль Node.js <null>
'commonjs-typescript' Загрузить модуль CommonJS Node.js с синтаксисом TypeScript <string> | <ArrayBuffer> | <TypedArray> | <null> | <undefined>
'commonjs' Загрузить модуль CommonJS Node.js <string> | <ArrayBuffer> | <TypedArray> | <null> | <undefined>
'json' Загрузить файл JSON <string> | <ArrayBuffer> | <TypedArray>
'module-typescript' Загрузить модуль ES с синтаксисом TypeScript <string> | <ArrayBuffer> | <TypedArray>
'module' Загрузить модуль ES <string> | <ArrayBuffer> | <TypedArray>
'wasm' Загрузить модуль WebAssembly <ArrayBuffer> | <TypedArray>

Значение source игнорируется для формата 'builtin', поскольку в настоящее время заменить значение встроенного (основного) модуля Node.js невозможно.

Все эти типы соответствуют классам, определённым в ECMAScript.

  • Конкретный объект <ArrayBuffer> — это <SharedArrayBuffer>.
  • Конкретный объект <TypedArray> — это <Uint8Array>.

Если исходное значение текстового формата (то есть 'json', 'module') не является строкой, оно преобразуется в строку с помощью util.TextDecoder.

Асинхронные хуки настройки

Стабильность: 1.1 - Активная разработка
Ограничения асинхронных хуков настройки

У асинхронных хуков настройки есть множество ограничений, и неизвестно, удастся ли устранить связанные с ними проблемы. Чтобы избежать этих ограничений, пользователям рекомендуется вместо них использовать синхронные хуки настройки через module.registerHooks().

  • Асинхронные хуки выполняются в отдельном потоке, поэтому функции хуков не могут напрямую изменять глобальное состояние настраиваемых модулей. Обычно для передачи данных между потоками или управления ходом выполнения используют каналы сообщений и атомарные операции. См. раздел Обмен данными с асинхронными хуками настройки модулей.
  • Асинхронные хуки влияют не на все вызовы require() в графе модулей.
    • Пользовательские функции require, созданные с помощью module.createRequire(), не затрагиваются.
    • Если асинхронный хук load не переопределяет source для проходящих через него модулей CommonJS, дочерние модули, загружаемые этими модулями CommonJS через встроенный require(), также не будут затронуты асинхронными хуками.
  • При настройке модулей CommonJS асинхронным хукам необходимо учитывать несколько ограничений. Подробности см. в разделах асинхронный хук resolve и асинхронный хук load.
  • Когда асинхронные хуки настраивают вызовы require() внутри модулей CommonJS, Node.js может потребоваться несколько раз загрузить исходный код модуля CommonJS, чтобы обеспечить совместимость с существующими механизмами подмены CommonJS. Если код модуля изменится между загрузками, это может привести к неожиданному поведению.
    • В результате, если зарегистрированы и асинхронные, и синхронные хуки, а асинхронные хуки решают настроить модуль CommonJS, синхронные хуки могут вызываться несколько раз для вызовов require() в этом модуле CommonJS.
Регистрация асинхронных хуков настройки

Асинхронные хуки настройки регистрируются с помощью module.register(), которому передаётся путь или URL другого модуля, экспортирующего функции асинхронных хуков.

Как и registerHooks(), register() можно вызвать в модуле, предварительно загруженном с помощью --import или --require, либо непосредственно в точке входа.

Модули JavaScript
// Use module.register() to register asynchronous hooks in a dedicated thread.
import { register } from 'node:module';
register('./hooks.mjs', import.meta.url);

// If my-app.mjs is loaded statically here as `import './my-app.mjs'`, since ESM
// dependencies are evaluated before the module that imports them,
// it's loaded _before_ the hooks are registered above and won't be affected.
// To ensure the hooks are applied, dynamic import() must be used to load ESM
// after the hooks are registered.
import('./my-app.mjs');
CommonJS
const { register } = require('node:module');
const { pathToFileURL } = require('node:url');
// Use module.register() to register asynchronous hooks in a dedicated thread.
register('./hooks.mjs', pathToFileURL(__filename));

import('./my-app.mjs');

В hooks.mjs:

// hooks.mjs
export async function resolve(specifier, context, nextResolve) {
  /* implementation */
}
export async function load(url, context, nextLoad) {
  /* implementation */
} copy

В отличие от синхронных хуков, асинхронные хуки не будут выполняться для этих модулей, загруженных в файле, который вызывает register():

// register-hooks.js
import { register, createRequire } from 'node:module';
register('./hooks.mjs', import.meta.url);

// Asynchronous hooks does not affect modules loaded via custom require()
// functions created by module.createRequire().
const userRequire = createRequire(__filename);
userRequire('./my-app-2.cjs');  // Hooks won't affect this copy
// register-hooks.js
const { register, createRequire } = require('node:module');
const { pathToFileURL } = require('node:url');
register('./hooks.mjs', pathToFileURL(__filename));

// Asynchronous hooks does not affect modules loaded via built-in require()
// in the module calling `register()`
require('./my-app-2.cjs');  // Hooks won't affect this
// .. or custom require() functions created by module.createRequire().
const userRequire = createRequire(__filename);
userRequire('./my-app-3.cjs');  // Hooks won't affect this copy

Асинхронные хуки также можно зарегистрировать с помощью URL data: и флага --import:

node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("my-instrumentation", pathToFileURL("./"));' ./my-app.js copy
Цепочки асинхронных хуков настройки

Цепочки register() работают аналогично registerHooks(). Если используются и синхронные, и асинхронные хуки, синхронные хуки всегда выполняются первыми, до запуска асинхронных хуков. Иными словами, в последнем выполняемом синхронном хуке его следующий хук включает вызов асинхронных хуков.

Модули JavaScript
// entrypoint.mjs
import { register } from 'node:module';

register('./foo.mjs', import.meta.url);
register('./bar.mjs', import.meta.url);
await import('./my-app.mjs');
CommonJS
// 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');

Если foo.mjs и bar.mjs определяют хук resolve, вызовы будут выполняться в следующем порядке (обратите внимание: справа налево, начиная с ./bar.mjs, затем ./foo.mjs, после чего используется реализация Node.js по умолчанию):

Реализация Node.js по умолчанию ← ./foo.mjs ← ./bar.mjs

При использовании асинхронных хуков зарегистрированные хуки также влияют на последующие вызовы register, которые загружают модули хуков. В приведённом выше примере bar.mjs будет разрешён и загружен с помощью хуков, зарегистрированных через foo.mjs (поскольку хуки foo уже будут добавлены в цепочку). Это позволяет, например, писать хуки на языках, отличных от JavaScript, если ранее зарегистрированные хуки преобразуют их в JavaScript.

Метод register() нельзя вызывать из потока, выполняющего модуль хуков, экспортирующий асинхронные хуки, или его зависимостей.

Обмен данными с асинхронными хуками настройки модулей

Асинхронные хуки выполняются в выделенном потоке, отдельно от основного потока, в котором работает код приложения. Это означает, что изменение глобальных переменных не повлияет на другие потоки, а для обмена данными между потоками необходимо использовать каналы сообщений.

Метод register можно использовать для передачи данных хуку initialize. Передаваемые хуку данные могут включать передаваемые объекты, например порты.

Модули JavaScript
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);
});
port1.unref();

register('./my-hooks.mjs', {
  parentURL: import.meta.url,
  data: { number: 1, port: port2 },
  transferList: [port2],
});
CommonJS
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);
});
port1.unref();

register('./my-hooks.mjs', {
  parentURL: pathToFileURL(__filename),
  data: { number: 1, port: port2 },
  transferList: [port2],
});
Асинхронные хуки, принимаемые module.register()
История
Версия Изменения
v20.6.0, v18.19.0

Добавлен хук initialize вместо globalPreload.

v18.6.0, v16.17.0

Добавлена поддержка цепочек загрузчиков.

v16.12.0

Удалены getFormat, getSource, transformSource и globalPreload; добавлены хук load и хук getGlobalPreload.

v8.8.0

Добавлено в версии: v8.8.0

Метод 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.
} copy

Асинхронные хуки выполняются в отдельном потоке, изолированном от основного потока, в котором выполняется код приложения. Это означает, что они работают в другой области. Основной поток может в любой момент завершить поток хуков, поэтому не рассчитывайте на завершение асинхронных операций (например, console.log). По умолчанию они наследуются дочерними рабочими потоками.

initialize()
Добавлено в версии: v20.6.0, v18.19.0
  • data <any> Данные из register(loader, import.meta.url, { data }).

Хук initialize принимается только методом register. registerHooks() не поддерживает и не требует его, поскольку инициализацию синхронных хуков можно выполнить непосредственно перед вызовом registerHooks().

Хук initialize позволяет определить пользовательскую функцию, которая выполняется в потоке хуков при инициализации модуля хуков. Инициализация происходит при регистрации модуля хуков с помощью register.

Этот хук может получать данные из вызова register, включая порты и другие передаваемые объекты. Возвращаемым значением initialize может быть <Promise>; в этом случае основной поток приложения дождётся его выполнения, прежде чем продолжить работу.

Код настройки модулей:

// path-to-my-hooks.js

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

Код вызывающего модуля:

Модули JavaScript
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');
});
port1.unref();

register('./path-to-my-hooks.js', {
  parentURL: import.meta.url,
  data: { number: 1, port: port2 },
  transferList: [port2],
});
CommonJS
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');
});
port1.unref();

register('./path-to-my-hooks.js', {
  parentURL: pathToFileURL(__filename),
  data: { number: 1, port: port2 },
  transferList: [port2],
});
Асинхронный resolve(specifier, context, nextResolve)
История
Версия Изменения
v21.0.0, v20.10.0, v18.19.0

Свойство context.importAssertions заменено на context.importAttributes. Использование старого имени по-прежнему поддерживается и вызывает предупреждение об экспериментальном API.

v18.6.0, v16.17.0

Добавлена поддержка цепочек хуков разрешения. Каждый хук должен либо вызывать nextResolve(), либо возвращать объект со свойством shortCircuit, равным true.

v17.1.0, v16.14.0

Добавлена поддержка утверждений импорта.

  • specifier <string>
  • context <Object>
    • conditions <string[]> Условия экспорта для соответствующего package.json
    • importAttributes <Object> Объект, пары ключ-значение которого задают атрибуты импортируемого модуля
    • parentURL <string> | <undefined> Модуль, импортирующий этот модуль, или undefined, если это точка входа Node.js
  • nextResolve <Function> Следующий хук resolve в цепочке или хук resolve Node.js по умолчанию после последнего предоставленного пользователем хука resolve
    • specifier <string>
    • context <Object> | <undefined> Если значение не указано, используются значения по умолчанию. Если значение указано, значения по умолчанию объединяются с ним, причём приоритет имеют указанные свойства.
  • Возвращает: <Object> | <Promise> Асинхронная версия принимает либо объект со следующими свойствами, либо Promise, который разрешится в такой объект.
    • format <string> | <null> | <undefined> Подсказка для хука load (она может быть проигнорирована). Это может быть формат модуля (например, 'commonjs' или 'module') либо произвольное значение, например 'css' или 'yaml'.
    • importAttributes <Object> | <undefined> Атрибуты импорта, используемые при кэшировании модуля (необязательно; если не указаны, будут использованы входные данные)
    • shortCircuit <undefined> | <boolean> Сигнал о том, что этот хук намерен завершить цепочку хуков resolve. По умолчанию: false
    • url <string> Абсолютный URL, в который разрешается этот входной параметр

Асинхронная версия работает аналогично синхронной, за исключением того, что функция nextResolve возвращает Promise, а сам хук resolve может возвращать Promise.

Предупреждение В асинхронной версии, несмотря на поддержку возвращаемых промисов и асинхронных функций, вызовы resolve всё ещё могут блокировать основной поток, что может повлиять на производительность.

Предупреждение Хук resolve, вызываемый для вызовов require() внутри модулей CommonJS, настраиваемых асинхронными хуками, не получает исходный спецификатор, переданный в require(). Вместо него он получает URL, уже полностью разрешённый с помощью стандартного механизма разрешения CommonJS.

Предупреждение В модулях CommonJS, настраиваемых асинхронными хуками настройки, require.resolve() и require() будут использовать условие экспорта "import" вместо "require", что может привести к неожиданному поведению при загрузке двойных пакетов.

export async function resolve(specifier, context, nextResolve) {
  // When calling `defaultResolve`, the arguments can be modified. For example,
  // to change the specifier or add conditions.
  if (specifier.includes('foo')) {
    specifier = specifier.replace('foo', 'bar');
    return nextResolve(specifier, {
      ...context,
      conditions: [...context.conditions, 'another-condition'],
    });
  }

  // The hook can also skips default resolution and provide a custom URL.
  if (specifier === 'special-module') {
    return {
      url: 'file:///path/to/special-module.mjs',
      format: 'module',
      shortCircuit: true,  // This is mandatory if not calling nextResolve().
    };
  }

  // If no customization is needed, 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);
} copy
Асинхронный load(url, context, nextLoad)
История
Версия Изменения
v22.6.0

Добавлена поддержка source с форматом commonjs-typescript и module-typescript.

v20.6.0

Добавлена поддержка source с форматом commonjs.

v18.6.0, v16.17.0

Добавлена поддержка цепочек хуков загрузки. Каждый хук должен либо вызывать nextLoad(), либо возвращать объект со свойством shortCircuit, равным true.

  • url <string> URL, возвращённый цепочкой resolve
  • context <Object>
    • conditions <string[]> Условия экспорта для соответствующего package.json
    • format <string> | <null> | <undefined> Формат, необязательно предоставляемый цепочкой хуков resolve. На вход можно передать строку любого значения; входные значения не обязаны соответствовать списку допустимых возвращаемых значений, описанному ниже.
    • importAttributes <Object>
  • nextLoad <Function> Следующий хук load в цепочке или хук load Node.js по умолчанию после последнего предоставленного пользователем хука load
    • url <string>
    • context <Object> | <undefined> Если значение не указано, используются значения по умолчанию. Если значение указано, значения по умолчанию объединяются с ним, причём приоритет имеют указанные свойства. В реализации nextLoad по умолчанию свойство context.format обязательно, если модуль, на который указывает url, не содержит явных сведений о типе модуля.
  • Возвращает: <Promise> Асинхронная версия принимает либо объект со следующими свойствами, либо Promise, который разрешится в такой объект.
    • format <string>
    • shortCircuit <undefined> | <boolean> Сигнал о том, что этот хук намерен завершить цепочку хуков load. По умолчанию: false
    • source <string> | <ArrayBuffer> | <TypedArray> Исходный код, который должен выполнить Node.js

Предупреждение: Асинхронный хук load несовместим с именованными экспортами из модулей CommonJS. При их совместном использовании импорт вернёт пустой объект. В будущем это ограничение может быть устранено. Оно не относится к синхронному хуку load, при использовании которого экспорты работают как обычно.

Асинхронная версия работает аналогично синхронной, однако при использовании асинхронного хука load указание или пропуск source для 'commonjs' приводит к совершенно разным результатам:

  • Если указано значение source, все вызовы require из этого модуля будут обрабатываться загрузчиком ESM с зарегистрированными хуками resolve и load; все вызовы require.resolve из этого модуля будут обрабатываться загрузчиком ESM с зарегистрированными хуками resolve; будет доступна только часть API CommonJS (например, без require.extensions, без require.cache, без require.resolve.paths), а подмена загрузчика модулей CommonJS применяться не будет.
  • Если source имеет значение undefined или null, модуль будет обрабатываться загрузчиком CommonJS, а вызовы require/require.resolve не будут проходить через зарегистрированные хуки. Такое поведение для пустых значений source временное — в будущем пустые значения source не будут поддерживаться.

Эти ограничения не относятся к синхронному хуку load: в этом случае настраиваемым модулям CommonJS доступен полный набор API CommonJS, а вызовы require/require.resolve всегда проходят через зарегистрированные хуки.

Внутренняя асинхронная реализация load в Node.js, являющаяся значением next для последнего хука в цепочке load, возвращает null для source, если format имеет значение 'commonjs', для обеспечения обратной совместимости. Ниже приведён пример хука, который явно включает нестандартное поведение:

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

// Asynchronous version accepted by module.register(). This fix is not needed
// for the synchronous version accepted by module.registerHooks().
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;
} copy

Это также не относится к синхронному хуку load: в этом случае возвращаемое значение source содержит исходный код, загруженный следующим хуком, независимо от формата модуля.

Примеры

Различные хуки настройки модулей можно использовать совместно, чтобы добиться широкого спектра изменений в поведении Node.js при загрузке и выполнении кода.

Импорт по HTTPS

Приведённый ниже хук регистрирует хуки, обеспечивающие базовую поддержку таких спецификаторов. Хотя это может показаться значительным улучшением функциональности ядра 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);
} copy
// main.mjs
import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js';

console.log(VERSION); copy

С указанным выше модулем хуков запуск node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./https-hooks.mjs"));' ./main.mjs выводит текущую версию CoffeeScript согласно модулю по URL из main.mjs.

Транспиляция

Исходный код в форматах, которые Node.js не понимает, можно преобразовать в JavaScript с помощью хука load.

Этот способ менее производителен, чем транспиляция исходных файлов до запуска Node.js; хуки транспилятора следует использовать только для разработки и тестирования.

Асинхронная версия
// coffeescript-hooks.mjs
import { readFile } from 'node:fs/promises';
import { findPackageJSON } from 'node:module';
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. Use a custom format
    // to tell Node.js not to detect its module type.
    const { source: rawSource } = await nextLoad(url, { ...context, format: 'coffee' });
    // This hook converts CoffeeScript source code into JavaScript source code
    // for all imported CoffeeScript files.
    const transformedSource = coffeescript.compile(rawSource.toString(), url);

    // To determine how Node.js would interpret the transpilation result,
    // search up the file system for the nearest parent package.json file
    // and read its "type" field.
    return {
      format: await getPackageType(url),
      shortCircuit: true,
      source: transformedSource,
    };
  }

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

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 pJson = findPackageJSON(url);

  return readFile(pJson, 'utf8')
    .then(JSON.parse)
    .then((json) => json?.type)
    .catch(() => undefined);
} copy
Синхронная версия
// coffeescript-sync-hooks.mjs
import { readFileSync } from 'node:fs';
import { registerHooks, findPackageJSON } from 'node:module';
import coffeescript from 'coffeescript';

const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/;

function load(url, context, nextLoad) {
  if (extensionsRegex.test(url)) {
    const { source: rawSource } = nextLoad(url, { ...context, format: 'coffee' });
    const transformedSource = coffeescript.compile(rawSource.toString(), url);

    return {
      format: getPackageType(url),
      shortCircuit: true,
      source: transformedSource,
    };
  }

  return nextLoad(url, context);
}

function getPackageType(url) {
  const pJson = findPackageJSON(url);
  if (!pJson) {
    return undefined;
  }
  try {
    const file = readFileSync(pJson, 'utf-8');
    return JSON.parse(file)?.type;
  } catch {
    return undefined;
  }
}

registerHooks({ load }); copy
Запуск хуков
# 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}" copy
# scream.coffee
export scream = (str) -> str.toUpperCase() copy

Для запуска этого примера добавьте файл package.json, содержащий тип модуля для файлов CoffeeScript.

{
  "type": "module"
} copy

Это нужно только для запуска примера. В реальных загрузчиках getPackageType() должен возвращать format, известный Node.js, даже если в package.json явно не указан тип; в противном случае вызов nextLoad выбросит ERR_UNKNOWN_FILE_EXTENSION (если значение не определено) или ERR_UNKNOWN_MODULE_FORMAT (если это неизвестный формат, не указанный в документации хука загрузки).

С указанными выше модулями хуков запуск node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee или node --import ./coffeescript-sync-hooks.mjs ./main.coffee приводит к преобразованию main.coffee в JavaScript после загрузки исходного кода с диска, но до его выполнения Node.js; то же самое происходит со всеми файлами .coffee, .litcoffee или .coffee.md, на которые ссылаются операторы import в загруженных файлах.

Карты импорта

В двух предыдущих примерах были определены хуки load. Ниже приведён пример хука resolve. Этот модуль хуков читает файл import-map.json, в котором указано, какие спецификаторы следует перенаправить на другие URL (это очень упрощённая реализация небольшой части спецификации «карт импорта»).

Асинхронная версия
// 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);
} copy
Синхронная версия
// 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 }); copy
Использование хуков

С этими файлами:

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

Запуск node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./import-map-hooks.js"));' main.js или node --import ./import-map-sync-hooks.js main.js должен вывести some module!.

Поддержка карт исходного кода

Добавлено в: v13.7.0, v12.17.0
Стабильность: 1 - Экспериментальный

Node.js поддерживает формат карты исходного кода TC39 ECMA-426 (ранее он назывался форматом карты исходного кода версии 3).

API в этом разделе — вспомогательные средства для работы с кэшем карт исходного кода. Этот кэш заполняется, когда включён разбор карт исходного кода и в конце модуля обнаружены директивы подключения карт исходного кода.

Чтобы включить разбор карт исходного кода, Node.js необходимо запустить с флагом --enable-source-maps, включить сбор покрытия кода, задав NODE_V8_COVERAGE=dir, либо включить его программно с помощью module.setSourceMapsSupport().

Модули JavaScript
// module.mjs
// In an ECMAScript module
import { findSourceMap, SourceMap } from 'node:module';
CommonJS
// module.cjs
// In a CommonJS module
const { findSourceMap, SourceMap } = require('node:module');

module.getSourceMapsSupport()

Добавлено в: v23.7.0, v22.14.0
  • Возвращает: <Object>
    • enabled <boolean> Включена ли поддержка карт исходного кода
    • nodeModules <boolean> Включена ли поддержка для файлов в node_modules.
    • generatedCode <boolean> Включена ли поддержка для сгенерированного кода из eval или new Function.

Этот метод возвращает информацию о том, включена ли поддержка Source Map v3 для трассировок стека.

module.findSourceMap(path)

Добавлено в: v13.7.0, v12.17.0
  • path <string>
  • Возвращает: <module.SourceMap> | <undefined> Возвращает module.SourceMap, если карта исходного кода найдена, и undefined в противном случае.

path — разрешённый путь к файлу, для которого следует получить соответствующую карту исходного кода.

module.setSourceMapsSupport(enabled[, options])

Добавлено в: v23.7.0, v22.14.0
  • enabled <boolean> Включить поддержку карт исходного кода.
  • options <Object> Необязательный параметр
    • nodeModules <boolean> Включить ли поддержку для файлов в node_modules. По умолчанию: false.
    • generatedCode <boolean> Включить ли поддержку для сгенерированного кода из eval или new Function. По умолчанию: false.

Эта функция включает или отключает поддержку Source Map v3 для трассировок стека.

Она предоставляет те же возможности, что и запуск процесса Node.js с параметрами командной строки --enable-source-maps, а также дополнительные параметры, позволяющие изменить поддержку файлов в node_modules или сгенерированного кода.

Будут разобраны и загружены только карты исходного кода из файлов JavaScript, загруженных после включения поддержки карт исходного кода. Рекомендуется использовать параметры командной строки --enable-source-maps, чтобы не потерять карты исходного кода модулей, загруженных до вызова этого API.

Класс: module.SourceMap

Добавлено в: v13.7.0, v12.17.0
new SourceMap(payload[, { lineLengths }])
История
Версия Изменения
v20.5.0

Добавлена поддержка lineLengths.

  • payload <Object>
  • lineLengths <number[]>

Создаёт новый экземпляр sourceMap.

payload — это объект с ключами, соответствующими формату карты исходного кода:

  • file <string>
  • version <number>
  • sources <string[]>
  • sourcesContent <string[]>
  • names <string[]>
  • mappings <string>
  • sourceRoot <string>

lineLengths — это необязательный массив, содержащий длину каждой строки сгенерированного кода.

sourceMap.payload
  • Возвращает: <Object>

Геттер для данных, использованных при создании экземпляра SourceMap.

sourceMap.findEntry(lineOffset, columnOffset)
  • lineOffset <number> Смещение номера строки в сгенерированном исходном коде с нумерацией от нуля
  • columnOffset <number> Смещение номера столбца в сгенерированном исходном коде с нумерацией от нуля
  • Возвращает: <Object>

При заданных смещении строки и смещении столбца в файле сгенерированного исходного кода возвращает объект, представляющий диапазон SourceMap в исходном файле, если он найден, или пустой объект, если нет.

Возвращаемый объект содержит следующие ключи:

  • generatedLine <number> Смещение строки, в которой начинается диапазон в сгенерированном исходном коде
  • generatedColumn <number> Смещение столбца, в котором начинается диапазон в сгенерированном исходном коде
  • originalSource <string> Имя файла исходного кода, указанное в SourceMap
  • originalLine <number> Смещение строки, в которой начинается диапазон в исходном коде
  • originalColumn <number> Смещение столбца, в котором начинается диапазон в исходном коде
  • name <string>

Возвращаемое значение представляет исходный диапазон в том виде, в каком он указан в SourceMap, и использует смещения с нумерацией от нуля, а не номера строк и столбцов с нумерацией от единицы, используемые в сообщениях Error и объектах CallSite.

Чтобы получить соответствующие номера строк и столбцов с нумерацией от единицы по значениям lineNumber и columnNumber, указанным в стеке Error и объектах CallSite, используйте sourceMap.findOrigin(lineNumber, columnNumber)

sourceMap.findOrigin(lineNumber, columnNumber)
Добавлено в: v20.4.0, v18.18.0
  • lineNumber <number> Номер строки места вызова в сгенерированном исходном коде с нумерацией от единицы
  • columnNumber <number> Номер столбца места вызова в сгенерированном исходном коде с нумерацией от единицы
  • Возвращает: <Object>

По заданным lineNumber и columnNumber с нумерацией от единицы для места вызова в сгенерированном исходном коде находит соответствующее место вызова в исходном коде.

Если заданные lineNumber и columnNumber не найдены ни в одной карте исходного кода, возвращается пустой объект. В противном случае возвращаемый объект содержит следующие ключи:

  • name <string> | <undefined> Имя диапазона в карте исходного кода, если оно указано
  • fileName <string> Имя файла исходного кода, указанное в SourceMap
  • lineNumber <number> Номер строки соответствующего места вызова в исходном коде с нумерацией от единицы
  • columnNumber <number> Номер столбца соответствующего места вызова в исходном коде с нумерацией от единицы

© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/dist/latest-v24.x/docs/api/module.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API