模块
使用模块扩展 Nitro 的构建时行为
Nitro 模块是在 Nitro 实例初始化时运行一次的函数(在开发、构建和预渲染过程中)。它们接收 nitro 构建上下文,可用于修改选项、注册构建时钩子、添加虚拟文件或注册路由处理器
| 模块 | 插件 | |
|---|---|---|
| 运行时机 | 一次,在初始化和构建期间 | 一次,在服务器启动时 |
| 接收对象 | nitro(构建上下文) | nitroApp(运行时应用) |
| 典型用途 | 修改配置、添加处理器和虚拟文件、接入构建流程 | 接入请求/响应生命周期 |
| 是否打包到输出中 | 否 | 是 |
#使用模块
使用 modules 配置数组注册模块:
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 类型来确保类型安全:
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 | 解析后的配置(可变):handlers、virtual、plugins、runtimeConfig 以及其他所有选项 |
nitro.hooks | 构建时钩子(hookable 实例) |
nitro.logger | 用于构建时输出的带标签 consola 日志记录器 |
nitro.meta | Nitro 版本信息(version、majorVersion) |
#构建时钩子
模块可以通过 nitro.hooks.hook() 接入构建生命周期:
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 | () => void | Nitro 实例关闭时 |
所有钩子都可以是异步的。完整列表和确切签名请参阅钩子源码,有关 prerender:* 钩子的具体用法,请参阅预渲染指南
Tip
这里不提供运行时钩子(request、response、error);它们属于插件。模块仍然可以通过向 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 键下,而不是发布两个入口:
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 会忽略插件钩子,因此双方都无需了解对方的存在。