Spec-Zone.ru › Node.js

Модули: node:module API

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

Объект Module

  • Тип: <Object>

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

module.builtinModules

Добавлено в: v9.3.0, v8.10.0, v6.13.0
История
Версия Изменения
v23.5.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');
mjscopy

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 не влияют на разрешение. В будущем это может измениться.

Модули 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
mjscopy

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

Добавлено в: v20.6.0, v18.19.0Устарело в: v25.9.0
История
Версия Изменения
v26.0.0

Устаревание во время выполнения (DEP0205).

v23.6.1, v22.13.1, v20.18.2

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

v20.8.0, v18.19.0

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

Стабильность: 0 — Устарело: используйте вместо этого module.registerHooks().

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

Добавлено в: v23.5.0, v22.15.0
История
Версия Изменения
v25.4.0, v24.13.1

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

Стабильность: 1.2 — Кандидат на выпуск

  • options <Object>
    • load <Function> | <undefined> См. хук load. По умолчанию: undefined.
    • resolve <Function> | <undefined> См. хук resolve. По умолчанию: undefined.
  • Возвращает: <Object> Объект со следующим свойством:
    • deregister() <Function> Удаляет зарегистрированные хуки, чтобы они больше не вызывались. В противном случае хуки сохраняются до завершения текущего процесса.

Регистрирует хуки, настраивающие разрешение и загрузку модулей Node.js. См. раздел Хуки настройки. Возвращённый объект можно использовать, чтобы отменить регистрацию хуков.

module.stripTypeScriptTypes(code[, options])

Добавлено в: v23.2.0, v22.13.0
История
Версия Изменения
v26.0.0

Удалены параметры transform и sourceMap.

Стабильность: 1.2 — Кандидат на выпуск

  • code <string> Код, из которого нужно удалить аннотации типов.
  • options <Object>
    • mode <string> По умолчанию: 'strip'. Возможные значения:
      • 'strip' Удалять только аннотации типов, не преобразуя возможности TypeScript.
    • sourceUrl <string> Указывает исходный URL, используемый в карте исходного кода.
  • Возвращает: <string> Код с удалёнными аннотациями типов.

module.stripTypeScriptTypes() удаляет аннотации типов из кода TypeScript. Её можно использовать для удаления аннотаций типов из кода TypeScript перед его запуском с помощью vm.runInContext() или vm.compileFunction().

По умолчанию будет выброшена ошибка, если код содержит возможности TypeScript, требующие преобразования, например enums. Дополнительные сведения см. в разделе удаление типов.

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

Модули 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;

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

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

Добавлено в: v22.1.0
История
Версия Изменения
v22.8.0

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

Кэш компиляции модулей можно включить с помощью метода 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; не следует считать его совместимым с другими версиями 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 });
    jscopy
  2. Задав переменную среды: NODE_COMPILE_CACHE_PORTABLE=1

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

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

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

module.constants.compileCacheStatus

Добавлено в: v22.8.0
История
Версия Изменения
v25.4.0

Эта возможность больше не является экспериментальной.

Следующие константы возвращаются в качестве поля 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])

Добавлено в: v22.8.0
История
Версия Изменения
v25.4.0

Эта возможность больше не является экспериментальной.

v25.0.0, v24.12.0

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

v25.0.0, v24.12.0

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

  • 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
История
Версия Изменения
v25.4.0

Эта возможность больше не является экспериментальной.

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

module.getCompileCacheDir()

Добавлено в: v22.8.0
История
Версия Изменения
v25.4.0

Эта возможность больше не является экспериментальной.

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

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

Добавлено в: v8.8.0
История
Версия Изменения
v25.4.0, 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.

В настоящее время 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
bashcopy

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

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

В этом случае 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
bashcopy
Соглашение об использовании хуков и цепочки вызовов

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

Функции хуков образуют вложенную структуру: каждая из них всегда должна возвращать обычный объект, а цепочка вызовов формируется, когда каждая функция вызывает 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 из хука, чтобы указать, что цепочка намеренно завершается на вашем хуке.

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

Отмена регистрации синхронных хуков настройки

