# 模块 > 使用模块扩展 Nitro 的构建时行为 Nitro 模块是在 **Nitro 实例初始化时运行一次**的函数(在开发、构建和预渲染过程中)。它们接收 `nitro` 构建上下文,可用于修改选项、注册构建时钩子、添加虚拟文件或注册路由处理器 ::note 模块在 **构建时**运行。要扩展服务器的**运行时**行为(请求[生命周期](/docs/lifecycle)、错误处理、关闭),请改用[插件](/docs/plugins) :: | | 模块 | [插件](/docs/plugins) | | --- | --- | --- | | 运行时机 | 一次,在初始化和构建期间 | 一次,在服务器启动时 | | 接收对象 | `nitro`(构建上下文) | `nitroApp`(运行时应用) | | 典型用途 | 修改配置、添加处理器和虚拟文件、接入构建流程 | 接入请求/响应生命周期 | | 是否打包到输出中 | 否 | 是 | ## 使用模块 使用 `modules` 配置数组注册模块: ```ts [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/` 目录中的文件会自动注册为模块(类似于用于[运行时插件](/docs/plugins)的 `plugins/`) 只有以下两种模式会自动注册(支持任意以下扩展名:`.ts`、`.mts`、`.cts`、`.js`、`.mjs`、`.cjs`、`.tsx`、`.jsx`): - `modules/*.ts`(单文件模块) - `modules/*/index.ts`(目录模块) `modules/` 中不匹配这些模式的任何其他文件(例如模块自身的 `runtime/` 文件)都会被扫描器忽略 通过路径或包名引用的模块只会被解析和安装**一次**;指向同一文件的重复条目会被跳过 ::tip 在 [Vite](/docs/vite) 项目中,`vite.config.ts` 中任何带有 `nitro` 键的插件都会自动注册为 Nitro 模块。无需再将其列入 `modules` :: ## 编写模块 模块是一个包含 `setup` 函数的对象,该函数接收 [`Nitro`](https://github.com/nitrojs/nitro/blob/main/src/types/nitro.ts) 实例。使用 `nitro/types` 中的 `NitroModule` 类型来确保类型安全: ```ts [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` | 解析后的配置(可变):`handlers`、`virtual`、`plugins`、`runtimeConfig` 以及其他所有选项 | | `nitro.hooks` | 构建时[钩子](#build-time-hooks)(`hookable` 实例) | | `nitro.logger` | 用于构建时输出的带标签 [consola](https://github.com/unjs/consola) 日志记录器 | | `nitro.meta` | Nitro 版本信息(`version`、`majorVersion`) | ## 构建时钩子 模块可以通过 `nitro.hooks.hook()` 接入构建生命周期: ```ts [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) => void` | 预渲染之前。添加或移除要预渲染的路由 | | `prerender:config` | `(config) => void` | 创建预渲染器的 Nitro 配置时 | | `prerender:generate` | `(route, nitro) => void` | 对于每个路由,在生成该路由之前 | | `prerender:route` | `(route) => void` | 对于每个路由,在生成该路由之后 | | `prerender:done` | `({ prerenderedRoutes, failedRoutes }) => void` | 预渲染完成之后 | | `close` | `() => void` | Nitro 实例关闭时 | 所有钩子都可以是异步的。完整列表和确切签名请参阅[钩子源码](https://github.com/nitrojs/nitro/blob/main/src/types/hooks.ts),有关 `prerender:*` 钩子的具体用法,请参阅[预渲染指南](/docs/prerendering#hooks) ::tip 这里不提供运行时钩子(`request`、`response`、`error`);它们属于[插件](/docs/plugins)。模块仍然可以通过向 `nitro.options.plugins` 推送插件文件来添加运行时行为 :: ## 最佳实践 - **为每个模块提供一个 `name`。** 这样可以使模块在配置中更易识别;对于插件对象,Rollup 和 Vite 也要求通过它来识别插件 - **优先使用插件对象形式。** `{ name, nitro: { setup } }` 同时可被 Nitro、Rollup 和 Vite 理解,因此一个导出即可同时适用于三者。Vite 应用的用户将其放入 `plugins`,独立 Nitro 应用的用户将其放入 `modules`,无论采用哪种方式,模块都能运行 - **保持模块幂等。** 在开发环境中,配置变更时可能会重新处理选项,因此在推送重复的处理器、插件或虚拟条目之前进行检查 - **优先使用钩子而不是打补丁。** 使用构建钩子响应生命周期事件,而不是修改 Nitro 内部实现或覆盖函数 - **尊重用户选项。** 从 `nitro.options` 中读取现有值并在其基础上进行扩展,而不是替换它们。让用户可以选择退出模块的行为 - **使用 `nitro.logger`** 输出构建时日志,而不是使用 `console` - **注意运行时包。** 模块添加的任何处理器或插件都会为每个部署目标打包,因此应保持运行时代码精简且与运行时环境无关 ## 分发模块 要将模块作为 npm 包共享,请将模块对象作为**默认导出**,用户即可通过包名引用它: ```ts [my-nitro-module/src/index.ts] import type { NitroModule } from "nitro/types"; export default { name: "my-nitro-module", setup(nitro) { // ... }, } satisfies NitroModule; ``` ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ modules: ["my-nitro-module"], }); ``` 将 `nitro` 添加为对等依赖,并且只从 `nitro/types` 导入类型,这样你的包就不会将 Nitro 自身打包进去 ### 同时作为打包器插件的模块 如果你的包已经提供 Rollup 或 Vite 插件,请将 Nitro 模块放在插件对象的 `nitro` 键下,而不是发布两个入口: ```ts [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";`; }, }, }; } ``` 同一个导出随后即可用于两个配置文件: ::code-group ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import myPlugin from "my-plugin"; export default defineConfig({ plugins: [nitro(), myPlugin()], }); ``` ```ts [nitro.config.ts] import { defineConfig } from "nitro"; import myPlugin from "my-plugin"; export default defineConfig({ modules: [myPlugin()], }); ``` :: 打包器会忽略额外的 `nitro` 键,而 Nitro 会忽略插件钩子,因此双方都无需了解对方的存在。