用过装备(第 4 章的 Pinia 集成),现在进车间学造装备。本节把"一段每次新项目都要复制的配置与插件"提炼成模块:defineNuxtModule 的结构、模块能改什么(配置、钩子、模板)、本地开发调试循环。写完这节你会重新理解 modules 数组里每一项的价值。
先看症状:你在第五个项目里复制同一段"给所有接口加统一前缀、注册一个埋点插件、注入一个默认配置"的样板。每次复制都要手改三处,漏一处出隐晦 bug。模块化就是把这三件事封装成一个可安装单元:
// modules/telemetry/index.ts // 本地模块:统一埋点能力,任何项目一行配置接入 import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit' export default defineNuxtModule({ meta: { name: 'telemetry', // 模块名,配置命名空间 configKey: 'telemetry', // nuxt.config 里读取的键名 }, // 默认配置:用户没写时兜底 defaults: { endpoint: '/api/telemetry', sampleRate: 1, }, setup(options, nuxt) { // 能力一:读用户配置并与默认值合并 if (options.sampleRate <= 0) { throw new Error('采样率必须大于零') } // 能力二:注册插件——埋点插件的物理载体 const resolver = createResolver(import.meta.url) addPlugin(resolver.resolve('./runtime/plugin')) // 能力三:注入运行时配置,插件里用 useRuntimeConfig 读到 nuxt.options.runtimeConfig.public.telemetry = { endpoint: options.endpoint, sampleRate: options.sampleRate, } // 能力四:挂构建钩子——构建期做事的标准时机 nuxt.hook('nitro:config', (nitroConfig) => { // 让 Nitro 知道埋点上报端点,服务端也能上报 nitroConfig.runtimeConfig = nitroConfig.runtimeConfig || {} }) }, })
// modules/telemetry/runtime/plugin.ts // 模块注入的插件:跑在应用两端 export default defineNuxtPlugin((nuxtApp) => { const config = useRuntimeConfig().public.telemetry // 暴露全局能力:组件里 useNuxtApp().$telemetry 即可用 return { provide: { telemetry: { track(event: string, data?: Record<string, unknown>) { if (Math.random() > config.sampleRate) return // 采样 $fetch(config.endpoint, { method: 'POST', body: { event, data, ts: Date.now() }, }).catch(() => {}) // 埋点失败静默,不打扰业务 }, }, }, } })
接入只需两行:
// nuxt.config.ts export default defineNuxtConfig({ modules: ['~/modules/telemetry'], // 本地路径直接可装 telemetry: { sampleRate: 0.5 }, // 配置进模块的命名空间 })
<!-- 任意组件 --> <script setup> const { $telemetry } = useNuxtApp() function checkout() { $telemetry.track('checkout-click', { from: 'cart' }) } </script>
@nuxt/kit 提供的改装面,按频率排序:
| 能力 | API | 典型场景 |
|---|---|---|
| 加插件 | addPlugin | 注入全局能力(上例) |
| 加组件 | addComponentsDir | 组件库模块注册自己的组件 |
| 加组合函数 | addImports | 模块提供 useXxx 自动导入 |
| 改配置 | nuxt.options | 合并默认 runtimeConfig |
| 挂钩子 | nuxt.hook | 构建期介入(加模板、改产物) |
| 加服务端处理器 | addServerHandler | 模块自带 API 端点 |
| 生成模板 | addTemplate | 按配置生成代码文件 |
模块与插件的区别值得一表说清:插件跑在应用运行时(每个请求/每次加载),模块跑在构建时(只在 nuxt dev / build 启动阶段)。模块是"改装车间",装完就不在运行时存在;插件是"装上去的零件",随应用跑。模块的常见产出就是往应用里塞插件、组件、配置。
本地模块目录(modules/xxx)会被 Nuxt 自动发现(较新版本支持 modules 目录约定),改模块代码需要重启开发服务器——它只在启动时执行一次。调试三个手段:
// 手段一:安装时打印,确认模块被加载、配置长什么样 setup(options, nuxt) { if (nuxt.options.dev) { console.log('[telemetry] 装载,配置:', options) } } // 手段二:用 nuxt.hook 观察构建流程走到哪一步 nuxt.hook('modules:done', () => console.log('[telemetry] 所有模块装载完成')) // 手段三:kit 的 buildLog 类工具输出结构化信息(或直接 console)
成熟后发布成 npm 包,modules 数组里写包名即可给别人用——你写模块的姿势没变,变的只是安装方式。团队内部常见形态是私有包:公司统一埋点、鉴权、设计系统各一个模块,新项目接入成本从"复制三十行改三处"降到"写一行配置"。
判据三条,满足其一就值得:
反过来,单项目的一次性定制直接写 nuxt.config 与 plugins 就好,过早模块化是负资产。
⚠️ 常见坑:在模块 setup 里做异步重活拖慢每次 dev 启动。模块启动期执行,一次远程请求加两秒,团队每次热重启都交这笔税。构建期数据要么缓存,要么挪到 nitro 钩子里按需做。
💡 关键直觉:模块是"带安装器的配置包"——它把你对项目做的所有手工改造(插件、组件、配置、端点)打包成一个可声明依赖的单元。读懂 defineNuxtModule,Nuxt 生态对你就从魔法变成工具箱。