Модули: node:module API
Объект Module
- Тип: <Object>
Предоставляет общие вспомогательные методы для работы с экземплярами Module — переменной module, часто встречающейся в модулях CommonJS. Доступ к ней осуществляется через import 'node:module' или require('node:module').
module.builtinModules
- Тип: <string[]>
Список имён всех модулей, предоставляемых Node.js. Его можно использовать, чтобы проверить, поддерживается ли модуль сторонним разработчиком.
Примечание: список не содержит модули, для которых обязателен префикс, например node:test.
module в этом контексте — не тот же объект, который предоставляется обёрткой модуля. Чтобы получить к нему доступ, используйте require для модуля 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)
-
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.constants.compileCacheStatus
Следующие константы возвращаются в качестве поля 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([cacheDir])
-
cacheDir<string> | <undefined> Необязательный путь к каталогу, в котором будет храниться кэш компиляции или из которого он будет считываться. - Возвращает: <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.
Если cacheDir не задан, Node.js использует каталог, указанный в переменной окружения NODE_COMPILE_CACHE=dir, если она задана, либо path.join(os.tmpdir(), 'node-compile-cache') в противном случае. В большинстве случаев рекомендуется вызывать module.enableCompileCache(), не указывая cacheDir, чтобы при необходимости каталог можно было переопределить с помощью переменной окружения NODE_COMPILE_CACHE.
Поскольку кэш компиляции — это незаметная оптимизация, не требуемая для работы приложения, этот метод не выбрасывает исключение, если включить кэш компиляции не удаётся. Вместо этого он возвращает объект, содержащий сообщение об ошибке в поле message, чтобы упростить отладку. Если кэш компиляции включён успешно, поле directory возвращаемого объекта содержит путь к каталогу, в котором хранится кэш компиляции. В поле status возвращаемого объекта будет одно из значений module.constants.compileCacheStatus, указывающее результат попытки включить кэш компиляции модулей.
Этот метод влияет только на текущий экземпляр Node.js. Чтобы включить кэш в дочерних рабочих потоках, вызовите этот метод и в них либо задайте для process.env.NODE_COMPILE_CACHE путь к каталогу кэша компиляции, чтобы дочерние рабочие потоки унаследовали это поведение. Каталог можно получить из поля directory, возвращаемого этим методом, или с помощью module.getCompileCacheDir().
Кэш компиляции модулей
Кэш компиляции модулей можно включить с помощью метода module.enableCompileCache() или переменной окружения NODE_COMPILE_CACHE=dir. После включения кэша при компиляции Node.js модулей CommonJS или ECMAScript будет использовать сохранённый на диске кэш кода V8 из указанного каталога, чтобы ускорить компиляцию. Это может замедлить первую загрузку графа модулей, но последующие загрузки того же графа могут выполняться значительно быстрее, если содержимое модулей не изменилось.
Чтобы очистить созданный на диске кэш компиляции, достаточно удалить каталог кэша. При следующем использовании того же каталога для хранения кэша компиляции он будет создан заново. Чтобы не заполнять диск устаревшими данными кэша, рекомендуется использовать каталог внутри os.tmpdir(). Если кэш компиляции включён вызовом module.enableCompileCache() без указания каталога, Node.js использует переменную окружения NODE_COMPILE_CACHE=dir, если она задана, либо path.join(os.tmpdir(), 'node-compile-cache') по умолчанию. Чтобы узнать каталог кэша компиляции, используемый работающим экземпляром Node.js, вызовите module.getCompileCacheDir().
При использовании кэша компиляции вместе с покрытием кода JavaScript V8 данные о покрытии, собираемые V8, могут быть менее точными для функций, десериализованных из кэша кода. Для получения точных данных о покрытии рекомендуется отключать кэш при запуске тестов.
Включённый кэш компиляции модулей можно отключить с помощью переменной окружения NODE_DISABLE_COMPILE_CACHE=1. Это может быть полезно, если кэш компиляции приводит к неожиданному или нежелательному поведению (например, к снижению точности покрытия кода в тестах).
Кэш компиляции, созданный одной версией Node.js, нельзя использовать в другой версии Node.js. Если для хранения кэша используется один и тот же базовый каталог, кэши, созданные разными версиями Node.js, сохраняются отдельно и могут сосуществовать.
В настоящее время при включённом кэше компиляции и первой загрузке модуля кэш кода создаётся сразу после компиляции, но записывается на диск только перед завершением работы экземпляра Node.js. Это поведение может измениться. Метод module.flushCompileCache() позволяет принудительно записать накопленный кэш кода на диск, если приложение собирается запустить другие экземпляры Node.js и предоставить им доступ к кэшу задолго до завершения работы родительского экземпляра.
module.getCompileCacheDir()
- Возвращает: <string> | <undefined> Путь к каталогу кэша компиляции модулей, если он включён, либо
undefinedв противном случае.
module.findPackageJSON(specifier[, base])
-
specifier<string> | <URL> Спецификатор модуля, для которого нужно получитьpackage.json. Если передан необработанный спецификатор, возвращаетсяpackage.jsonв корне пакета. Если передан относительный спецификатор или абсолютный спецификатор, возвращается ближайший родительскийpackage.json. -
base<string> | <URL> Абсолютное местоположение (строка URLfile:или путь файловой системы) содержащего модуля. Для 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.register(specifier[, parentURL][, options])
-
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)
-
options<Object>-
load<Function> | <undefined> См. хук загрузки. По умолчанию:undefined. -
resolve<Function> | <undefined> См. хук разрешения. По умолчанию:undefined.
-
Регистрирует хуки, которые настраивают разрешение модулей Node.js и поведение при их загрузке. См. раздел Хуки настройки.
module.stripTypeScriptTypes(code[, options])
-
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. Её можно использовать для удаления аннотаций типов из кода TypeScript перед его запуском с помощьюvm.runInContext()илиvm.compileFunction(). По умолчанию функция выдаёт ошибку, если код содержит возможности TypeScript, требующие преобразования, напримерEnums; дополнительную информацию см. в разделе удаление типов. Если режим имеет значение'transform', функция также преобразует возможности TypeScript в JavaScript; дополнительную информацию см. в разделе преобразование возможностей TypeScript. Если режим имеет значение'strip', карты исходного кода не создаются, поскольку позиции сохраняются. Если заданоsourceMap, при режиме'strip'будет выдана ошибка.
ПРЕДУПРЕЖДЕНИЕ: Результат работы этой функции не следует считать неизменным в разных версиях 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;Если 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()
Метод 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 Кэш компиляции модулей
Кэш компиляции модулей можно включить с помощью метода module.enableCompileCache() или переменной среды NODE_COMPILE_CACHE=dir. После включения кэша при компиляции Node.js модуля CommonJS или ECMAScript будет использовать сохранённый на диске кэш кода V8 из указанного каталога для ускорения компиляции. Это может замедлить первую загрузку графа модулей, но последующие загрузки того же графа могут выполняться значительно быстрее, если содержимое модулей не изменилось.
Чтобы очистить созданный кэш компиляции на диске, просто удалите каталог кэша. При следующем использовании того же каталога для хранения кэша компиляции он будет создан заново. Чтобы не заполнять диск устаревшим кэшем, рекомендуется использовать каталог внутри os.tmpdir(). Если кэш компиляции включён вызовом метода module.enableCompileCache() без указания каталога, Node.js будет использовать переменную среды NODE_COMPILE_CACHE=dir, если она задана, а в противном случае — path.join(os.tmpdir(), 'node-compile-cache'). Чтобы определить каталог кэша компиляции, используемый работающим экземпляром Node.js, воспользуйтесь module.getCompileCacheDir().
В настоящее время при использовании кэша компиляции вместе с покрытием кода JavaScript V8 покрытие, собираемое V8, может быть менее точным для функций, десериализованных из кэша кода. Для получения точных данных о покрытии при запуске тестов рекомендуется отключать эту функцию.
Включённый кэш компиляции модулей можно отключить с помощью переменной среды NODE_DISABLE_COMPILE_CACHE=1. Это может быть полезно, если кэш компиляции приводит к неожиданному или нежелательному поведению (например, к снижению точности покрытия тестами).
Кэш компиляции, созданный одной версией Node.js, нельзя использовать с другой версией Node.js. Если для хранения кэша используется один и тот же базовый каталог, кэши, созданные разными версиями Node.js, будут храниться отдельно и смогут сосуществовать.
В настоящее время, когда кэш компиляции включён и модуль загружается впервые, кэш кода создаётся сразу после компиляции, но записывается на диск только перед завершением работы экземпляра Node.js. Это может измениться. Метод module.flushCompileCache() позволяет записать накопленный кэш кода на диск, если приложение хочет запустить другие экземпляры Node.js и предоставить им общий доступ к кэшу задолго до завершения работы родительского экземпляра.
module.constants.compileCacheStatus
Следующие константы возвращаются в поле 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([cacheDir])
-
cacheDir<string> | <undefined> Необязательный путь к каталогу, в котором будет храниться или из которого будет загружаться кэш компиляции. - Возвращает: <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.
Если cacheDir не указан, Node.js будет использовать каталог, заданный переменной среды NODE_COMPILE_CACHE=dir, если она установлена, а в противном случае — path.join(os.tmpdir(), 'node-compile-cache'). Для большинства сценариев рекомендуется вызывать module.enableCompileCache() без указания cacheDir, чтобы при необходимости каталог можно было переопределить с помощью переменной среды NODE_COMPILE_CACHE.
Поскольку кэш компиляции — это незаметная оптимизация, не являющаяся необходимым условием работы приложения, этот метод не выбрасывает исключение, если включить кэш компиляции не удаётся. Вместо этого он возвращает объект, поле message которого содержит сообщение об ошибке для упрощения отладки. Если кэш компиляции успешно включён, поле directory возвращённого объекта содержит путь к каталогу, в котором хранится кэш компиляции. Поле status возвращённого объекта будет содержать одно из значений module.constants.compileCacheStatus, указывающих результат попытки включить кэш компиляции модулей.
Этот метод влияет только на текущий экземпляр Node.js. Чтобы включить кэш в дочерних рабочих потоках, вызовите этот метод и в них либо задайте для переменной process.env.NODE_COMPILE_CACHE путь к каталогу кэша компиляции, чтобы дочерние рабочие потоки унаследовали это поведение. Каталог можно получить из поля directory, возвращённого этим методом, или с помощью module.getCompileCacheDir().
module.flushCompileCache()
Записывает на диск кэш компиляции модулей, накопленный для уже загруженных в текущем экземпляре Node.js модулей. Метод возвращает управление после завершения всех операций записи в файловую систему — независимо от того, прошли ли они успешно. При возникновении ошибок метод завершится без сообщений, поскольку отсутствие записей в кэше компиляции не должно мешать фактической работе приложения.
module.getCompileCacheDir()
- Возвращает: <string> | <undefined> Путь к каталогу кэша компиляции модулей, если он включён, или
undefinedв противном случае.
Хуки настройки
В настоящее время поддерживаются два типа хуков настройки модулей:
-
module.register(specifier[, parentURL][, options]), которому передаётся модуль, экспортирующий асинхронные функции-хуки. Эти функции выполняются в отдельном потоке загрузчика. -
module.registerHooks(options), которому передаются синхронные функции-хуки, выполняемые непосредственно в потоке, где загружается модуль.
Включение
Настроить разрешение и загрузку модулей можно следующим образом:
- Зарегистрировать файл, экспортирующий набор асинхронных функций-хуков, с помощью метода
registerизnode:module, - Зарегистрировать набор синхронных функций-хуков с помощью метода
registerHooksизnode:module.
Хуки можно зарегистрировать до запуска кода приложения с помощью флага --import или --require:
node --import ./register-hooks.js ./my-app.js node --require ./register-hooks.js ./my-app.js copy
Модули JavaScript
// register-hooks.js
// This file can only be require()-ed if it doesn't contain top-level await.
// Use module.register() to register asynchronous hooks in a dedicated thread.
import { register } from 'node:module';
register('./hooks.mjs', import.meta.url);CommonJS
// register-hooks.js
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));Модули JavaScript
// Use module.registerHooks() to register synchronous hooks in the main thread.
import { registerHooks } from 'node:module';
registerHooks({
resolve(specifier, context, nextResolve) { /* implementation */ },
load(url, context, nextLoad) { /* implementation */ },
});CommonJS
// Use module.registerHooks() to register synchronous hooks in the main thread.
const { registerHooks } = require('node:module');
registerHooks({
resolve(specifier, context, nextResolve) { /* implementation */ },
load(url, context, nextLoad) { /* implementation */ },
});Файл, передаваемый в --import или --require, также может быть экспортом зависимости:
node --import some-package/register ./my-app.js node --require some-package/register ./my-app.js copy
Если в some-package есть поле "exports", определяющее экспорт /register, который следует сопоставить с файлом, вызывающим register(), как в следующем примере register-hooks.js.
Использование --import или --require гарантирует, что хуки будут зарегистрированы до импорта любых файлов приложения, включая точку входа приложения, а также по умолчанию для всех рабочих потоков.
Кроме того, register() и registerHooks() можно вызвать из точки входа, однако для любого кода ESM, который должен выполняться после регистрации хуков, необходимо использовать динамический import().
Модули JavaScript
import { register } from 'node:module';
register('http-to-https', import.meta.url);
// Because this is a dynamic `import()`, the `http-to-https` hooks will run
// to handle `./my-app.js` and any other files it imports or requires.
await import('./my-app.js');CommonJS
const { register } = require('node:module');
const { pathToFileURL } = require('node:url');
register('http-to-https', pathToFileURL(__filename));
// Because this is a dynamic `import()`, the `http-to-https` hooks will run
// to handle `./my-app.js` and any other files it imports or requires.
import('./my-app.js');Хуки настройки будут выполняться для любых модулей, загруженных после регистрации, а также для модулей, на которые они ссылаются через import и встроенный require. Функцию require, созданную пользователями с помощью module.createRequire(), можно настроить только с помощью синхронных хуков.
В этом примере мы регистрируем хуки http-to-https, однако они будут доступны только для модулей, импортированных после этого, — в данном случае для my-app.js и всего, на что он ссылается через import или встроенный require в зависимостях CommonJS.
Если бы import('./my-app.js') вместо этого был статическим import './my-app.js', приложение уже загрузилось бы до того, как были зарегистрированы хуки http-to-https. Это обусловлено спецификацией модулей ES: сначала вычисляются статические импорты в листьях дерева, а затем — по направлению к корню. Внутри my-app.js могут находиться статические импорты, которые не будут вычислены, пока my-app.js не будет импортирован динамически.
При использовании синхронных хуков поддерживаются import, require и пользовательские require, созданные с помощью createRequire().
Модули JavaScript
import { registerHooks, createRequire } from 'node:module';
registerHooks({ /* implementation of synchronous hooks */ });
const require = createRequire(import.meta.url);
// The synchronous hooks affect import, require() and user require() function
// created through createRequire().
await import('./my-app.js');
require('./my-app-2.js');CommonJS
const { register, registerHooks } = require('node:module');
const { pathToFileURL } = require('node:url');
registerHooks({ /* implementation of synchronous hooks */ });
const userRequire = createRequire(__filename);
// The synchronous hooks affect import, require() and user require() function
// created through createRequire().
import('./my-app.js');
require('./my-app-2.js');
userRequire('./my-app-3.js');Наконец, если вам нужно лишь зарегистрировать хуки до запуска приложения и вы не хотите создавать для этого отдельный файл, можно передать URL data: в --import:
node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("http-to-https", pathToFileURL("./"));' ./my-app.js copy Цепочки
Метод register можно вызвать несколько раз:
Модули 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');В этом примере зарегистрированные хуки образуют цепочки. Они выполняются в обратном порядке регистрации (LIFO). Если и foo.mjs, и bar.mjs определяют хук resolve, вызовы будут выполняться следующим образом (справа налево): по умолчанию Node.js ← ./foo.mjs ← ./bar.mjs (сначала ./bar.mjs, затем ./foo.mjs, а после — поведение Node.js по умолчанию). То же относится и ко всем остальным хукам.
Зарегистрированные хуки также влияют на сам register. В этом примере bar.mjs будет разрешён и загружен с помощью хуков, зарегистрированных foo.mjs (поскольку хуки foo уже будут добавлены в цепочку). Это позволяет, например, писать хуки на языках, отличных от JavaScript, при условии, что хуки, зарегистрированные ранее, преобразуют код в JavaScript.
Метод register нельзя вызвать из модуля, в котором определены хуки.
Цепочки registerHooks работают аналогично. Если синхронные и асинхронные хуки используются вместе, синхронные хуки всегда выполняются первыми, до запуска асинхронных. Иными словами, в последнем синхронном хуке вызов следующего хука включает запуск асинхронных хуков.
Модули JavaScript
// entrypoint.mjs
import { registerHooks } from 'node:module';
const hook1 = { /* implementation of hooks */ };
const hook2 = { /* implementation of hooks */ };
// hook2 run before hook1.
registerHooks(hook1);
registerHooks(hook2);CommonJS
// entrypoint.cjs
const { registerHooks } = require('node:module');
const hook1 = { /* implementation of hooks */ };
const hook2 = { /* implementation of hooks */ };
// hook2 run before hook1.
registerHooks(hook1);
registerHooks(hook2);Взаимодействие с хуками настройки модулей
Асинхронные хуки выполняются в выделенном потоке, отдельно от основного потока, в котором работает код приложения. Поэтому изменение глобальных переменных не повлияет на другие потоки, а для обмена данными между потоками необходимо использовать каналы сообщений.
Метод 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()
Метод 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). По умолчанию они наследуются дочерними рабочими потоками.
Синхронные хуки, принимаемые module.registerHooks()
Метод module.registerHooks() принимает функции синхронных хуков. initialize() не поддерживается и не требуется, поскольку реализующий хук может просто выполнить код инициализации непосредственно перед вызовом 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.
При использовании синхронных хуков можно ожидать, что console.log() завершится так же, как ожидается завершение console.log() в коде модуля.
Соглашения об использовании хуков
Хуки являются частью цепочки, даже если она состоит только из одного пользовательского (предоставленного пользователем) хука и стандартного хука, который присутствует всегда. Функции хуков образуют вложенную структуру: каждая из них всегда должна возвращать обычный объект, а цепочка формируется в результате вызова каждой функцией next<hookName>() — ссылки на хук следующего загрузчика (в порядке LIFO).
Если хук возвращает значение, в котором отсутствует обязательное свойство, возникает исключение. Если хук возвращает значение, не вызывая next<hookName>() и не возвращая shortCircuit: true, также возникает исключение. Эти ошибки помогают предотвратить непреднамеренные нарушения цепочки. Возвращайте из хука shortCircuit: true, чтобы указать, что цепочка намеренно завершается на вашем хуке.
initialize()
-
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)
-
specifier<string> -
context<Object>-
conditions<string[]> Условия экспорта для соответствующегоpackage.json -
importAttributes<Object> Объект, пары ключ-значение которого представляют атрибуты импортируемого модуля -
parentURL<string> | <undefined> Модуль, импортирующий этот модуль, или undefined, если это точка входа Node.js
-
-
nextResolve<Function> Следующий хукresolveв цепочке или стандартный хукresolveNode.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, в который разрешается этот входной параметр
-
Предупреждение В случае асинхронной версии вызовы
resolveвсё ещё могут блокировать основной поток, несмотря на поддержку возвращаемых промисов и асинхронных функций, что может повлиять на производительность.
Цепочка хуков 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.
// Asynchronous version accepted by module.register().
export async function resolve(specifier, context, nextResolve) {
const { parentURL = null } = context;
if (Math.random() > 0.5) { // Some condition.
// For some or all specifiers, do some custom logic for resolving.
// Always return an object of the form {url: <string>}.
return {
shortCircuit: true,
url: parentURL ?
new URL(specifier, parentURL).href :
new URL(specifier).href,
};
}
if (Math.random() < 0.5) { // Another condition.
// When calling `defaultResolve`, the arguments can be modified. In this
// case it's adding another value for matching conditional exports.
return nextResolve(specifier, {
...context,
conditions: [...context.conditions, 'another-condition'],
});
}
// Defer to the next hook in the chain, which would be the
// Node.js default resolve if this is the last user-specified loader.
return nextResolve(specifier);
} copy // Synchronous version accepted by module.registerHooks().
function resolve(specifier, context, nextResolve) {
// Similar to the asynchronous resolve() above, since that one does not have
// any asynchronous logic.
} copy
load(url, context, nextLoad)
-
url<string> URL, возвращённый цепочкойresolve -
context<Object>-
conditions<string[]> Условия экспорта соответствующегоpackage.json -
format<string> | <null> | <undefined> Формат, необязательно предоставленный цепочкой хуковresolve. В качестве входных данных может использоваться любое строковое значение; входные значения не обязаны соответствовать списку допустимых возвращаемых значений, описанному ниже. -
importAttributes<Object>
-
-
nextLoad<Function> Следующий хукloadв цепочке или стандартный хукloadNode.js после последнего заданного пользователем хукаload-
url<string> -
context<Object> | <undefined> Если параметр не указан, используются значения по умолчанию. Если он указан, значения по умолчанию объединяются с ним, причём приоритет имеют заданные свойства. В стандартномnextLoad, если модуль, на который указываетurl, не содержит явных сведений о типе модуля, параметрcontext.formatобязателен.
-
- Возвращает: <Object> | <Promise> Асинхронная версия принимает либо объект со следующими свойствами, либо
Promise, который разрешится в такой объект. Синхронная версия принимает только объект, возвращённый синхронно.-
format<string> -
shortCircuit<undefined> | <boolean> Сигнал о том, что этот хук намерен завершить цепочку хуковload. По умолчанию:false -
source<string> | <ArrayBuffer> | <TypedArray> Исходный код, который должен выполнить Node.js
-
Хук load позволяет определить пользовательский способ интерпретации, получения и разбора URL. Он также отвечает за проверку атрибутов импорта.
Итоговое значение 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 невозможно.
Особенности асинхронного хука load
При использовании асинхронного хука load отсутствие или наличие source для 'commonjs' приводит к совершенно разным последствиям:
- Если задан
source, все вызовыrequireиз этого модуля будут обрабатываться загрузчиком ESM с зарегистрированными хукамиresolveиload; все вызовыrequire.resolveиз этого модуля будут обрабатываться загрузчиком ESM с зарегистрированными хукамиresolve; будет доступна только часть API CommonJS (например, не будут доступныrequire.extensions,require.cacheиrequire.resolve.paths), а monkey patching загрузчика модулей CommonJS применяться не будет. - Если
sourceне определён или равенnull, обработка будет выполняться загрузчиком модулей CommonJS, а вызовыrequire/require.resolveне будут проходить через зарегистрированные хуки. Такое поведение для nullish-значенияsourceявляется временным — в будущем nullish-значениеsourceподдерживаться не будет.
Эти особенности не относятся к синхронному хуку load: в этом случае настроенным модулям CommonJS доступен полный набор API CommonJS, а вызовы require/require.resolve всегда проходят через зарегистрированные хуки.
Когда node запускается с --experimental-default-type=commonjs, внутренняя асинхронная реализация 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 содержит исходный код, загруженный следующим хуком, независимо от формата модуля.
Предупреждение: Асинхронный хук
loadнесовместим с пространственными экспортами из модулей CommonJS. Их совместное использование приведёт к тому, что импорт вернёт пустой объект. Возможно, в будущем это ограничение будет устранено. Оно не относится к синхронному хукуload, в котором экспорты можно использовать как обычно.
Все эти типы соответствуют классам, определённым в ECMAScript.
- В качестве конкретного объекта <ArrayBuffer> используется <SharedArrayBuffer>.
- В качестве конкретного объекта <TypedArray> используется <Uint8Array>.
Если исходное значение текстового формата (т. е. 'json', 'module') не является строкой, оно преобразуется в строку с помощью util.TextDecoder.
Хук load позволяет определить пользовательский способ получения исходного кода разрешённого URL. Это может позволить загрузчику избежать чтения файлов с диска. Его также можно использовать для преобразования нераспознанного формата в поддерживаемый, например yaml в module.
// Asynchronous version accepted by module.register().
export async function load(url, context, nextLoad) {
const { format } = context;
if (Math.random() > 0.5) { // Some condition
/*
For some or all URLs, do some custom logic for retrieving the source.
Always return an object of the form {
format: <string>,
source: <string|buffer>,
}.
*/
return {
format,
shortCircuit: true,
source: '...',
};
}
// Defer to the next hook in the chain.
return nextLoad(url);
} copy // Synchronous version accepted by module.registerHooks().
function load(url, context, nextLoad) {
// Similar to the asynchronous load() above, since that one does not have
// any asynchronous logic.
} copy В более сложных сценариях это также можно использовать для преобразования неподдерживаемого исходного кода в поддерживаемый (см. раздел Примеры ниже).
Примеры
Различные хуки настройки модулей можно использовать вместе для широкого спектра изменений поведения 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!.
Поддержка карт исходного кода
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()
- Возвращает: <Object>
Этот метод возвращает, включена ли поддержка карт исходного кода версии 3 для трассировок стека.
module.findSourceMap(path)
-
path<string> - Возвращает: <module.SourceMap> | <undefined> Возвращает
module.SourceMap, если карта исходного кода найдена, иundefinedв противном случае.
path — это разрешённый путь к файлу, для которого необходимо получить соответствующую карту исходного кода.
module.setSourceMapsSupport(enabled[, options])
Эта функция включает или отключает поддержку карт исходного кода версии 3 для трассировок стека.
Она предоставляет те же возможности, что и запуск процесса Node.js с параметрами командной строки --enable-source-maps, а также дополнительные параметры для изменения поддержки файлов в node_modules или сгенерированного кода.
Будут разбираться и загружаться только карты исходного кода из файлов JavaScript, загруженных после включения поддержки карт исходного кода. Рекомендуется использовать параметры командной строки --enable-source-maps, чтобы не потерять карты исходного кода модулей, загруженных до вызова этого API.
Класс: module.SourceMap
new SourceMap(payload[, { 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)
-
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-v22.x/docs/api/module.html