Module Author Guide
Nuxt 的 配置 和 钩子 系统使得可以自定义 Nuxt 的每个方面,并添加你可能需要的任何集成(Vue 插件、CMS、服务器路由、组件、日志记录等)。
Nuxt 模块 是在使用 nuxi dev
启动 Nuxt 开发模式或使用 nuxi 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
项目,包含开发和发布模块所需的所有样板代码。
后续步骤:
- 在你喜欢的 IDE 中打开
my-module
- 使用你偏好的包管理器安装依赖
- 使用
npm run dev:prepare
为开发准备本地文件 - 继续阅读本文档以了解更多关于 Nuxt 模块的信息
使用启动模板
学习如何使用模块启动模板执行基本任务。
如何开发
虽然你的模块源代码位于 src
目录中,但在大多数情况下,开发模块需要一个 Nuxt 应用。这就是 playground
目录的用途。它是一个已配置好使用你的模块的 Nuxt 应用,你可以在其中进行实验。
你可以像操作任何 Nuxt 应用一样与 playground 交互:
- 使用
npm run dev
启动其开发服务器,它会在你更改src
目录中的模块时自动重新加载 - 使用
npm run dev:build
构建它
nuxi
命令都可以针对 playground
目录使用(例如 nuxi <COMMAND> playground
)。你可以根据需要,在 package.json
中声明额外的 dev:*
脚本以便更方便地引用它们。如何测试
模块启动模板带有一个基本的测试套件:
如何构建
Nuxt 模块自带由 @nuxt/module-builder
提供的构建器。此构建器无需你进行任何配置,支持 TypeScript,并确保你的资源被正确打包以分发到其他 Nuxt 应用。
你可以通过运行 npm run prepack
构建你的模块。
playground
会处理,发布脚本在发布时也会为你搞定。如何发布
npm login
进行身份验证。虽然你可以通过提升版本并使用 npm publish
命令发布模块,但模块启动模板带有一个发布脚本,可以帮助你确保发布一个正常工作的模块版本到 npm 以及更多。
要使用发布脚本,首先提交所有更改(我们建议遵循 Conventional Commits 以利用自动版本提升和变更日志更新),然后使用 npm run release
运行发布脚本。
运行发布脚本时,将发生以下步骤:
- 首先,它会运行你的测试套件,包括:
- 运行代码检查器(
npm run lint
) - 运行单元、集成和端到端测试(
npm run test
) - 构建模块(
npm run prepack
)
- 运行代码检查器(
- 然后,如果测试套件通过,它会继续发布你的模块,包括:
- 根据你的 Conventional Commits 提升模块版本并生成变更日志
- 将模块发布到 npm(为此,模块会再次构建以确保发布的产物中包含更新的版本号)
- 将表示新发布版本的 git 标签推送到你的 git 远程 origin
package.json
中的默认 release
脚本。开发模块
Nuxt 模块提供了一系列强大的 API 和模式,允许以几乎任何方式改变 Nuxt 应用。本节将教你如何利用这些功能。
模块结构
我们可以考虑两种 Nuxt 模块:
- 发布到 npm 的模块 - 你可以在 Nuxt 网站 上查看一些社区模块的列表。
- “本地”模块,它们存在于 Nuxt 项目本身中,可以是 nuxt 配置中的内联模块 或作为 模块目录 的一部分。
无论哪种情况,它们的结构类似。
模块定义
src/module.ts
。模块定义是模块的入口点。当在 Nuxt 配置中引用你的模块时,Nuxt 会加载它。
在低层次上,Nuxt 模块定义是一个简单的、可能异步的函数,接受内联用户选项和一个 nuxt
对象以与 Nuxt 交互。
export default function (inlineOptions, nuxt) {
// 你可以在这里做任何你想做的事情
console.log(inlineOptions.token) // `123`
console.log(nuxt.options.dev) // `true` 或 `false`
nuxt.hook('ready', async 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 版本的 Semver 版本
nuxt: '>=3.0.0'
}
},
// 模块的默认配置选项,也可以是一个返回这些选项的函数
defaults: {},
// 简写形式注册 Nuxt 钩子
hooks: {},
// 包含模块逻辑的函数,可以是异步的
setup(moduleOptions, nuxt) {
// ...
}
})
最终,defineNuxtModule
返回一个带有低层次 (inlineOptions, nuxt)
模块签名的包装函数。此包装函数在调用你的 setup
函数之前应用默认值和其他必要步骤:
- 支持
defaults
和meta.configKey
以自动合并模块选项 - 提供类型提示和自动类型推断
- 添加对 Nuxt 2 基本兼容性的垫片
- 使用从
meta.name
或meta.configKey
计算的唯一键确保模块只安装一次 - 自动注册 Nuxt 钩子
- 根据模块元数据自动检查兼容性问题
- 暴露
getOptions
和getMeta
供 Nuxt 内部使用 - 只要模块使用最新版本的
@nuxt/kit
中的defineNuxtModule
,就能确保向后和向前兼容性 - 与模块构建工具集成
运行时目录
src/runtime
。模块和 Nuxt 配置中的所有内容一样,不会包含在应用的运行时中。然而,你可能希望你的模块为安装它的应用提供或注入运行时代码。这就是运行时目录的作用。
在运行时目录中,你可以提供与 Nuxt 应用相关的任何类型的资源:
- Vue 组件
- 组合式函数
- Nuxt 插件
对于 服务器引擎 Nitro:
- API 路由
- 中间件
- Nitro 插件
或你想注入用户 Nuxt 应用的任何其他类型资源:
- 样式表
- 3D 模型
- 图片
- 等等
然后,你可以从 模块定义 中将所有这些资源注入到应用中。
#imports
或类似位置显式导入。
由于性能原因,
node_modules
(已发布模块最终所在位置)中的文件不会启用自动导入。
如果你使用模块启动模板,playground 中也不会启用自动导入。
工具
模块附带了一套官方工具,帮助你进行开发。
@nuxt/module-builder
Nuxt Module Builder 是一个零配置的构建工具,负责处理构建和发布模块的所有繁重工作。它确保你的模块构建产物与 Nuxt 应用的正确兼容性。
@nuxt/kit
Nuxt Kit 提供可组合的实用工具,帮助你的模块与 Nuxt 应用交互。建议尽可能使用 Nuxt Kit 实用工具,而不是手动替代方案,以确保更好的兼容性和模块代码的可读性。
@nuxt/test-utils
Nuxt Test Utils 是一组实用工具集合,帮助在模块测试中设置和运行 Nuxt 应用。
配方
这里介绍了一些常见的模块开发模式。
修改 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
来扩展用户提供的公共运行时配置,而不是覆盖它。
然后,你可以在插件、组件或应用中像访问其他运行时配置一样访问模块选项:
const options = useRuntimeConfig().public.myModule
使用 addPlugin
注入插件
插件是模块添加运行时逻辑的常见方式。你可以使用 addPlugin
实用工具从模块中注册它们。
import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// Create resolver to resolve relative paths
const resolver = createResolver(import.meta.url)
addPlugin(resolver.resolve('./runtime/plugin'))
}
})
使用 addComponent
注入 Vue 组件
如果你的模块需要提供 Vue 组件,你可以使用 addComponent
实用工具将它们添加为 Nuxt 自动导入的组件。
import { defineNuxtModule, addComponent } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const resolver = createResolver(import.meta.url)
// 从运行时目录
addComponent({
name: 'MySuperComponent', // 在 Vue 模板中使用的组件名称
export: 'MySuperComponent', // (可选)如果组件是命名导出而非默认导出
filePath: resolver.resolve('runtime/components/MySuperComponent.vue')
})
// 从库
addComponent({
name: 'MyAwesomeComponent', // 在 Vue 模板中使用的组件名称
export: 'MyAwesomeComponent', // (可选)如果组件是命名导出而非默认导出
filePath: '@vue/awesome-components'
})
}
})
或者,你可以使用 addComponentsDir
添加整个目录。
import { defineNuxtModule, addComponentsDir } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const resolver = createResolver(import.meta.url)
addComponentsDir({
path: resolver.resolve('runtime/components')
})
}
})
使用 addImports
和 addImportsDir
注入组合式函数
如果你的模块需要提供组合式函数,你可以使用 addImports
实用工具将它们添加为 Nuxt 自动导入的函数。
import { defineNuxtModule, addImports, createResolver } 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 { defineNuxtModule, addImportsDir, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const resolver = createResolver(import.meta.url)
addImportsDir(resolver.resolve('runtime/composables'))
}
})
使用 addServerHandler
注入服务器路由
import { defineNuxtModule, addServerHandler, createResolver } 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 { defineNuxtModule, addServerHandler, createResolver } 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, addPlugin, 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 { defineNuxtModule, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
nuxt.hook('nitro:config', async (nitroConfig) => {
nitroConfig.publicAssets ||= []
nitroConfig.publicAssets.push({
dir: resolver.resolve('./runtime/public'),
maxAge: 60 * 60 * 24 * 365 // 1 year
})
})
}
})
在模块中使用其他模块
如果你的模块依赖于其他模块,你可以使用 Nuxt Kit 的 installModule
实用工具添加它们。例如,如果你想在模块中使用 Nuxt Tailwind,你可以按如下方式添加:
import { defineNuxtModule, createResolver, installModule } from '@nuxt/kit'
export default defineNuxtModule<ModuleOptions>({
async setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
// We can inject our CSS file which includes Tailwind's directives
nuxt.options.css.push(resolver.resolve('./runtime/assets/styles.css'))
await installModule('@nuxtjs/tailwindcss', {
// 模块配置
exposeConfig: true,
config: {
darkMode: 'class',
content: {
files: [
resolver.resolve('./runtime/components/**/*.{vue,mjs,ts}'),
resolver.resolve('./runtime/*.{mjs,js,ts}')
]
}
}
})
}
})
使用钩子
生命周期钩子 允许你扩展 Nuxt 的几乎每个方面。模块可以通过编程方式或通过定义中的 hooks
映射挂钩到它们。
import { defineNuxtModule, addPlugin, createResolver } 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} 个页面`);
})
}
})
如果你的模块开启、处理或启动了监视器,你应该在 Nuxt 生命周期结束时关闭它。为此可以使用
close
钩子。import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
nuxt.hook('close', async nuxt => {
// 你的自定义代码
})
}
})
添加模板/虚拟文件
如果你需要添加一个可以导入到用户应用的虚拟文件,你可以使用 addTemplate
实用工具。
import { defineNuxtModule, addTemplate } 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 { defineNuxtModule, addServerTemplate } 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 { defineNuxtModule, addTemplate, addTypeTemplate } 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 'nitro/types' {
interface NitroRouteRules extends MyModuleNitroRules {}
interface NitroRouteConfig extends MyModuleNitroRules {}
}
export {}`
})
}
})
如果需要更精细的控制,你可以使用 prepare:types
钩子注册一个回调来注入你的类型。
const template = addTemplate({ /* 模板选项 */ })
nuxt.hook('prepare:types', ({ references }) => {
references.push({ path: template.dst })
})
更新模板
如果需要更新模板/虚拟文件,你可以这样利用 updateTemplates
实用工具:
nuxt.hook('builder:watch', async (event, path) => {
if (path.includes('my-module-feature.config')) {
// 这将重新加载你注册的模板
updateTemplates({ filter: t => t.filename === 'my-module-feature.mjs' })
}
})
测试
测试有助于确保你的模块在各种设置下按预期工作。本节介绍如何对模块执行各种类型的测试。
单元和集成测试
端到端测试
Nuxt Test Utils 是帮助你以端到端方式测试模块的首选库。以下是使用它的工作流程:
- 在
test/fixtures/*
中创建一个用作“测试夹具”的 Nuxt 应用 - 在测试文件中使用此夹具设置 Nuxt
- 使用
@nuxt/test-utils
的实用工具与夹具交互(例如获取页面) - 对夹具执行相关检查(例如“HTML 包含...”)
- 重复
实际上,夹具如下:
// 1. 创建一个用作“夹具”的 Nuxt 应用
import MyModule from '../../../src/module'
export default defineNuxtConfig({
ssr: true,
modules: [
MyModule
]
})
对应的测试如下:
import { describe, it, expect } from 'vitest'
import { fileURLToPath } from 'node:url'
import { setup, $fetch } 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 和外部进行手动 QA
在开发模块时,拥有一个用于测试的 playground Nuxt 应用非常有用。模块启动模板为此集成了一个。
你也可以在其他 Nuxt 应用(非模块仓库中的应用)中本地测试你的模块。为此,你可以使用 npm pack
命令或你的包管理器的等效命令,从模块创建一个 tarball。然后在你的测试项目中,你可以将模块添加到 package.json
的依赖中,如:"my-module": "file:/path/to/tarball.tgz"
。
之后,你应该可以像在任何常规项目中一样引用 my-module
。
最佳实践
能力越大,责任越大。虽然模块功能强大,但在开发模块时需要注意以下最佳实践,以保持应用性能和良好的开发者体验。
异步模块
正如我们所见,Nuxt 模块可以是异步的。例如,你可能想开发一个需要获取 API 或调用异步函数的模块。
然而,要小心异步行为,因为 Nuxt 会等待你的模块设置完成,然后才会继续处理下一个模块并启动开发服务器、构建过程等。优先将耗时逻辑推迟到 Nuxt 钩子中。
始终为暴露的接口添加前缀
Nuxt 模块应为任何暴露的配置、插件、API、组合式函数或组件提供明确的前缀,以避免与其他模块和内部冲突。
理想情况下,你应该以模块名称作为前缀(例如,如果你的模块名为 nuxt-foo
,暴露 <FooButton>
和 useFooBar()
,而不是 <Button>
和 useBar()
)。
友好支持 TypeScript
Nuxt 为最佳开发者体验提供了一流的 TypeScript 集成。
暴露类型并使用 TypeScript 开发模块,即使不直接使用 TypeScript,也能为用户带来好处。
避免使用 CommonJS 语法
Nuxt 依赖原生 ESM。请阅读 原生 ES 模块 获取更多信息。
记录模块使用方法
考虑在 readme 文件中记录模块使用方法:
- 为什么要使用这个模块?
- 如何使用这个模块?
- 这个模块做什么?
链接到集成网站和文档始终是一个好主意。
提供 StackBlitz 演示或样板
为你的模块创建一个最小重现并使用 StackBlitz,并将其添加到模块的 readme 中是一个好习惯。
这不仅为潜在的模块用户提供了一个快速、简单的方式来试验模块,也为他们在遇到问题时提供了一种便捷的方式来创建最小重现并发送给你。
不要宣传特定 Nuxt 版本
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 上更容易被发现。这是草拟和尝试想法的最佳起点!
私有或个人模块 是为你自己的用例或公司创建的模块。它们无需遵循任何命名规则即可与 Nuxt 一起工作,通常以 npm 组织为作用域(例如 @my-company/nuxt-auth
)。
列出你的社区模块
欢迎任何社区模块在 模块列表 上列出。要列出你的模块,请在 nuxt/modules 仓库中开一个 issue。Nuxt 团队可以帮助你在列出前应用最佳实践。
nuxt-modules
和 @nuxtjs/
加入
通过将你的模块迁移到 nuxt-modules,总会有人帮助你,这样我们就可以联合起来打造一个完美的解决方案。
如果你有一个已经发布且正常工作的模块,并想将其转移到 nuxt-modules
,请在 nuxt/modules 中开一个 issue。
加入 nuxt-modules
后,我们可以将你的社区模块重命名为 @nuxtjs/
作用域,并为其文档提供一个子域名(例如 my-module.nuxtjs.org
)。