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:* 脚本。如何测试
模块启动器带有一个基本的测试套件:
- 一个由 ESLint 提供支持的 linter,使用
npm run lint运行它 - 一个由 Vitest 提供支持的测试运行器,使用
npm run test或npm run test:watch运行它
如何构建
Nuxt 模块自带一个由 @nuxt/module-builder 提供的构建器。此构建器不需要你进行任何配置,支持 TypeScript,并确保你的资产被正确打包以便分发给其他 Nuxt 应用程序。
你可以通过运行 npm run prepack 构建你的模块。
playground 在开发时会处理它,并且发布脚本在发布时也会覆盖它。如何发布
在将你的模块发布到 npm 之前,请确保你拥有一个 npmjs.com 帐户,并且你已使用 npm login 在本地对其进行身份验证。
虽然你可以通过增加模块的版本并使用 npm publish 命令来发布你的模块,但模块启动器带有一个发布脚本,该脚本可以帮助你确保将模块的工作版本发布到 npm 等。
要使用发布脚本,首先,提交所有更改(我们建议你遵循 Conventional Commits 以便利用自动版本 bump 和 changelog 更新),然后使用 npm run release 运行发布脚本。
运行发布脚本时,将发生以下情况:
- 首先,它将运行你的测试套件:
- 运行 linter (
npm run lint) - 运行单元测试、集成测试和端到端测试 (
npm run test) - 构建模块 (
npm run prepack)
- 运行 linter (
- 然后,如果你的测试套件运行良好,它将继续发布你的模块:
- 根据你的 Conventional Commits 增加你的模块版本并生成 changelog
- 将模块发布到 npm(为此,模块将再次构建以确保在发布的工件中考虑到其更新的版本号)
- 将代表新发布版本的 git 标签推送到你的 git 远程仓库
package.json 中的默认 release 脚本以更好地满足你的需求。开发模块
Nuxt 模块带有一系列强大的 API 和模式,允许它们以几乎任何可能的方式更改 Nuxt 应用程序。本节将教你如何利用这些 API 和模式。
模块结构
我们可以考虑两种 Nuxt 模块:
- 已发布的模块在 npm 上分发 - 你可以在 Nuxt 网站 上看到一些社区模块的列表。
- “本地” 模块,它们存在于 Nuxt 项目本身中,可以 内联在 Nuxt 配置 中或作为 modules 目录 的一部分。
无论哪种情况,它们的结构都是相似的。
Module Definition
src/module.ts。模块定义是你模块的入口点。当你的模块在 Nuxt 配置中被引用时,Nuxt 会加载它。
在底层,Nuxt 模块定义是一个简单的、可能是异步的函数,它接受内联用户选项和一个 nuxt 对象来与 Nuxt 交互。
export default function (inlineOptions, nuxt) {
// You can do whatever you like here..
console.log(inlineOptions.token) // `123`
console.log(nuxt.options.dev) // `true` or `false`
nuxt.hook('ready', async nuxt => {
console.log('Nuxt is ready')
})
}
你可以使用 Nuxt Kit 提供的更高级别的 defineNuxtModule 助手函数来获得此函数的类型提示支持。
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule((options, nuxt) => {
nuxt.hook('pages:extend', pages => {
console.log(`Discovered ${pages.length} pages`)
})
})
但是,我们 不建议 使用这种低级别的函数定义。相反,要定义一个模块,我们建议 使用带有 meta 属性的对象语法来标识你的模块,尤其是在发布到 npm 时。
此助手函数通过实现模块所需的许多常见模式,保证未来的兼容性并改善模块作者和用户的体验,从而使编写 Nuxt 模块更加直接。
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
meta: {
// Usually the npm package name of your module
name: '@nuxtjs/example',
// The key in `nuxt.config` that holds your module options
configKey: 'sample',
// Compatibility constraints
compatibility: {
// Semver version of supported nuxt versions
nuxt: '>=3.0.0'
}
},
// Default configuration options for your module, can also be a function returning those
defaults: {},
// Shorthand sugar to register Nuxt hooks
hooks: {},
// The function holding your module logic, it can be asynchronous
setup(moduleOptions, nuxt) {
// ...
}
})
最终,defineNuxtModule 返回一个包装函数,其模块签名为较低级别的 (inlineOptions, nuxt)。此包装函数在调用你的 setup 函数之前应用默认值和其他必要的步骤:
- 支持
defaults和meta.configKey以自动合并模块选项 - 类型提示和自动类型推断
- 为基本的 Nuxt 2 兼容性添加 shims
- 使用从
meta.name或meta.configKey计算出的唯一键确保模块只安装一次 - 自动注册 Nuxt 钩子
- 根据模块元数据自动检查兼容性问题
- 暴露
getOptions和getMeta以供 Nuxt 内部使用 - 只要模块使用最新版本的
@nuxt/kit中的defineNuxtModule,就确保向后和向前兼容 - 与模块构建器工具集成
运行时目录
src/runtime。模块,就像 Nuxt 配置中的所有内容一样,不包含在你的应用程序运行时中。但是,你可能希望你的模块向其安装的应用程序提供或注入运行时代码。这就是运行时目录使你能够做到的。
在运行时目录中,你可以提供任何与 Nuxt 应用程序相关的资产:
- Vue 组件
- Composables
- 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 Test Utils 是一组实用程序,可帮助在你的模块测试中设置和运行 Nuxt 应用程序。
配方
在此处查找用于编写模块的常用模式。
更改 Nuxt 配置
模块可以读取和更改 Nuxt 配置。这是一个启用实验性功能的模块示例。
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// We create the `experimental` object if it doesn't exist yet
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)
// From the runtime directory
addComponent({
name: 'MySuperComponent', // name of the component to be used in vue templates
export: 'MySuperComponent', // (optional) if the component is a named (rather than default) export
filePath: resolver.resolve('runtime/components/MySuperComponent.vue')
})
// From a library
addComponent({
name: 'MyAwesomeComponent', // name of the component to be used in vue templates
export: 'MyAwesomeComponent', // (optional) if the component is a named (rather than default) export
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 注入 Composables
如果你的模块应该提供 composables,你可以使用 addImports 实用程序将它们添加为 Nuxt 可以解析的自动导入。
import { defineNuxtModule, addImports, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const resolver = createResolver(import.meta.url)
addImports({
name: 'useComposable', // name of the composable to be used
as: 'useComposable',
from: resolver.resolve('runtime/composables/useComposable') // path of composable
})
}
})
或者,你可以使用 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', {
// module configuration
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({
// Hook to the `app:error` hook through the `hooks` map
hooks: {
'app:error': (err) => {
console.info(`This error happened: ${err}`);
}
},
setup (options, nuxt) {
// Programmatically hook to the `pages:extend` hook
nuxt.hook('pages:extend', (pages) => {
console.info(`Discovered ${pages.length} pages`);
})
}
})
如果你的模块打开、处理或启动了一个监听器,你应该在 Nuxt 生命周期结束后关闭它。
close 钩子可用于此目的。import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
nuxt.hook('close', async nuxt => {
// Your custom code here
})
}
})
添加模板/虚拟文件
如果需要添加一个可以导入到用户应用程序中的虚拟文件,可以使用 addTemplate 实用程序。
import { defineNuxtModule, addTemplate } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// The file is added to Nuxt's internal virtual file system and can be imported from '#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) {
// The file is added to Nitro's virtual file system and can be imported in the server code from '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: () => `// Generated by 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', async (event, path) => {
if (path.includes('my-module-feature.config')) {
// This will reload the template that you registered
updateTemplates({ filter: t => t.filename === 'my-module-feature.mjs' })
}
})
测试
测试有助于确保你的模块在各种设置下都能按预期工作。在本节中,你将找到如何针对你的模块执行各种类型的测试。
单元测试和集成测试
端到端测试
Nuxt Test Utils 是帮助你以端到端方式测试模块的首选库。以下是使用它的工作流程:
- 在
test/fixtures/*中创建一个用作 “fixture” 的 Nuxt 应用程序 - 在你的测试文件中使用此 fixture 设置 Nuxt
- 使用
@nuxt/test-utils中的实用程序与 fixture 交互(例如,获取页面) - 执行与此 fixture 相关的检查(例如,“HTML 包含 ...”)
- 重复
实际上,fixture 如下所示:
// 1. Create a Nuxt application to be used as a "fixture"
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. Setup Nuxt with this fixture inside your test file
await setup({
rootDir: fileURLToPath(new URL('./fixtures/ssr', import.meta.url)),
})
it('renders the index page', async () => {
// 3. Interact with the fixture using utilities from `@nuxt/test-utils`
const html = await $fetch('/')
// 4. Perform checks related to this fixture
expect(html).toContain('<div>ssr</div>')
})
})
// 5. Repeat
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、composable 或组件提供显式前缀,以避免与其他模块和内部组件冲突。
理想情况下,你应该使用你的模块名称作为前缀(例如,如果你的模块名为 nuxt-foo,则暴露 <FooButton> 和 useFooBar(),而 不是 <Button> 和 useBar())。
友好支持 TypeScript
Nuxt 具有一流的 TypeScript 集成,以提供最佳的开发者体验。
即使不直接使用 TypeScript,暴露类型和使用 TypeScript 开发模块也能使使用者受益。
避免 CommonJS 语法
Nuxt 依赖于原生 ESM。请阅读 原生 ES 模块 以获取更多信息。
编写模块用法文档
考虑在自述文件中记录模块用法:
- 为什么要使用此模块?
- 如何使用此模块?
- 此模块做什么?
- 链接到集成网站和文档始终是一个好主意。
提供 StackBlitz 演示或样板
一个好的做法是使用你的模块和 StackBlitz 创建一个最小的可复现示例,并将其添加到你的模块自述文件中。
这不仅为你的模块的潜在用户提供了一种快速简便的模块实验方式,而且还为他们提供了一种构建最小可复现示例的简便方法,以便在遇到问题时发送给你。
不要使用特定的 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)。