У объекта, возвращаемого registerHooks(), есть метод deregister(), который можно использовать для удаления хуков из цепочки. После вызова deregister() хуки больше не будут вызываться при разрешении или загрузке модулей.

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

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

const hooks = registerHooks({
  resolve(specifier, context, nextResolve) {
    console.log('resolve hook called for', specifier);
    return nextResolve(specifier, context);
  },
  load(url, context, nextLoad) {
    return nextLoad(url, context);
  },
});

// At this point, the hooks are active and will be called for
// any subsequent import() or require() calls.
await import('./my-module.mjs');

// Later, remove the hooks from the chain.
hooks.deregister();

// Subsequent loads will no longer trigger the hooks.
await import('./another-module.mjs');
CommonJS
const { registerHooks } = require('node:module');

const hooks = registerHooks({
  resolve(specifier, context, nextResolve) {
    console.log('resolve hook called for', specifier);
    return nextResolve(specifier, context);
  },
  load(url, context, nextLoad) {
    return nextLoad(url, context);
  },
});

// At this point, the hooks are active and will be called for
// any subsequent require() calls.
require('./my-module.cjs');

// Later, remove the hooks from the chain.
hooks.deregister();

// Subsequent loads will no longer trigger the hooks.
require('./another-module.cjs');
Функции хуков, принимаемые 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.
}
mjscopy

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

В отличие от асинхронных хуков, синхронные хуки по умолчанию не наследуются дочерними рабочими потоками. Однако если хуки зарегистрированы с помощью файла, загруженного заранее через --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 });
mjscopy
Синхронный 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, если модуль, на который указывает url, не содержит явной информации о типе модуля, обязательно указать context.format.
  • Возвращает: <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({ load });
mjscopy

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

Допустимые итоговые форматы, возвращаемые 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 */
}
mjscopy

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

Модули JavaScript
// 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(import.meta.filename);
userRequire('./my-app-2.cjs');  // Hooks won't affect this
CommonJS
// 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

Асинхронные хуки также можно зарегистрировать с помощью 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
bashcopy
Цепочки асинхронных хуков настройки

Цепочки 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()
Добавлено в: v8.8.0
История
Версия Изменения
v20.6.0, v18.19.0

Добавлен хук initialize, заменяющий globalPreload.

v18.6.0, v16.17.0

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

v16.12.0

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

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

Асинхронные хуки выполняются в отдельном потоке, изолированном от основного потока, в котором выполняется код приложения. Это означает, что они работают в другой области. Основной поток может в любой момент завершить поток хуков, поэтому не рассчитывайте на завершение асинхронных операций (например, 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}`);
}
mjscopy

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

Модули 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. Использование старого имени по-прежнему поддерживается и вызывает предупреждение об экспериментальной функциональности.

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);
}
mjscopy
Асинхронный 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, если модуль, на который указывает url, не содержит явных сведений о типе модуля, параметр context.format обязателен.
  • Возвращает: <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;
}
mjscopy

Это также не относится к синхронному хуку 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);
}
mjscopy
// main.mjs
import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js';

console.log(VERSION);
mjscopy

С указанным выше модулем хуков запуск 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);
}
mjscopy
Синхронная версия
// 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 });
mjscopy
Запуск хуков
# 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}"
coffeecopy
# scream.coffee
export scream = (str) -> str.toUpperCase()
coffeecopy

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

{
  "type": "module"
}
jsoncopy

Это требуется только для запуска примера. В реальных загрузчиках 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);
}
mjscopy
Синхронная версия
// 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 });
mjscopy
Использование хуков

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

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

Запуск 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.

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

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.

Эта функция включает или отключает поддержку карт исходного кода версии 3 для трассировок стека.

Она предоставляет те же возможности, что и запуск процесса 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>

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

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

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

Возвращаемое значение представляет необработанный диапазон, указанный в карте исходного кода, на основе смещений с нумерацией с нуля, а не номеров строк и столбцов с нумерацией с единицы, используемых в сообщениях об ошибках и объектах 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> Имя файла исходного кода, указанное в карте исходного кода
  • 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/api/module.html

Spec-Zone.ru

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