Nuxt 的配置和钩子系统使得定制 Nuxt 的每个方面并添加任何集成都成为可能(Vue 插件、CMS、服务器路由、组件、日志记录等)。
Nuxt 模块 是在使用 nuxt dev 启动 Nuxt 开发模式或使用 nuxt build 构建生产项目时,按顺序执行的函数。利用模块,你可以封装、适当测试并作为 npm 包共享自定义解决方案,而无需给项目添加多余的样板代码,也不必修改 Nuxt 本身。
我们推荐你使用我们的启动模板来开始创建 Nuxt 模块:
npm create nuxt -- -t module my-module
yarn create nuxt -t module my-module
pnpm create nuxt -t module my-module
bun create nuxt -- -t module my-module
这将创建一个名为 my-module 的项目,并包含开发和发布模块所需的所有样板代码。
后续步骤:
my-modulenpm run dev:prepare 准备本地开发文件了解如何使用模块启动模板执行基本任务。
虽然你的模块源代码位于 src 目录中,大多数情况下,开发模块时你需要一个 Nuxt 应用。这正是 playground 目录存在的意义。它是一个 Nuxt 应用,你可以在其中自由试验,并已配置为与你的模块一起运行。
你可以像使用任何 Nuxt 应用一样操作 playground。
npm run dev 启动它的开发服务器,修改 src 目录中的模块代码时,服务器会自动重载npm run dev:build 构建应用nuxt 命令都可以针对 playground 目录使用(例如 nuxt <COMMAND> playground)。你也可以在 package.json 中自定义额外的 dev:* 脚本以方便调用。模块启动模板包含一个基础测试套件:
Nuxt 模块自带由 @nuxt/module-builder 提供的构建工具。该构建器无需任何配置,支持 TypeScript,并确保你的资源正确打包,以便分发到其他 Nuxt 应用中。
你可以通过运行 npm run prepack 构建模块。
npm login 进行了身份验证。你可以简单地通过修改模块版本并使用 npm publish 命令发布模块,但模块启动模板提供了一个发布脚本,帮助你确保发布的是可用的模块版本,并更便捷地完成发布流程。
使用发布脚本前,请提交所有改动(我们建议遵循Conventional Commits,以便自动版本提升和更新变更日志),然后执行 npm run release。
运行发布脚本时会发生以下操作:
npm run lint)npm run test)npm run prepack)package.json 中根据需要自定义默认的 release 脚本。Nuxt 模块拥有丰富且强大的 API 和模式,允许它们以几乎任何方式修改 Nuxt 应用。本节将教你如何利用这些能力。
我们可以将 Nuxt 模块分为两类:
modules 目录 的一部分。无论哪种形式,其结构类似。
src/module.ts。模块定义是模块的入口点。Nuxt 在配置中引用你的模块时将加载它。
底层上,Nuxt 模块定义是一个简单且可能是异步的函数,它接受内联用户选项和用于与 Nuxt 交互的 nuxt 对象。
export default function (inlineOptions, nuxt) {
// 你可以在这里任意操作..
console.log(inlineOptions.token) // `123`
console.log(nuxt.options.dev) // `true` 或 `false`
nuxt.hook('ready', (nuxt) => {
console.log('Nuxt 已准备就绪')
})
}
你可以使用 Nuxt Kit 提供的高级辅助 defineNuxtModule 获得类型提示支持。
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule((options, nuxt) => {
nuxt.hook('pages:extend', (pages) => {
console.log(`发现了 ${pages.length} 个页面`)
})
})
然而,我们不建议直接使用此低级函数定义。定义模块时,我们推荐使用带有 meta 属性的对象语法来标识模块,特别是当发布到 npm 时。
此辅助函数通过实现模块所需的许多通用模式,使编写 Nuxt 模块更简单,保证未来兼容性,并改善模块作者和用户的体验。
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
meta: {
// 通常是模块的 npm 包名
name: '@nuxtjs/example',
// 在 `nuxt.config` 中保存模块选项的键名
configKey: 'sample',
// 兼容性约束
compatibility: {
// 支持的 Nuxt 语义化版本范围
nuxt: '>=3.0.0',
},
},
// 模块的默认配置选项,也可以是返回这些选项的函数
defaults: {},
// 注册 Nuxt 钩子的简写语法
hooks: {},
// 配置其他模块 — 这不保证它们在你的模块之前运行,但允许你在它们运行前修改配置
moduleDependencies: {
'some-module': {
// 可以为模块指定版本约束。用户若安装不同版本,Nuxt 启动时会报错。
version: '>=2',
// 默认情况下,moduleDependencies 会添加到 Nuxt 要安装的模块列表,除非设置 optional 为 true。
optional: true,
// 应覆盖 nuxt.options 的配置
overrides: {},
// 应设置的配置,会覆盖模块默认值,但不会覆盖 nuxt.options 中已有配置。
defaults: {},
},
},
// 包含模块逻辑的函数,可以是异步的
setup(moduleOptions, nuxt) {
// ...
},
})
最终,defineNuxtModule 返回一个封装函数,该函数实现低级 (inlineOptions, nuxt) 模块函数签名。该封装函数在调用你的 setup 函数前,会执行默认值等必要步骤:
defaults 和 meta.configKey,自动合并模块选项meta.name 或 meta.configKey 计算的唯一键getOptions 和 getMeta 给 Nuxt 内部使用@nuxt/kit 的 defineNuxtModule,就保证前后向兼容src/runtime。模块本身以及 Nuxt 配置中的其它内容不包含在应用的运行时中。但你可能希望模块为被安装的应用提供或注入运行时代码。运行时目录就是为了此目的。
运行时目录下,可以提供任何与 Nuxt 应用相关的资源,包括:
对于 服务器引擎 Nitro:
或任何你想注入到用户 Nuxt 应用中的其他资源:
你可以在模块定义中注入这些资源到应用。
#imports 等路径导入。
node_modules(即发布模块的所在位置)内的文件启用。模块开发配备了一套官方工具,帮助你管理开发流程。
@nuxt/module-builderNuxt 模块构建工具是一款零配置的构建工具,负责处理构建和发布模块的大部分复杂工作,确保模组产物与 Nuxt 应用的兼容性。
@nuxt/kitNuxt Kit 提供了组合式工具,辅助模块与 Nuxt 应用交互。建议你在可能的情况下使用 Nuxt Kit 工具,确保模块兼容性和代码可读性。
@nuxt/test-utilsNuxt 测试工具是一个集合,帮助在模块测试中搭建和运行 Nuxt 应用。
这里列出常用的模块创作模式。
模块可以读取并修改 Nuxt 配置。例如启用实验性功能的模块示例:
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// 若还不存在 experimental 对象,则创建
nuxt.options.experimental ||= {}
nuxt.options.experimental.componentIslands = true
},
})
复杂配置修改时建议使用 defu。
模块不在应用运行时中,因此其选项也不会被包含。但通常你可能需要在运行时代码中访问模块选项。建议通过 Nuxt 的 runtimeConfig 暴露必要的配置。
import { defineNuxtModule } from '@nuxt/kit'
import { defu } from 'defu'
export default defineNuxtModule({
setup (options, nuxt) {
nuxt.options.runtimeConfig.public.myModule = defu(nuxt.options.runtimeConfig.public.myModule, {
foo: options.foo,
})
},
})
注意,我们使用 defu 以扩展用户提供的公共运行时配置,而非覆盖。
你可以像访问普通运行时配置那样,在插件、组件或应用中访问模块选项:
import { useRuntimeConfig } from '@nuxt/kit'
const options = useRuntimeConfig().public.myModule
addPlugin 注入插件插件是模块添加运行时代码的常用方式。你可以使用 addPlugin 工具从模块中注册插件。
import { addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// 创建路径解析器
const resolver = createResolver(import.meta.url)
addPlugin(resolver.resolve('./runtime/plugin'))
},
})
addComponent 注入 Vue 组件如果模块提供 Vue 组件,可以使用 addComponent 工具将它们添加为 Nuxt 的自动导入组件。
import { addComponent, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
// 从 runtime 目录注入组件
addComponent({
name: 'MySuperComponent', // Vue 模板中使用的组件名
export: 'MySuperComponent', // (可选)若组件为具名导出而非默认导出
filePath: resolver.resolve('runtime/components/MySuperComponent.vue'),
})
// 来自一个库
addComponent({
name: 'MyAwesomeComponent',
export: 'MyAwesomeComponent',
filePath: '@vue/awesome-components',
})
},
})
或者使用 addComponentsDir 一次添加整个目录:
import { addComponentsDir, defineNuxtModule, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
addComponentsDir({
path: resolver.resolve('runtime/components'),
})
},
})
addImports 和 addImportsDir 注入组合函数 (composables)如果模块提供组合函数,可以使用 addImports 工具添加自动导入支持:
import { addImports, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
addImports({
name: 'useComposable', // 组合函数名
as: 'useComposable',
from: resolver.resolve('runtime/composables/useComposable'), // 组合函数路径
})
},
})
或者使用 addImportsDir 一次添加整个目录:
import { addImportsDir, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
addImportsDir(resolver.resolve('runtime/composables'))
},
})
addServerHandler 注入服务器路由import { addServerHandler, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
addServerHandler({
route: '/api/hello',
handler: resolver.resolve('./runtime/server/api/hello/index.get'),
})
},
})
你也可以添加动态路径的服务器路由:
import { addServerHandler, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
addServerHandler({
route: '/api/hello/:name',
handler: resolver.resolve('./runtime/server/api/hello/[name].get'),
})
},
})
如果模块提供其他类型资源,也可以注入。例如向 Nuxt 的 css 数组推入样式表:
import { defineNuxtModule, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
nuxt.options.css.push(resolver.resolve('./runtime/style.css'))
},
})
更高级地,可通过 Nitro 的 publicAssets 选项暴露一整文件夹的资源:
import { createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
nuxt.hook('nitro:config', (nitroConfig) => {
nitroConfig.publicAssets ||= []
nitroConfig.publicAssets.push({
dir: resolver.resolve('./runtime/public'),
maxAge: 60 * 60 * 24 * 365, // 1 年
})
})
},
})
如果你的模块依赖其他模块,可以使用 moduleDependencies 选项添加它们。例如,若你的模块需要使用 Nuxt Tailwind:
import { createResolver, defineNuxtModule } from '@nuxt/kit'
const resolver = createResolver(import.meta.url)
export default defineNuxtModule<ModuleOptions>({
meta: {
name: 'my-module',
},
moduleDependencies: {
'@nuxtjs/tailwindcss': {
// 你可以为模块指定版本约束
version: '>=6',
// 应覆盖 nuxt.options 的配置
overrides: {
exposeConfig: true,
},
// 应设置的配置,会覆盖默认值,但不覆盖 nuxt.options 中已有配置
defaults: {
config: {
darkMode: 'class',
content: {
files: [
resolver.resolve('./runtime/components/**/*.{vue,mjs,ts}'),
resolver.resolve('./runtime/*.{mjs,js,ts}'),
],
},
},
},
},
},
setup (options, nuxt) {
// 注入包含 Tailwind 指令的 CSS
nuxt.options.css.push(resolver.resolve('./runtime/assets/styles.css'))
},
})
moduleDependencies 选项取代已废弃的 installModule 函数,确保正确的安装顺序与配置合并。生命周期钩子允许你扩展 Nuxt 的各个方面。模块可通过 hooks 对象声明或编程式调用 nuxt.hook 注册钩子。
import { addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
// 通过 `hooks` 对象监听 `app:error` 钩子
hooks: {
'app:error': (err) => {
console.info(`发生错误:${err}`)
},
},
setup (options, nuxt) {
// 编程式监听 `pages:extend` 钩子
nuxt.hook('pages:extend', (pages) => {
console.info(`发现了 ${pages.length} 个页面`)
})
},
})
close 钩子实现。import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
nuxt.hook('close', async (nuxt) => {
// 你的清理代码
})
},
})
模块还可以定义和调用自己的钩子,这是一种强大的模式,可以使您的模块可扩展。
如果您希望其他模块能够订阅您模块的钩子,您应该在 modules:done 钩子中调用它们。这确保所有其他模块都有机会在自己的 setup 函数中进行设置并注册它们的监听器到您的钩子。
// my-module/module.ts
import { defineNuxtModule } from '@nuxt/kit'
export interface ModuleHooks {
'my-module:custom-hook': (payload: { foo: string }) => void
}
export default defineNuxtModule({
setup (options, nuxt) {
// 在 `modules:done` 钩子中调用自定义钩子
nuxt.hook('modules:done', async () => {
const payload = { foo: 'bar' }
await nuxt.callHook('my-module:custom-hook', payload)
})
},
})
如果您需要添加一个可以导入到用户应用中的虚拟文件,可以使用 addTemplate 工具。
import { addTemplate, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// 文件会加入 Nuxt 内部虚拟文件系统,可通过 '#build/my-module-feature.mjs' 导入
addTemplate({
filename: 'my-module-feature.mjs',
getContents: () => 'export const myModuleFeature = () => "hello world !"',
})
},
})
服务端使用 addServerTemplate:
import { addServerTemplate, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// 文件加入 Nitro 虚拟文件系统,服务器代码中可通过 'my-server-module.mjs' 导入
addServerTemplate({
filename: 'my-server-module.mjs',
getContents: () => 'export const myServerModule = () => "hello world !"',
})
},
})
你可能想向用户项目添加类型声明(比如扩展 Nuxt 接口或提供全局类型),Nuxt 提供了 addTypeTemplate 工具用于写入文件并添加到生成的 nuxt.d.ts。
若需扩展 Nuxt 管理的类型,可使用 addTypeTemplate:
import { addTemplate, addTypeTemplate, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
addTypeTemplate({
filename: 'types/my-module.d.ts',
getContents: () => `// 由 my-module 生成
interface MyModuleNitroRules {
myModule?: { foo: 'bar' }
}
declare module 'nitropack' {
interface NitroRouteRules extends MyModuleNitroRules {}
interface NitroRouteConfig extends MyModuleNitroRules {}
}
export {}`,
})
},
})
如果需要更灵活控制,可以用 prepare:types 钩子注册回调注入类型引用。
const template = addTemplate({ /* template options */ })
nuxt.hook('prepare:types', ({ references }) => {
references.push({ path: template.dst })
})
若需要更新模板/虚拟文件,可以使用 updateTemplates:
nuxt.hook('builder:watch', (event, path) => {
if (path.includes('my-module-feature.config')) {
// 重新加载已注册的模板
updateTemplates({ filter: t => t.filename === 'my-module-feature.mjs' })
}
})
测试确保你的模块在各种配置下均能正常工作。本节讲述如何对模块进行多种测试。
Nuxt 测试工具 是模块端到端测试的首选库。使用流程:
test/fixtures/* 下创建 Nuxt 应用用作“测试用例”@nuxt/test-utils 的工具对用例操作(例如请求页面)示例用例:
// 1. 创建 Nuxt 应用用作测试用例
import MyModule from '../../../src/module'
export default defineNuxtConfig({
ssr: true,
modules: [
MyModule,
],
})
测试代码:
import { describe, expect, it } from 'vitest'
import { fileURLToPath } from 'node:url'
import { $fetch, setup } from '@nuxt/test-utils/e2e'
describe('ssr', async () => {
// 2. 用此测试用例设置 Nuxt
await setup({
rootDir: fileURLToPath(new URL('./fixtures/ssr', import.meta.url)),
})
it('渲染首页', async () => {
// 3. 使用 `@nuxt/test-utils` 与用例交互
const html = await $fetch('/')
// 4. 执行断言
expect(html).toContain('<div>ssr</div>')
})
})
// 5. 可以重复编写其它测试
describe('csr', async () => { /* ... */ })
拥有 playground Nuxt 应用,方便你开发时测试模块。模块启动模板集成了 playground。
你也可以在其他 Nuxt 应用(非模块仓库内)本地测试模块。使用 npm pack 或包管理器创建模块压缩包,再在测试项目中将模块添加到 package.json,如 "my-module": "file:/path/to/tarball.tgz"。
这样,你就可以像普通项目一样引用 my-module。
强大的工具也意味着责任。模块虽强大,创建模块时请牢记以下最佳实践,以保证应用性能和良好的开发体验。
如前所述,Nuxt 模块可以是异步的,方便调用某些 API 或异步函数。
但是要注意,Nuxt 会等待模块完成 setup 后才继续加载下一个模块和启动开发服务器或构建等。因此应尽量将耗时逻辑延迟到 Nuxt 钩子中。
当你的模块需要进行一次性设置(例如生成配置文件、数据库初始化或安装依赖)时,使用生命周期钩子,而非在主 setup 函数中运行这些逻辑。
import { addServerHandler, defineNuxtModule } from 'nuxt/kit'
import semver from 'semver'
export default defineNuxtModule({
meta: {
name: 'my-database-module',
version: '1.0.0',
},
async onInstall (nuxt) {
// 一次性设置:创建数据库模式、生成配置文件等
await generateDatabaseConfig(nuxt.options.rootDir)
},
async onUpgrade (options, nuxt, previousVersion) {
// 处理版本相关的迁移
if (semver.lt(previousVersion, '1.0.0')) {
await migrateLegacyData()
}
},
setup (options, nuxt) {
// 常规设置逻辑,构建时执行
addServerHandler({ /* ... */ })
},
})
此模式避免每次构建都执行耗时操作,提升开发体验。详情请参阅 生命周期钩子文档。
模块应为所有导出的配置、插件、API、组合函数或组件添加显式前缀,避免与其他模块或内核冲突。
理想情况下,使用你的模块名作为前缀(例如模块名为 nuxt-foo,则导出 <FooButton> 和 useFooBar(),而非 <Button> 和 useBar())。
Nuxt 对 TypeScript 支持一流。暴露类型和使用 TypeScript 编写模块即使用户未直接使用 TypeScript,也能带来更佳体验。
Nuxt 依赖原生 ESM。请查阅原生 ES 模块获取更多信息。
在 README 中文档化模块使用方式:
并附上集成网站和文档链接,效果更佳。
推荐在模块 README 中附上最小复现示例的 StackBlitz 链接。
这不仅给潜在用户快速体验模块的途径,也方便用户遇到问题时提供最小复现给你。
Nuxt、Nuxt Kit 及其它新工具都考虑了向前和向后兼容。
请使用“X for Nuxt”而非“X for Nuxt 3”,以避免生态碎片化,并使用 meta.compatibility 来设置 Nuxt 版本约束。
模块启动模板遵循一套默认工具和配置(如 ESLint),若打算开源模块,建议保持默认设置,使模块与其它社区模块保持一致风格,便于贡献者参与。
Nuxt 模块生态累计每月超过 1500 万 NPM 下载量,提供丰富功能和各种工具的集成。你也可以加入这个生态!
官方模块 以 @nuxt/ 命名空间为前缀(如 @nuxt/content),由 Nuxt 团队开发维护。社区贡献亦欢迎!
社区模块 以 @nuxtjs/ 命名空间为前缀(如 @nuxtjs/tailwindcss),由社区成员维护,欢迎各方贡献。
第三方及其它社区模块 通常以 nuxt- 前缀命名,任何人均可创建,使用这种前缀便于 npm 上模块的发现。适合试验和构想新思路。
私有或个人模块 针对个人或公司需求创建,不须遵循特定命名规则,通常位于 npm 组织命名空间下(如 @my-company/nuxt-auth)。
欢迎将社区模块添加到模块列表。提交模块注册请求,请在 Nuxt 官方仓库 nuxt/modules 中创建 issue。Nuxt 团队可帮助你应用最佳实践后再加入列表。
nuxt-modules 和 @nuxtjs/将模块移至 nuxt-modules 组织,意味着有更多人协助维护,共同打造完美解决方案。
如果你已有公开模块并想移交给 nuxt-modules,请在 Nuxt 模块仓库开 issue。
加入 nuxt-modules 后,我们可将你的社区模块重命名为 @nuxtjs/ 命名空间,并为其提供子域(如 my-module.nuxtjs.org)方便托管文档。