模块作者指南
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:*
脚本以方便引用。如何测试
模块启动器附带一个基本的测试套件:
- 一个由 ESLint 提供支持的 linter,可以使用
npm run lint
运行 - 一个由 Vitest 提供支持的测试运行器,可以使用
npm run test
或npm run test:watch
运行
如何构建
Nuxt 模块带有由 @nuxt/module-builder
提供的构建工具。该构建工具在您的端不需要任何配置,支持 TypeScript,并确保您的资产被正确打包以便分发给其他 Nuxt 应用程序。
您可以通过运行 npm run prepack
来构建模块。
playground
会处理此事,而发布脚本在发布时也会为您提供支持。如何发布
npm login
进行了身份验证。虽然您可以通过提高版本号并使用 npm publish
命令来发布模块,但模块启动器附带的发布脚本可帮助您确保将模块的可用版本发布到 npm 及其他平台。
要使用发布脚本,首先提交您所有的更改(我们建议您遵循 Conventional Commits 以便利用自动版本提升和变更日志更新),然后使用 npm run release
运行发布脚本。
运行发布脚本时,将发生以下情况:
- 首先,它将运行您的测试套件:
- 运行 linter (
npm run lint
) - 运行单元、集成和 e2e 测试 (
npm run test
) - 构建模块 (
npm run prepack
)
- 运行 linter (
- 然后,如果您的测试套件运行良好,将继续发布您的模块:
- 提高模块版本并生成符合您的 Conventional Commits 的变更日志
- 将模块发布到 npm(为此,该模块将重新构建以确保其更新的版本号被考虑在已发布的工件中)
- 推送代表新发布版本的 git 标签到您的 git 远程源
package.json
中的默认 release
脚本,以更好地满足您的需求。开发模块
Nuxt 模块配备了多种强大的 API 和模式,使其能够以几乎任何可能的方式更改 Nuxt 应用程序。本节教您如何充分利用这些。
模块结构
我们可以将 Nuxt 模块视为两种类型:
- 发布的模块在 npm 上分发 - 您可以在 Nuxt 网站 上查看一些社区模块的列表。
- “本地”模块,它们存在于 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', 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 兼容性添加 shim
- 确保模块仅使用从
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) {
// 创建解析器以解决相对路径
const { resolve } = createResolver(import.meta.url)
addPlugin(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 { resolve } = createResolver(import.meta.url)
nuxt.options.css.push(resolve('./runtime/style.css'))
}
})
另一个更复杂的示例,通过 Nitro 的 publicAssets
选项暴露一组资产:
import { defineNuxtModule, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const { resolve } = createResolver(import.meta.url)
nuxt.hook('nitro:config', async (nitroConfig) => {
nitroConfig.publicAssets ||= []
nitroConfig.publicAssets.push({
dir: resolve('./runtime/public'),
maxAge: 60 * 60 * 24 * 365 // 1年
})
})
}
})
在您的模块中使用其他模块
如果您的模块依赖于其他模块,则可以使用 Nuxt Kit 的 installModule
实用程序添加它们。例如,如果您想在模块中使用 Nuxt Tailwind,您可以像下面这样添加它:
import { defineNuxtModule, createResolver, installModule } from '@nuxt/kit'
export default defineNuxtModule<ModuleOptions>({
async setup (options, nuxt) {
const { resolve } = createResolver(import.meta.url)
// 我们可以注入我们的 CSS 文件,其中包括 Tailwind 的指令
nuxt.options.css.push(resolve('./runtime/assets/styles.css'))
await installModule('@nuxtjs/tailwindcss', {
// 模块配置
exposeConfig: true,
config: {
darkMode: 'class',
content: {
files: [
resolve('./runtime/components/**/*.{vue,mjs,ts}'),
resolve('./runtime/*.{mjs,js,ts}')
]
}
}
})
}
})
使用钩子
生命周期钩子 允许您以编程方式或通过定义中的 hooks
映射扩展几乎每个方面的 Nuxt。
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({ /* template options */ })
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 是帮助您以端到端方式测试模块的首选库。这里是要采用的工作流程:
- 创建一个 Nuxt 应用程序作为
test/fixtures/*
中的“样本” - 在测试文件中使用此样本设置 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 应用程序在开发模块时测试模块是非常有用的。模块启动器集成了一个用于此目的的 playground。
您可以通过在本地与其他 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 演示或样板
创建一个使用您的模块的最小重现示例并添加到您的模块 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 中打开一个问题。Nuxt 团队可以帮助您在列出之前应用最佳实践。
nuxt-modules
和 @nuxtjs/
加入
通过将模块迁移到 nuxt-modules,总会有人来提供帮助,这样我们可以联合起来形成一个完美的解决方案。
如果您有一个已发布且正常工作的模块,并希望将其转移到 nuxt-modules
,请 在 nuxt/modules 中打开一个问题。
通过加入 nuxt-modules
,我们可以将您的社区模块重命名为 @nuxtjs/
范围,并为其文档提供子域名(例如 my-module.nuxtjs.org
)。