Модули: node:module API
Объект Module
Предоставляет общие вспомогательные методы для работы с экземплярами Module — переменной module, часто встречающейся в модулях CommonJS. Доступ к ней осуществляется через import 'node:module' или require('node:module').
module.builtinModules
Список имен всех модулей, предоставляемых Node.js. Его можно использовать, чтобы определить, поддерживается ли модуль сторонней организацией.
Примечание: список не содержит модули только с префиксом, такие как node:test.
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'); copy
module.isBuiltin(moduleName)
-
moduleName<string> имя модуля - Возвращает: <boolean> возвращает true, если модуль встроенный, иначе возвращает false
import { isBuiltin } from 'node:module';
isBuiltin('node:fs'); // true
isBuiltin('fs'); // true
isBuiltin('wss'); // false copy
module.register(specifier[, parentURL][, options])
-
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.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 Хуки настройки
Включение
Разрешение и загрузку модулей можно настроить, зарегистрировав файл, экспортирующий набор хуков. Это можно сделать с помощью метода register из node:module, который можно запустить до выполнения кода приложения, используя флаг --import:
node --import ./register-hooks.js ./my-app.js copy
Модули JavaScript
// register-hooks.js
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');
register('./hooks.mjs', pathToFileURL(__filename));Файл, передаваемый в --import, также может быть экспортом зависимости:
node --import some-package/register ./my-app.js copy
При этом some-package имеет поле "exports", определяющее экспорт /register, который нужно сопоставить с файлом, вызывающим register(), как в следующем примере register-hooks.js.
Использование --import гарантирует, что хуки будут зарегистрированы до импорта любых файлов приложения, включая точку входа приложения. В качестве альтернативы register можно вызвать из точки входа, но для любого кода, который должен выполняться после регистрации хуков, необходимо использовать динамический 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');В этом примере регистрируются хуки http-to-https, но они будут доступны только для модулей, импортированных впоследствии, — в данном случае для my-app.js и всего, на что он ссылается через import (и, при необходимости, require). Если бы import('./my-app.js') вместо этого был статическим import './my-app.js', приложение уже было бы загружено до регистрации хуков http-to-https. Это связано со спецификацией ES-модулей: сначала вычисляются статические импорты из листьев дерева, а затем — в направлении корня. Внутри my-app.js могут находиться статические импорты, которые не будут вычислены, пока my-app.js не будет импортирован динамически.
my-app.js также может быть модулем CommonJS. Хуки настройки будут выполняться для любых модулей, на которые он ссылается через import (и, при необходимости, require).
Наконец, если нужно только зарегистрировать хуки до запуска приложения и для этого не требуется создавать отдельный файл, можно передать 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 нельзя вызвать из модуля, в котором определены хуки.
Взаимодействие с хуками настройки модулей
Хуки настройки модулей выполняются в выделенном потоке, отдельном от основного потока, в котором выполняется код приложения. Это означает, что изменение глобальных переменных не повлияет на другие потоки, а для обмена сообщениями между потоками необходимо использовать каналы сообщений.
Метод 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],
});Хуки
Метод 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 Хуки входят в цепочку, даже если она состоит только из одного пользовательского хука и стандартного хука, который присутствует всегда. Функции-хуки вложены друг в друга: каждая из них должна возвращать обычный объект, а цепочка формируется, когда каждая функция вызывает next<hookName>() — ссылку на хук следующего загрузчика (в порядке LIFO).
Если хук возвращает значение без обязательного свойства, возникает исключение. Исключение также возникает, если хук возвращает значение, не вызвав next<hookName>() и не вернув shortCircuit: true. Эти ошибки помогают предотвратить непреднамеренное нарушение цепочки. Верните shortCircuit: true из хука, чтобы указать, что цепочка намеренно завершается на вашем хуке.
Хуки выполняются в отдельном потоке, изолированном от основного потока, в котором работает код приложения. Это означает, что используется другая область выполнения (realm). Основной поток может в любой момент завершить поток хуков, поэтому не следует полагаться на завершение асинхронных операций (например, console.log).
initialize()
-
data<any> Данные изregister(loader, import.meta.url, { data }).
Хук initialize позволяет определить пользовательскую функцию, которая запускается в потоке хуков при инициализации модуля хуков. Инициализация происходит при регистрации модуля хуков с помощью register.
Этот хук может получать данные из вызова register, включая порты и другие передаваемые объекты. Возвращаемым значением initialize может быть <Promise>; в таком случае его выполнение будет ожидаться, прежде чем возобновится выполнение в основном потоке приложения.
Код настройки модулей:
// path-to-my-hooks.js
export async function initialize({ number, port }) {
port.postMessage(`increment: ${number + 1}`);
} 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 - Возвращает: <Object> | <Promise>
-
format<string> | <null> | <undefined> Подсказка для хука загрузки (она может быть проигнорирована)'builtin' | 'commonjs' | 'json' | 'module' | 'wasm' -
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.
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
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 - Возвращает: <Object>
-
format<string> -
shortCircuit<undefined> | <boolean> Сигнал о том, что этот хук намерен завершить цепочку хуковload. По умолчанию:false -
source<string> | <ArrayBuffer> | <TypedArray> Исходный код, который должен выполнить Node.js
-
Хук load позволяет определить пользовательский способ интерпретации, получения и разбора URL. Он также отвечает за проверку утверждения импорта.
Итоговое значение format должно быть одним из следующих:
format |
Описание | Допустимые типы для source, возвращаемые load
|
|---|---|---|
'builtin' |
Загрузить встроенный модуль Node.js | Неприменимо |
'commonjs' |
Загрузить модуль CommonJS Node.js | { string, ArrayBuffer, TypedArray, null, undefined } |
'json' |
Загрузить файл JSON | { string, ArrayBuffer, TypedArray } |
'module' |
Загрузить модуль ES | { string, ArrayBuffer, TypedArray } |
'wasm' |
Загрузить модуль WebAssembly | { ArrayBuffer, TypedArray } |
Значение source игнорируется для типа 'builtin', поскольку в настоящее время заменить значение встроенного (основного) модуля Node.js невозможно.
Отсутствие или наличие source для 'commonjs' имеет совершенно разные последствия:
- Если указан
source, все вызовыrequireиз этого модуля будут обрабатываться загрузчиком ESM с зарегистрированными хукамиresolveиload; все вызовыrequire.resolveиз этого модуля будут обрабатываться загрузчиком ESM с зарегистрированными хукамиresolve; будет доступна только часть API CommonJS (например, безrequire.extensions, безrequire.cache, безrequire.resolve.paths), а исправления на лету загрузчика модулей CommonJS применяться не будут. - Если
sourceне определён или равенnull, модуль будет обрабатываться загрузчиком модулей CommonJS, а вызовыrequire/require.resolveне будут проходить через зарегистрированные хуки. Такое поведение для nullish-значенийsourceявляется временным — в будущем nullish-значенияsourceподдерживаться не будут.
Когда node выполняется с --experimental-default-type=commonjs, внутренняя реализация load Node.js, являющаяся значением next для последнего хука в цепочке load, возвращает null для source, если format имеет значение 'commonjs', для обеспечения обратной совместимости. Ниже приведён пример хука, который явно включает нестандартное поведение:
import { readFile } from 'node:fs/promises';
export async function load(url, context, nextLoad) {
const result = await nextLoad(url, context);
if (result.format === 'commonjs') {
result.source ??= await readFile(new URL(result.responseURL ?? url));
}
return result;
} copy Предупреждение: хук ESM
loadи именованные экспорты из модулей CommonJS несовместимы. При попытке использовать их вместе импорт вернёт пустой объект. Возможно, это будет исправлено в будущем.
Все эти типы соответствуют классам, определённым в ECMAScript.
- Конкретный объект
ArrayBufferявляется объектомSharedArrayBuffer. - Конкретный объект
TypedArrayявляется объектомUint8Array.
Если исходное значение текстового формата (то есть 'json', 'module') не является строкой, оно преобразуется в строку с помощью util.TextDecoder.
Хук load позволяет определить пользовательский способ получения исходного кода для разрешённого URL. Благодаря этому загрузчик может, например, не читать файлы с диска. Его также можно использовать для сопоставления нераспознанного формата с поддерживаемым, например yaml с module.
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 В более сложных случаях это также можно использовать для преобразования неподдерживаемого исходного кода в поддерживаемый (см. раздел Примеры ниже).
globalPreload()
Предупреждение: этот хук будет удалён в будущей версии. Вместо него используйте
initialize. Если модуль хуков экспортируетinitialize,globalPreloadбудет проигнорирован.
-
context<Object> Информация для кода предварительной загрузки-
port<MessagePort>
-
- Возвращает: <string> Код, выполняемый перед запуском приложения
Иногда может потребоваться выполнить код в той же глобальной области видимости, в которой работает приложение. Этот хук позволяет вернуть строку, которая при запуске выполняется как скрипт в нестрогом режиме.
Как и в случае с обёртками CommonJS, код выполняется в неявной области видимости функции. Единственный аргумент — функция, подобная require, которую можно использовать для загрузки встроенных модулей, например "fs": getBuiltin(request: string).
Если коду нужны более продвинутые возможности require, он должен самостоятельно создать require с помощью module.createRequire().
export function globalPreload(context) {
return `\
globalThis.someInjectedProperty = 42;
console.log('I just set some globals!');
const { createRequire } = getBuiltin('module');
const { cwd } = getBuiltin('process');
const require = createRequire(cwd() + '/<preload>');
// [...]
`;
} copy Коду предварительной загрузки также передаётся аргумент port. Он доступен как параметр хука и внутри текста исходного кода, возвращаемого хуком. Эта функциональность перенесена в хук initialize.
Необходимо правильно вызывать port.ref() и port.unref(), чтобы процесс не оказался в состоянии, при котором он не сможет нормально завершиться.
/**
* This example has the application context send a message to the hook
* and sends the message back to the application context
*/
export function globalPreload({ port }) {
port.onmessage = (evt) => {
port.postMessage(evt.data);
};
return `\
port.postMessage('console.log("I went to the hook and back");');
port.onmessage = (evt) => {
eval(evt.data);
};
`;
} copy Примеры
Различные хуки настройки модулей можно использовать совместно для широкого спектра изменений поведения Node.js при загрузке и выполнении кода.
Импорт по HTTPS
В текущей версии Node.js спецификаторы, начинающиеся с https://, являются экспериментальными (см. раздел Импорт по HTTPS и HTTP).
Приведённый ниже хук регистрирует хуки, обеспечивающие базовую поддержку таких спецификаторов. Хотя это может показаться значительным улучшением функциональности ядра Node.js, у использования этих хуков есть серьёзные недостатки: они работают намного медленнее, чем загрузка файлов с диска, кэширование отсутствует, а также отсутствует безопасность.
// https-hooks.mjs
import { get } from 'node:https';
export function load(url, context, nextLoad) {
// For JavaScript to be loaded over the network, we need to fetch and
// return it.
if (url.startsWith('https://')) {
return new Promise((resolve, reject) => {
get(url, (res) => {
let data = '';
res.setEncoding('utf8');
res.on('data', (chunk) => data += chunk);
res.on('end', () => resolve({
// This example assumes all network-provided JavaScript is ES module
// code.
format: 'module',
shortCircuit: true,
source: data,
}));
}).on('error', (err) => reject(err));
});
}
// Let Node.js handle all other URLs.
return nextLoad(url);
} 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 { dirname, extname, resolve as resolvePath } from 'node:path';
import { cwd } from 'node:process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import coffeescript from 'coffeescript';
const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/;
export async function load(url, context, nextLoad) {
if (extensionsRegex.test(url)) {
// CoffeeScript files can be either CommonJS or ES modules, so we want any
// CoffeeScript file to be treated by Node.js the same as a .js file at the
// same location. To determine how Node.js would interpret an arbitrary .js
// file, search up the file system for the nearest parent package.json file
// and read its "type" field.
const format = await getPackageType(url);
const { source: rawSource } = await nextLoad(url, { ...context, format });
// This hook converts CoffeeScript source code into JavaScript source code
// for all imported CoffeeScript files.
const transformedSource = coffeescript.compile(rawSource.toString(), url);
return {
format,
shortCircuit: true,
source: transformedSource,
};
}
// Let Node.js handle all other URLs.
return nextLoad(url);
}
async function getPackageType(url) {
// `url` is only a file path during the first iteration when passed the
// resolved url from the load() hook
// an actual file path from load() will contain a file extension as it's
// required by the spec
// this simple truthy check for whether `url` contains a file extension will
// work for most projects but does not cover some edge-cases (such as
// extensionless files or a url ending in a trailing space)
const isFilePath = !!extname(url);
// If it is a file path, get the directory it's in
const dir = isFilePath ?
dirname(fileURLToPath(url)) :
url;
// Compose a file path to a package.json in the same directory,
// which may or may not exist
const packagePath = resolvePath(dir, 'package.json');
// Try to read the possibly nonexistent package.json
const type = await readFile(packagePath, { encoding: 'utf8' })
.then((filestring) => JSON.parse(filestring).type)
.catch((err) => {
if (err?.code !== 'ENOENT') console.error(err);
});
// If package.json existed and contained a `type` field with a value, voilà
if (type) return type;
// Otherwise, (if not at the root) continue checking the next directory up
// If at the root, stop and return false
return dir.length > 1 && getPackageType(resolvePath(dir, '..'));
} 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
С приведённым выше модулем хуков запуск node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-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 Для следующих файлов:
// 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 должен вывести some module!.
Поддержка карт исходного кода v3
Вспомогательные средства для работы с кэшем карт исходного кода. Этот кэш заполняется, когда включён разбор карт исходного кода и в конце модуля обнаруживаются директивы включения карты исходного кода.
Чтобы включить разбор карт исходного кода, Node.js необходимо запустить с флагом --enable-source-maps или включить сбор покрытия кода, задав NODE_V8_COVERAGE=dir.
Модули 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.findSourceMap(path)
-
path<string> - Возвращает: <module.SourceMap> | <undefined> Возвращает
module.SourceMap, если найдена карта исходного кода, иundefinedв противном случае.
path — это разрешённый путь к файлу, для которого необходимо получить соответствующую карту исходного кода.
Класс: module.SourceMap
new SourceMap(payload[, { lineLengths }])
-
payload<Object> -
lineLengths<number[]>
Создаёт новый экземпляр sourceMap.
payload — это объект с ключами, соответствующими формату карт исходного кода v3:
-
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, на основе смещений с отсчётом от нуля, а не номеров строк и столбцов с отсчётом от единицы, используемых в сообщениях об ошибках и объектах 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-v20.x/docs/api/module.html