Модули: node:module API
Объект Module
Предоставляет общие служебные методы при взаимодействии с экземплярами Module, переменной module, часто встречающейся в модулях CommonJS. Доступ к нему осуществляется через import 'node:module' или require('node:module').
module.builtinModules
Список имён всех модулей, предоставляемых Node.js. Может использоваться для проверки, поддерживается ли модуль третьей стороной.
Объект module в данном контексте не является тем же объектом, что предоставляется обёрткой модуля module wrapper. Для доступа к нему требуется подключить модуль Module:
Модули MJS
// module.mjs
// In an ECMAScript module
import { builtinModules as builtin } from 'node:module';
Модули CJS
// module.cjs
// In a CommonJS module
const builtin = require('node:module').builtinModules;
module.createRequire(filename)
-
filename<строка> | <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<строка> имя модуля - Возвращает: <логическое> возвращает true, если модуль встроенный, иначе возвращает false
import { isBuiltin } from 'node:module';
isBuiltin('node:fs'); // true
isBuiltin('fs'); // true
isBuiltin('wss'); // false copy
module.register(specifier[, parentURL][, options])
-
specifier<строка> | <URL> Хелперы настройки модулей для регистрации; эта строка должна быть такой же, что и передавалась вimport(), за исключением того, что, если она относительная, она разрешается относительноparentURL. -
parentURL<строка> | <URL> Если вы хотите разрешитьspecifierотносительно базового URL, напримерimport.meta.url, вы можете передать этот URL сюда. По умолчанию:'data:' -
options<Объект>-
parentURL<строка> | <URL> Если вы хотите разрешитьspecifierотносительно базового URL, напримерimport.meta.url, вы можете передать этот URL сюда. Это свойство игнорируется, еслиparentURLпередаётся в качестве второго аргумента. По умолчанию:'data:' -
data<любой> Любое произвольное, клонируемое значение JavaScript для передачи в хелперinitialize. -
transferList<Объекты[]> передаваемые объекты для передачи в хелперinitialize.
-
Регистрирует модуль, экспортирующий хелперы, которые настраивают разрешение и загрузку модулей Node.js. См. Хелперы настройки.
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
Модули MJS
// register-hooks.js
import { register } from 'node:module';
register('./hooks.mjs', import.meta.url);
Модули CJS
// 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().
Модули MJS
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');
Модули CJS
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).
Наконец, если всё, что вам нужно, это зарегистрировать хуки перед запуском приложения и вы не хотите создавать отдельный файл для этой цели, вы можете передать data: URL в --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 более одного раза.
Модули MJS
// entrypoint.mjs
import { register } from 'node:module';
register('./foo.mjs', import.meta.url);
register('./bar.mjs', import.meta.url);
await import('./my-app.mjs');
Модули CJS
// 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. Передаваемые данные могут включать переносимые объекты, такие как порты.
Модули MJS
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);
});
register('./my-hooks.mjs', {
parentURL: import.meta.url,
data: { number: 1, port: port2 },
transferList: [port2],
});
Модули CJS
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);
});
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<любой> Данные из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 Код вызывающей стороны:
Модули MJS
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');
});
register('./path-to-my-hooks.js', {
parentURL: import.meta.url,
data: { number: 1, port: port2 },
transferList: [port2],
});
Модули CJS
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');
});
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обработчик в цепочке илиresolveобработчик по умолчанию Node.js после последнего пользовательскогоresolveобработчика - Returns: <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 , переданном в обработчик. Для обеспечения поведения разрешения спецификатора модуля Node.js по умолчанию при вызове defaultResolve, массив 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обработчик в цепочке илиloadобработчик по умолчанию Node.js после последнего пользовательскогоloadобработчика - Returns: <Object>
-
format<string> -
shortCircuit<undefined> | <boolean> Сигнал, что этот обработчик намерен прервать цепочкуresolveобработчиков. По умолчанию:false -
source<string> | <ArrayBuffer> | <TypedArray> Источник, используемый Node.js для оценки
-
load обработчик предоставляет способ определить пользовательский метод определения того, как URL должен интерпретироваться, извлекаться и анализироваться. Он также отвечает за проверку утверждения импорта.
Конечное значение format должно быть одним из следующих:
format |
Описание | Допустимые типы для source возвращаемые load
|
|---|---|---|
'builtin' |
Загрузка встроенного модуля Node.js | Не применимо |
'commonjs' |
Загрузка модуля Node.js CommonJS | { 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не будут проходить через зарегистрированные обработчики. Такое поведение для нулевогоsourceвременное — в будущем нулевое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<Объект> Информация для поддержки кода preload-
port<MessagePort>
-
- Возвращает: <строка> Код для выполнения перед запуском приложения
Иногда может потребоваться выполнить код в том же глобальном пространстве имён, в котором работает приложение. Этот обработчик позволяет вернуть строку, которая выполняется как скрипт в режиме sloppy при запуске.
Подобно тому, как работают оболочки 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 Другой аргумент предоставляется коду preload: 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 maps").
// 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.
MJS-модули
// module.mjs
// In an ECMAScript module
import { findSourceMap, SourceMap } from 'node:module';
CJS-модули
// module.cjs
// In a CommonJS module
const { findSourceMap, SourceMap } = require('node:module');
module.findSourceMap(path)
-
path<строка> - Возвращает: <модуль.SourceMap> | <неопределено> Возвращает
module.SourceMapесли карта исходного кода найдена,undefinedв противном случае.
path — это разрешенный путь к файлу, для которого должна быть получена соответствующая карта исходного кода.
Класс: module.SourceMap
new SourceMap(payload[, { lineLengths }])
-
payload<объект> -
lineLengths<массив чисел>
Создает новый экземпляр sourceMap.
payload — это объект с ключами, соответствующими формату карты исходного кода v3:
-
file: <строка> -
version: <число> -
sources: <массив строк> -
sourcesContent: <массив строк> -
names: <массив строк> -
mappings: <строка> -
sourceRoot: <строка>
lineLengths — это необязательный массив длин каждой строки в сгенерированном коде.
sourceMap.payload
- Возвращает: <объект>
Геттер для содержимого, используемого для построения экземпляра SourceMap.
sourceMap.findEntry(lineOffset, columnOffset)
-
lineOffset<число> Смещение номера строки (нумерация с нуля) в сгенерированном исходном коде -
columnOffset<число> Смещение номера столбца (нумерация с нуля) в сгенерированном исходном коде - Возвращает: <объект>
Принимая смещение строки и столбца в сгенерированном исходном файле, возвращает объект, представляющий диапазон SourceMap в исходном файле, если он найден, или пустой объект, если нет.
Возвращаемый объект содержит следующие ключи:
- generatedLine: <число> Смещение номера строки начала диапазона в сгенерированном исходном коде
- generatedColumn: <число> Смещение номера столбца начала диапазона в сгенерированном исходном коде
- originalSource: <строка> Имя файла исходного кода, как указано в SourceMap
- originalLine: <число> Смещение номера строки начала диапазона в исходном коде
- originalColumn: <число> Смещение номера столбца начала диапазона в исходном коде
- name: <строка>
Возвращаемое значение представляет собой исходный диапазон, как он отображается в SourceMap, на основе смещений с нуля, а не 1-индексированных номеров строки и столбца, как они отображаются в сообщениях об ошибках и объектах CallSite.
Чтобы получить соответствующие 1-индексированные номера строки и столбца из номера строки и номера столбца, как они сообщаются стеками ошибок и объектами CallSite, используйте sourceMap.findOrigin(lineNumber, columnNumber)
sourceMap.findOrigin(lineNumber, columnNumber)
-
lineNumber<число> Номер строки (1-индекс) места вызова в сгенерированном исходном коде -
columnNumber<число> Номер столбца (1-индекс) места вызова в сгенерированном исходном коде - Возвращает: <объект>
Принимая 1-индексированный номер строки и номер столбца из места вызова в сгенерированном исходном коде, найдите соответствующее место вызова в исходном коде.
Если предоставленные номер строки и номер столбца не найдены ни в одной карте исходного кода, то возвращается пустой объект. В противном случае возвращаемый объект содержит следующие ключи:
- name: <строка> | <неопределено> Имя диапазона в карте исходного кода, если оно было предоставлено
- fileName: <строка> Имя файла исходного кода, как указано в SourceMap
- lineNumber: <число> Номер строки (1-индекс) соответствующего места вызова в исходном коде
- columnNumber: <число> Номер столбца (1-индекс) соответствующего места вызова в исходном коде
© 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