模块

使用模块扩展 Nitro 的构建时行为

Nitro 模块是在 Nitro 实例初始化时运行一次的函数(在开发、构建和预渲染过程中)。它们接收 nitro 构建上下文,可用于修改选项、注册构建时钩子、添加虚拟文件或注册路由处理器

Note

模块在 构建时运行。要扩展服务器的运行时行为(请求生命周期、错误处理、关闭),请改用插件

模块插件
运行时机一次,在初始化和构建期间一次,在服务器启动时
接收对象nitro(构建上下文)nitroApp(运行时应用)
典型用途修改配置、添加处理器和虚拟文件、接入构建流程接入请求/响应生命周期
是否打包到输出中

#使用模块

使用 modules 配置数组注册模块:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  modules: [
    // Path to a local module (relative to the project root)
    "./modules/my-module.ts",
    // Name of an npm package
    "my-nitro-module",
    // Inline module object
    {
      name: "inline-module",
      setup(nitro) {
        nitro.logger.info("Inline module setup");
      },
    },
    // Bare setup function
    (nitro) => {
      nitro.hooks.hook("compiled", () => {
        // ...
      });
    },
    // Plugin object with a `nitro` key
    {
      name: "plugin-shaped-module",
      nitro: {
        setup(nitro) {
          nitro.logger.info("Plugin module setup");
        },
      },
    },
  ],
});

每个条目可以采用以下形式之一:

形式示例描述
路径或包字符串"./modules/my-module.ts""my-nitro-module"从项目根目录解析并导入。使用其默认导出作为模块
模块对象{ name?, setup }一个包含 setup(nitro) 函数和可选 name 的对象
裸函数(nitro) => { ... }{ setup } 的简写形式
插件对象{ name?, nitro: { setup } }一个包含 nitro 键的对象,该键中存放模块。它与 Rollup/Vite 插件形式一致,因此一个导出既可以是打包器插件,也可以是 Nitro 模块

