Spec-Zone.ru › Deno 2

Хуки загрузчика

Deno поддерживает API Node.js module.registerHooks(), который позволяет перехватывать и настраивать разрешение и загрузку модулей. Это позволяет использовать виртуальные модули, пользовательскую транспиляцию, псевдонимы модулей и аналогичные решения без изменения импортирующего кода. API node:module является частью более широкого уровня совместимости с Node.js в Deno.

Хуки являются синхронными и выполняются в том же потоке, что и ваше приложение. Они работают как с ES-модулями (import), так и с CommonJS (require()).

Deno не реализует асинхронный API module.register(). Используйте registerHooks() для настройки как CommonJS, так и ESM.

Простой пример

main.mjs
import { registerHooks } from "node:module";

const hooks = registerHooks({
  resolve(specifier, context, nextResolve) {
    if (specifier === "virtual:greet") {
      return { url: "file:///virtual_greet.js", shortCircuit: true };
    }
    return nextResolve(specifier, context);
  },
  load(url, context, nextLoad) {
    if (url === "file:///virtual_greet.js") {
      return {
        source: 'export const msg = "hello from hooks";',
        format: "module",
        shortCircuit: true,
      };
    }
    return nextLoad(url, context);
  },
});

const { msg } = await import("virtual:greet");
console.log(msg); // "hello from hooks"

// Remove hooks when no longer needed
hooks.deregister();
›_
deno run --allow-all main.mjs

Загрузка хуков с помощью --import

Чтобы код приложения оставался чистым и хуки устанавливались до того, как какая-либо часть программы импортирует затрагиваемые ими модули, поместите вызов registerHooks() в отдельный файл загрузчика и предварительно загрузите его с помощью --import (псевдоним для --preload).

loader.mjs
import { registerHooks } from "node:module";

registerHooks({
  resolve(specifier, context, nextResolve) {
    if (specifier === "virtual:greet") {
      return { url: "file:///virtual_greet.js", shortCircuit: true };
    }
    return nextResolve(specifier, context);
  },
  load(url, context, nextLoad) {
    if (url === "file:///virtual_greet.js") {
      return {
        source: 'export const msg = "hello from loader";',
        format: "module",
        shortCircuit: true,
      };
    }
    return nextLoad(url, context);
  },
});
main.mjs
const { msg } = await import("virtual:greet");
console.log(msg); // "hello from loader"

Запустите программу, указав загрузчик в --import:

›_
deno run --import ./loader.mjs main.mjs

Параметр --import принимает несколько значений, поэтому можно комбинировать загрузчики (например, --import ./aliases.mjs --import ./transpile.mjs). Они регистрируются в указанном порядке, но выполняются в обратном — см. раздел Цепочка хуков. Флаг доступен в командах deno run, deno test, deno bench и deno serve.

Варианты использования

Пользовательская транспиляция

Преобразование нестандартных форматов файлов на лету:

import { registerHooks } from "node:module";

registerHooks({
  load(url, context, nextLoad) {
    if (url.endsWith(".coffee")) {
      const result = nextLoad(url, context);
      const compiled = compileCoffeeScript(result.source);
      return { source: compiled, format: "module", shortCircuit: true };
    }
    return nextLoad(url, context);
  },
});

Псевдонимы модулей

Перенаправление импортов на другие модули:

import { registerHooks } from "node:module";

registerHooks({
  resolve(specifier, context, nextResolve) {
    // Redirect lodash to lodash-es
    if (specifier === "lodash") {
      return nextResolve("lodash-es", context);
    }
    return nextResolve(specifier, context);
  },
});

Виртуальные модули

Создание модулей, существующих только в памяти:

import { registerHooks } from "node:module";

const virtualModules = new Map([
  ["virtual:config", 'export default { debug: true, version: "1.0.0" };'],
  ["virtual:env", `export const NODE_ENV = "${process.env.NODE_ENV}";`],
]);

registerHooks({
  resolve(specifier, context, nextResolve) {
    if (virtualModules.has(specifier)) {
      return { url: `file:///virtual/${specifier}`, shortCircuit: true };
    }
    return nextResolve(specifier, context);
  },
  load(url, context, nextLoad) {
    for (const [name, source] of virtualModules) {
      if (url === `file:///virtual/${name}`) {
        return { source, format: "module", shortCircuit: true };
      }
    }
    return nextLoad(url, context);
  },
});

Моки для тестов

Замена модулей моками во время тестирования:

import { registerHooks } from "node:module";

const hooks = registerHooks({
  resolve(specifier, context, nextResolve) {
    if (specifier === "./database.js") {
      return { url: "file:///mock_database.js", shortCircuit: true };
    }
    return nextResolve(specifier, context);
  },
  load(url, context, nextLoad) {
    if (url === "file:///mock_database.js") {
      return {
        source: 'export const query = () => [{ id: 1, name: "mock" }];',
        format: "module",
        shortCircuit: true,
      };
    }
    return nextLoad(url, context);
  },
});

// Run tests...

hooks.deregister(); // Clean up after tests

