Nuxt 的 configuration 和 hooks 系统使自定义 Nuxt 的各个方面并添加您可能需要的任何集成(Vue 插件)成为可能 、CMS、服务器路由、组件、日志记录等)。
Nuxt 模块 是在使用 nuxi dev
以开发模式启动 Nuxt 或使用 nuxi build
构建生产项目时顺序运行的功能。
使用模块,您可以将自定义解决方案封装、正确测试和共享为 npm 包,而无需向您的项目添加不必要的样板文件,也不需要更改 Nuxt 本身。
快速开始
我们建议您使用我们的 入门模板 开始使用 Nuxt 模块:
npx nuxi init -t module my-module
这将创建一个“我的模块”项目,其中包含开发和发布模块所需的所有样板。
下一步:
- 在您选择的 IDE 中打开
my-module
2.使用你最喜欢的包管理器安装依赖 - 使用
npm run dev:prepare
准备用于开发的本地文件 4.按照本文档了解有关Nuxt Modules的更多信息
使用启动器
了解如何使用模块启动器执行基本任务。
如何开发
虽然您的模块源代码位于 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 之前,请确保您拥有一个 npmjs.com 帐户,并且您已通过 npm login
在本地进行身份验证。
虽然您可以通过提升模块版本并使用npm publish
命令来发布模块,但模块启动器附带了一个发布脚本,可帮助您确保将模块的工作版本发布到 npm 等。
要使用发布脚本,首先,提交所有更改(我们建议您遵循 Conventional Commits 以利用自动版本升级和变更日志更新) ,然后使用 npm run release
运行发布脚本。
运行发布脚本时,将发生以下情况:
- 首先,它将通过以下方式运行您的测试套件:
- 运行 linter (
npm run lint
) - 运行单元、集成和 e2e 测试(
npm run test
) - 构建模块(
npm run prepack
)
- 运行 linter (
- 然后,如果您的测试套件运行良好,它将通过以下方式继续发布您的模块:
- 根据您的常规提交修改您的模块版本并生成变更日志
- 将模块发布到 npm(为此目的,将再次构建模块以确保在发布的工件中考虑其更新的版本号)
- 将代表新发布版本的 git 标签推送到您的 git 远程源
与其他脚本一样,您可以随意微调 package.json
中的默认 release
脚本以更好地满足您的需要。
开发模块
Nuxt 模块带有各种强大的 API 和模式,允许它们以几乎任何可能的方式改变 Nuxt 应用程序。 本节将教您如何利用这些。
模块剖析
我们可以考虑两种 Nuxt 模块:
- 已发布的模块分布在 npm 上 - 您可以在 Nuxt 网站 上看到一些社区模块的列表。
- “本地”模块,它们存在于 Nuxt 项目本身中,或者 内联在 Nuxt 配置中 或作为
modules
目录。
在任何一种情况下,它们的解剖结构都是相似的。
模块定义
使用 starter 时,您的模块定义位于 src/module.ts
中。
模块定义是模块的入口点。 当您的模块在 Nuxt 配置中被引用时,它会被 Nuxt 加载。
在低层次上,Nuxt 模块定义是一个简单的、可能是异步的、接受内联用户选项的函数和一个与 Nuxt 交互的 nuxt
对象。
export default function (inlineOptions, nuxt) {
// 你可以在这里做任何你想做的事..
console.log(inlineOptions.token) // `123`
console.log(nuxt.options.dev) // `true` or `false`
nuxt.hook('ready', async (nuxt) => {
console.log('Nuxt is ready')
})
}
在 nuxt.config.ts
中定义的短内联模块之外,我们不建议使用此低级函数定义。
相反,要定义一个模块,我们建议使用 Nuxt Kit 提供的更高级别的 defineNuxtModule
帮助程序。
这个助手通过实现模块中的许多常见模式,保证未来的兼容性,并改善模块作者开发人员和模块用户之一的体验,使编写 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 的速记糖
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 应用程序相关的任何类型的资产:
- 视图组件
- 可组合物
- Nuxt 插件
对于服务器引擎,Nitro:
- API 路线
- 中间件
- 硝基插件
或者你想在用户的 Nuxt 应用程序中注入任何其他类型的资产:
- 样式表
- 3D模型
- 图片
- ETC。
然后,您将能够从 模块定义 中将所有这些资产注入到应用程序中。
在 范例 中了解有关资产注入的更多信息。
已发布的模块不能为其运行时目录中的资产利用自动导入。 相反,他们必须从 #imports
或类似的地方显式导入它们。
实际上,出于性能原因,没有为 node_modules
(已发布模块最终将存在的位置)中的文件启用自动导入。
这就是为什么模块启动器 故意禁用它们 在开发模块时。
如果您使用的是模块启动器,则您的 playground 也不会启用自动导入。
工具
模块带有一组第一方工具来帮助您进行开发。
@nuxt/module-builder
Nuxt Module Builder 是一个零配置构建工具,负责构建和发布模块的所有繁重工作。 它确保您的模块构建工件与 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
注意不要在公共运行时配置中暴露任何敏感的模块配置,例如私有 API 密钥,因为它们最终会出现在公共包中。
使用 addPlugin
注入插件
插件是模块添加运行时逻辑的常用方式。 您可以使用 addPlugin 实用程序从您的模块中注册它们。
import { addPlugin, createResolver, defineNuxtModule } 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 { addComponent, defineNuxtModule } 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'
})
}
})
使用 addImports
和 addImportsDir
注入可组合项
如果您的模块应该提供可组合项,您可以使用 addImports
实用程序将它们添加为自动导入以供 Nuxt 解析。
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'))
}
})
注入其他资产
如果你的模块应该提供其他类型的资产,它们也可以被注入。 这是一个简单的示例模块,通过 Nuxt 的 css
数组注入样式表。
import { createResolver, defineNuxtModule } 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 { createResolver, defineNuxtModule } 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年
})
})
}
})
使用钩子
Lifecycle hooks 让你可以扩展 Nuxt 的几乎每一个方面。 模块可以通过编程方式或通过其定义中的“hooks”映射来挂钩它们。
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
// 通过 `hooks` 映射挂钩到 `app:error` 挂钩
hooks: {
'app:error': (err) => {
console.info(`This error happened: ${err}`)
}
},
setup(options, nuxt) {
// 以编程方式挂钩到 `page:extend` 挂钩
nuxt.hook('page:extend', (pages) => {
console.info(`Discovered ${pages.length} pages`)
})
}
})
Module cleanup
如果您的模块打开、处理或启动观察者,则应在 Nuxt 生命周期完成后将其关闭。 close
钩子可用于此。
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
nuxt.hook('close', async (nuxt) => {
// Your custom code here
})
}
})
Augmenting Types
If your module should augment types handled by Nuxt, you can use the prepare:types
hook to perform this operation.
import { addTemplate, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)
// Generating types to be injected
addTemplate({
filename: 'my-module.d.ts',
getContents: () => {
return `// Generated by my-module
interface MyModuleNitroRules {
myModule?: { foo: 'bar' }
}
declare module 'nitropack' {
interface NitroRouteRules extends MyModuleNitroRules {}
interface NitroRouteConfig extends MyModuleNitroRules {}
}
export {}`
},
})
// Injecting previously generated types
nuxt.hooks.hook('prepare:types', ({ references }) => {
references.push({ path: resolve(nuxt.options.buildDir, 'my-module.d.ts') })
})
}
})
测试
测试有助于确保您的模块在各种设置下按预期工作。 在本节中找到如何对您的模块执行各种测试。
单元和集成
我们仍在讨论和探索如何简化 Nuxt 模块上的单元和集成测试。
查看此 RFC 以加入对话。
端到端
Nuxt Test Utils 是帮助您以端到端方式测试模块的首选库。 这是采用它的工作流程:
- 在
test/fixtures/*
中创建一个 Nuxt 应用程序用作fixture
- 在你的测试文件中使用这个
fixture
设置Nuxt - 使用来自
@nuxt/test-utils
的实用程序与夹具交互(例如获取页面) - 执行与此固定装置相关的检查(例如“HTML 包含...”)
- 重复
用fixture
实践:
// 1. 创建一个 Nuxt 应用程序用作“fixture”
import MyModule from '../../../src/module'
export default defineNuxtConfig({
ssr: true,
modules: [
MyModule
]
})
及其测试:
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { $fetch, setup } from '@nuxt/test-utils'
describe('ssr', async () => {
// 2. 在测试文件中使用这个夹具设置 Nuxt
await setup({
rootDir: fileURLToPath(new URL('./fixtures/ssr', import.meta.url)),
})
it('renders the index page', 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
命令或等效的包管理器从您的模块创建压缩包。 然后在你的测试项目中,你可以将你的模块添加到 package.json
包中:"my-module": "file:/path/to/tarball.tgz"
。
之后,您应该能够像在任何常规项目中一样引用my-module
。
最佳实践
拥有权利的同时也被赋予了重大的责任。 虽然模块功能强大,但在编写模块以保持应用程序性能和开发人员体验良好时,请牢记以下最佳实践。
异步模块
正如我们所见,Nuxt 模块可以是异步的。 例如,您可能想要开发一个需要获取某些 API 或调用异步函数的模块。
但是,请注意异步行为,因为 Nuxt 将等待您的模块设置,然后再转到下一个模块并启动开发服务器、构建过程等。更喜欢将耗时的逻辑推迟到 Nuxt 挂钩。
如果你的模块需要超过 1 秒 来设置,Nuxt 将发出警告。
始终为暴露的接口添加前缀
Nuxt 模块应该为任何公开的配置、插件、API、可组合项或组件提供明确的前缀,以避免与其他模块和内部组件发生冲突。
理想情况下,您应该在它们前面加上您的模块名称(例如,如果您的模块名为 nuxt-foo
,则公开 <FooButton>
和 useFooBar()
以及不 <Button>
和 useBar( )
).
对 TypeScript 友好
Nuxt 3 具有一流的 TypeScript 集成,可提供最佳的开发人员体验。
即使不直接使用 TypeScript,公开类型和使用 TypeScript 开发模块也会使用户受益。
避免 CommonJS 语法
Nuxt 3,依赖原生 ESM。 请阅读 Native ES Modules 了解更多信息。
文档模块使用
考虑在自述文件中记录模块用法:
- 为什么使用这个模块?
- 如何使用这个模块?
- 这个模块是做什么的?
链接到集成网站和文档总是一个好主意。
提供 StackBlitz 演示或样板
使用您的模块和添加到模块自述文件的 StackBlitz 进行最小复制是一个很好的做法。
这不仅为您的模块的潜在用户提供了一种快速简便的方法来试验该模块,而且还为他们提供了一种简单的方法来构建最小的复制品,当他们遇到问题时可以发送给您。
不要用特定的 Nuxt 版本做广告
Nuxt 3、Nuxt Kit 和其他新工具都考虑到了向前和向后兼容性。
请使用“X for Nuxt”而不是“X for Nuxt 3”以避免生态系统中的碎片化,并且更喜欢使用 meta.compatibility
来设置 Nuxt 版本限制。
坚持初始默认值
模块启动器带有一组默认的工具和配置(例如 ESLint 配置)。 如果您打算开源您的模块,坚持使用这些默认设置可确保您的模块与其他社区模块 共享一致的编码风格,从而使其他人更容易做出贡献。
生态系统
Nuxt Module 生态系统 代表每月超过 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
)。