模块作者指南
Nuxt 的 配置 和 钩子 系统使得定制 Nuxt 的每一个方面并添加您可能需要的任何集成成为可能(Vue 插件、CMS、服务器路由、组件、日志记录等)。
Nuxt 模块 是在开发模式下使用 nuxi dev
启动 Nuxt 或使用 nuxi build
构建生产项目时顺序运行的函数。
有了模块,您可以封装、适当测试并作为 npm 包共享自定义解决方案,而无需向您的项目添加不必要的样板文件,也不需要对 Nuxt 本身进行更改。
快速开始
我们建议您使用我们的 起始模板 开始使用 Nuxt 模块:
npx nuxi init -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
来构建您的模块。
如何发布
npm login
在本地进行了身份验证。虽然您可以通过增加其版本并使用 npm publish
命令来发布您的模块,但模块起始模板带有一个发布脚本,可以帮助您确保您发布了一个可以工作的模块版本到 npm 和更多。
要使用发布脚本,首先,提交您所有的更改(我们建议您遵循 Conventional Commits 以利用自动版本增加和更改日志更新),然后使用 npm run release
运行发布脚本。
运行发布脚本时,以下将发生:
- 首先,它将通过以下方式运行您的测试套件:
- 运行 linter (
npm run lint
) - 运行单元测试、集成测试和端到端测试 (
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
对象以与 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 钩子
- 根据模块元数据自动检查兼容性问题
- 为 Nuxt 的内部使用公开
getOptions
和getMeta
- 只要模块使用最新版本的
@nuxt/kit
中的defineNuxtModule
,就确保向后和向前兼容性 - 与模块构建工具集成
运行时目录
src/runtime
中找到。模块和 Nuxt 配置中的所有内容一样,不包括在您的应用程序运行时中。然而,您可能希望您的模块提供或注入运行时代码到它安装的应用程序中。这就是运行时目录使您能够做到的。
在运行时目录中,您可以提供任何与 Nuxt 应用程序相关的资产:
- Vue 组件
- 可组合项
- Nuxt 插件
对于 服务器引擎,Nitro:
- API 路由
- 中间件
- Nitro 插件
或任何其他您想注入到用户 Nuxt 应用程序中的资产:
- 样式表
- 3D 模型
- 图像
- 等等。
然后您将能够从您的 模块定义 中注入所有这些资产到应用程序中。
#imports
或类似地方显式导入。
事实上,出于性能原因,
node_modules
(发布模块最终所在的位置)中的文件不启用自动导入。
如果您使用模块起始模板,playground 中也不会启用自动导入。
工具
模块带有一组第一方工具,以帮助您进行开发。
@nuxt/module-builder
Nuxt 模块构建器 是一个零配置构建工具,负责构建和发布您的模块的所有繁重工作。它确保您的模块构建工件与 Nuxt 应用程序兼容。
@nuxt/kit
Nuxt Kit 提供了可组合的实用工具,以帮助您的模块与 Nuxt 应用程序交互。尽可能使用 Nuxt Kit 实用工具而不是手动替代方案,以确保更好的兼容性和模块代码的可读性。
@nuxt/test-utils
Nuxt 测试实用工具 是一组实用工具的集合,可帮助您在模块测试中设置和运行 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)
// 我们可以注入包含 Tailwind 指令的 CSS 文件
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}')
]
}
}
})
}
})
使用钩子
生命周期钩子 允许您扩展 Nuxt 的几乎所有方面。模块可以通过它们的 hooks
映射以编程方式或通过它们定义中的 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 !"'
})
}
})
添加类型声明
您可能还想向用户的项目中添加类型声明(例如,增强 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/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'
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 3 拥有一流的 TypeScript 集成,以提供最佳的开发体验。
公开类型并使用 TypeScript 开发模块,即使用户直接不使用 TypeScript,也有利于用户。
避免 CommonJS 语法
Nuxt 3 依赖于原生 ESM。请阅读 原生 ES 模块 以获取更多信息。
文档化模块使用
考虑在自述文件中记录模块使用情况:
- 为什么要使用这个模块?
- 如何使用这个模块?
- 这个模块做了什么?
链接到集成网站和文档总是一个好主意。
提供 StackBlitz 演示或样板
使用您的模块创建一个最小的再现,并添加到您的模块自述文件中,这是一个好习惯 StackBlitz。
这不仅为用户提供了一种快速简便的方法来尝试模块,而且当他们遇到问题时,也为他们提供了一种简单的方法来构建最小的再现,可以发送给您。
不要为特定 Nuxt 版本做广告
Nuxt 3、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
)。