modules/ 目录中的文件会自动注册为模块(类似于用于运行时插件plugins/

只有以下两种模式会自动注册(支持任意以下扩展名:.ts.mts.cts.js.mjs.cjs.tsx.jsx):

  • modules/*.ts(单文件模块)
  • modules/*/index.ts(目录模块)

modules/ 中不匹配这些模式的任何其他文件(例如模块自身的 runtime/ 文件)都会被扫描器忽略

通过路径或包名引用的模块只会被解析和安装一次;指向同一文件的重复条目会被跳过

Tip

Vite 项目中,vite.config.ts 中任何带有 nitro 键的插件都会自动注册为 Nitro 模块。无需再将其列入 modules

#编写模块

模块是一个包含 setup 函数的对象,该函数接收 Nitro 实例。使用 nitro/types 中的 NitroModule 类型来确保类型安全:

modules/hello.ts
import type { NitroModule } from "nitro/types";

export default {
  name: "hello",
  setup(nitro) {
    // Add a virtual module usable anywhere in the server code as `#hello`
    nitro.options.virtual["#hello"] = `export const hello = "world";`;

    // Register a route handler pointing to the virtual module
    nitro.options.handlers.push({
      route: "/_hello",
      handler: "#hello-handler",
    });
    nitro.options.virtual["#hello-handler"] = /* ts */ `
      import { defineHandler } from "nitro";
      import { hello } from "#hello";
      export default defineHandler(() => ({ hello }));
    `;
  },
} satisfies NitroModule;

setup 函数可以是异步函数。nitro 实例上的实用属性:

属性描述
nitro.options解析后的配置(可变):handlersvirtualpluginsruntimeConfig 以及其他所有选项
nitro.hooks构建时钩子hookable 实例)
nitro.logger用于构建时输出的带标签 consola 日志记录器
nitro.metaNitro 版本信息(versionmajorVersion

#构建时钩子

模块可以通过 nitro.hooks.hook() 接入构建生命周期:

modules/build-info.ts
import type { NitroModule } from "nitro/types";

export default {
  name: "build-info",
  setup(nitro) {
    nitro.hooks.hook("compiled", () => {
      nitro.logger.info(`Server built in ${nitro.options.output.dir}`);
    });
  },
} satisfies NitroModule;

最有用的钩子:

钩子签名运行时机
build:before(nitro) => void构建开始之前、创建打包器配置之前
rollup:before(nitro, config) => void打包器(Rollup/Rolldown)配置解析之后、打包之前。修改配置的最后机会
compiled(nitro) => void服务器包写入输出目录之后
dev:start() => void在开发环境中,开发工作线程启动时
dev:reload(payload?) => void在开发环境中,每次重新构建后开发工作线程重新加载时
dev:error(cause?) => void在开发环境中,发生构建错误时
prerender:routes(routes: Set<string>) => void预渲染之前。添加或移除要预渲染的路由
prerender:config(config) => void创建预渲染器的 Nitro 配置时
prerender:generate(route, nitro) => void对于每个路由,在生成该路由之前
prerender:route(route) => void对于每个路由,在生成该路由之后
prerender:done({ prerenderedRoutes, failedRoutes }) => void预渲染完成之后
close() => voidNitro 实例关闭时

所有钩子都可以是异步的。完整列表和确切签名请参阅钩子源码,有关 prerender:* 钩子的具体用法,请参阅预渲染指南

Tip

这里不提供运行时钩子(requestresponseerror);它们属于插件。模块仍然可以通过向 nitro.options.plugins 推送插件文件来添加运行时行为

#最佳实践

  • 为每个模块提供一个 name 这样可以使模块在配置中更易识别;对于插件对象,Rollup 和 Vite 也要求通过它来识别插件
  • 优先使用插件对象形式。 { name, nitro: { setup } } 同时可被 Nitro、Rollup 和 Vite 理解,因此一个导出即可同时适用于三者。Vite 应用的用户将其放入 plugins,独立 Nitro 应用的用户将其放入 modules,无论采用哪种方式,模块都能运行
  • 保持模块幂等。 在开发环境中,配置变更时可能会重新处理选项,因此在推送重复的处理器、插件或虚拟条目之前进行检查
  • 优先使用钩子而不是打补丁。 使用构建钩子响应生命周期事件,而不是修改 Nitro 内部实现或覆盖函数
  • 尊重用户选项。nitro.options 中读取现有值并在其基础上进行扩展,而不是替换它们。让用户可以选择退出模块的行为
  • 使用 nitro.logger 输出构建时日志,而不是使用 console
  • 注意运行时包。 模块添加的任何处理器或插件都会为每个部署目标打包,因此应保持运行时代码精简且与运行时环境无关

#分发模块

要将模块作为 npm 包共享,请将模块对象作为默认导出,用户即可通过包名引用它:

import type { NitroModule } from "nitro/types";

export default {
  name: "my-nitro-module",
  setup(nitro) {
    // ...
  },
} satisfies NitroModule;

nitro 添加为对等依赖,并且只从 nitro/types 导入类型,这样你的包就不会将 Nitro 自身打包进去

#同时作为打包器插件的模块

如果你的包已经提供 Rollup 或 Vite 插件,请将 Nitro 模块放在插件对象的 nitro 键下,而不是发布两个入口:

my-plugin/src/index.ts
import type { Plugin } from "vite";
import type { NitroModule } from "nitro/types";

export default function myPlugin(): Plugin & { nitro: NitroModule } {
  return {
    name: "my-plugin",
    transform(code, id) {
      // ...
    },
    nitro: {
      setup(nitro) {
        nitro.options.virtual["#my-plugin"] = `export const value = "hello";`;
      },
    },
  };
}

同一个导出随后即可用于两个配置文件:

import { defineConfig } from "vite";
import { nitro } from "nitro/vite";
import myPlugin from "my-plugin";

export default defineConfig({
  plugins: [nitro(), myPlugin()],
});

打包器会忽略额外的 nitro 键,而 Nitro 会忽略插件钩子,因此双方都无需了解对方的存在。