Внешние зависимости в исходном коде, созданном хуками

Информация

Спецификаторы jsr:, npm: и https: не разрешаются автоматически, если они встречаются только в исходном коде, созданном хуком. Это относится к любому хуку load, который возвращает исходный код, не прочитанный Deno с диска самостоятельно, — например, при пользовательской транспиляции, создании виртуальных модулей, моков и т. п.

Deno обнаруживает и устанавливает внешние зависимости, статически анализируя граф модулей до выполнения. Исходный код, возвращаемый хуком load, создаётся во время загрузки, после завершения этого анализа, поэтому любые bare-импорты jsr:, npm: или https:, встречающиеся только в созданном исходном коде, не видны механизму разрешения зависимостей. В результате могут возникнуть ошибки, например Could not find constraint 'lodash-es@latest' in the list of packages.

Чтобы использовать внешнюю зависимость в коде, созданном хуком, объявите её заранее, чтобы она вошла в набор разрешённых пакетов. Например, добавьте её в карту imports в файле deno.json:

deno.json
{
  "imports": {
    "lodash-es": "npm:lodash-es@latest"
  }
}

и импортируйте её из созданного исходного кода по указанному имени. Это работает именно так, как задумано: разрешение зависимостей детерминировано и управляется lock-файлом, поэтому набор зависимостей должен быть известен до начала выполнения.

Хук resolve

Хук resolve перехватывает разрешение модулей, сопоставляя спецификаторы с URL.

resolve(specifier, context, nextResolve);

Параметры:

Параметр Тип Описание
specifier string Разрешаемый спецификатор модуля
context object Контекст разрешения (см. ниже)
nextResolve function Передаёт управление следующему хуку или стандартному механизму разрешения

Объект контекста:

Свойство Тип Описание
conditions string[] Условия импорта (например, ["node", "import"] для ESM)
parentURL string URL импортирующего модуля
importAttributes object Атрибуты импорта из инструкции импорта

Возвращаемое значение:

Свойство Тип Описание
url string Разрешённый URL модуля
shortCircuit boolean Если true, пропустить оставшиеся хуки в цепочке

Вызовите nextResolve() для передачи управления дальше либо верните результат с shortCircuit: true. Необходимо выполнить одно из этих действий.

Хук load

Хук load перехватывает загрузку модулей и предоставляет исходный код для разрешённого URL.

load(url, context, nextLoad);

Параметры:

Параметр Тип Описание
url string URL разрешённого модуля
context object Контекст загрузки (см. ниже)
nextLoad function Передаёт управление следующему хуку или стандартному загрузчику

Объект контекста:

Свойство Тип Описание
format string Подсказка о формате модуля (например, "module", "commonjs")
conditions string[] Условия импорта
importAttributes object Атрибуты импорта

Возвращаемое значение:

Свойство Тип Описание
source string | Buffer | null Исходный код модуля
format string Формат модуля: "module", "commonjs", "json"
shortCircuit boolean Если true, пропустить оставшиеся хуки в цепочке

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

registerHooks() возвращает объект с методом deregister() для удаления хуков:

const hooks = registerHooks({/* ... */});

// Later, remove hooks
hooks.deregister();

Цепочка хуков

Можно зарегистрировать несколько хуков, объединив их в цепочку. Хуки выполняются в порядке LIFO (последний зарегистрированный вызывается первым), и каждый хук может вызвать nextResolve() / nextLoad(), чтобы передать управление предыдущему хуку в цепочке:

import { registerHooks } from "node:module";

// Hook 1: registered first, runs second
const hook1 = registerHooks({
  load(url, context, nextLoad) {
    const result = nextLoad(url, context);
    if (url.includes("target.js")) {
      return {
        source: 'export default "from hook1"',
        format: "module",
        shortCircuit: true,
      };
    }
    return result;
  },
});

// Hook 2: registered second, runs first
const hook2 = registerHooks({
  load(url, context, nextLoad) {
    const result = nextLoad(url, context); // Calls hook1
    if (url.includes("target.js")) {
      return {
        source: 'export default "from hook2"',
        format: "module",
        shortCircuit: true,
      };
    }
    return result;
  },
});

// Result comes from hook2 since it runs first (LIFO)

CommonJS

Хуки также перехватывают require():

main.cjs
const { registerHooks } = require("module");

const hooks = registerHooks({
  resolve(specifier, context, nextResolve) {
    if (specifier === "virtual-module") {
      return { url: "file:///virtual.js", shortCircuit: true };
    }
    return nextResolve(specifier, context);
  },
  load(url, context, nextLoad) {
    if (url === "file:///virtual.js") {
      return {
        source: "module.exports = { value: 42 }",
        format: "commonjs",
        shortCircuit: true,
      };
    }
    return nextLoad(url, context);
  },
});

const mod = require("virtual-module");
console.log(mod.value); // 42

hooks.deregister();

Последнее обновление: 9 июля 2026 г.

© 2018–2025 the Deno authors
Licensed under the MIT License.
https://docs.deno.com/runtime/reference/loader_hooks

Spec-Zone.ru

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