Модули: node:module API
Объект Module
- Тип:
<Object>
Предоставляет общие служебные методы для работы с экземплярами Module — переменной module, часто встречающейся в модулях CommonJS. Доступна через import 'node:module' или require('node:module').
module.builtinModules
- Тип:
<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)
-
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])
Стабильность: 1.1 — Активная разработка
-
specifier<string>|<URL>Спецификатор модуля, для которого нужно получитьpackage.json. Если передан голый спецификатор, возвращаетсяpackage.jsonв корне пакета. Если передан относительный спецификатор или абсолютный спецификатор, возвращается ближайший родительскийpackage.json. -
base<string>|<URL>Абсолютное расположение (строка URLfile:или путь 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)
-
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])
Стабильность: 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)
Стабильность: 1.2 — Кандидат на выпуск
-
options<Object>-
load<Function>|<undefined>См. хук load. По умолчанию:undefined. -
resolve<Function>|<undefined>См. хук resolve. По умолчанию:undefined.
-
- Возвращает:
<Object>Объект со следующим свойством:-
deregister()<Function>Удаляет зарегистрированные хуки, чтобы они больше не вызывались. В противном случае хуки сохраняются до завершения текущего процесса.
-
Регистрирует хуки, настраивающие разрешение и загрузку модулей Node.js. См. раздел Хуки настройки. Возвращённый объект можно использовать, чтобы отменить регистрацию хуков.
module.stripTypeScriptTypes(code[, options])
Стабильность: 1.2 — Кандидат на выпуск
-
code<string>Код, из которого нужно удалить аннотации типов. -
options<Object> - Возвращает:
<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()
Метод 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Кэш компиляции модулей
Кэш компиляции модулей можно включить с помощью метода 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 не удаётся вычислить расположение модуля относительно каталога кэша, модуль не будет кэшироваться.
Переносимый режим можно включить двумя способами:
-
С помощью параметра 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 -
Задав переменную среды:
NODE_COMPILE_CACHE_PORTABLE=1
Ограничения кэша компиляции
При использовании кэша компиляции вместе с покрытием кода JavaScript в V8 собираемое V8 покрытие может быть менее точным для функций, десериализованных из кэша кода. Для получения точных данных о покрытии при запуске тестов рекомендуется отключать эту функцию.
Кэш компиляции, созданный одной версией Node.js, нельзя повторно использовать в другой версии Node.js. Если для хранения кэша используется один и тот же базовый каталог, кэши, созданные разными версиями 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([options])
-
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()
Записывает на диск кэш компиляции модулей, накопленный для модулей, уже загруженных в текущем экземпляре Node.js. Метод возвращает управление после завершения всех операций записи в файловую систему независимо от того, завершились ли они успешно. При возникновении ошибок они игнорируются, поскольку промахи кэша компиляции не должны мешать фактической работе приложения.
module.getCompileCacheDir()
- Возвращает:
<string>|<undefined>Путь к каталогу кэша компиляции модулей, если он включён, илиundefinedв противном случае.
Хуки настройки
В настоящее время Node.js поддерживает два типа хуков настройки модулей:
-
module.registerHooks(options): принимает синхронные функции-хуки, которые выполняются непосредственно в потоке, где загружаются модули. -
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()
Метод 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)
-
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>-
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)
-
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>-
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.
- В результате, если зарегистрированы и асинхронные, и синхронные хуки, а асинхронные хуки выбирают настройку модуля 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()
Метод 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()
-
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)
-
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, в который разрешается этот входной параметр
-
Асинхронная версия работает аналогично синхронной, однако функция 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)
-
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обязателен.
-
- Возвращает:
<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!.
Поддержка карт исходного кода
Стабильность: 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()
- Возвращает:
<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>
Принимая смещения строки и столбца в сгенерированном исходном файле, метод возвращает объект, представляющий диапазон в исходном файле согласно карте исходного кода, если он найден, или пустой объект, если нет.
Возвращаемый объект содержит следующие ключи:
-
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)
-
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