模块作者指南
Nuxt的配置和钩子系统使得可以定制Nuxt的每个方面,并添加任何可能需要的集成(Vue插件、CMS、服务器路由、组件、日志记录等)。
Nuxt模块是在使用nuxt dev
启动Nuxt开发模式或使用nuxt 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:*
脚本,以方便使用。测试流程
模块入门自带了一个基本的测试套件:
构建流程
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远程仓库
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兼容性添加shim
- 使用从
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 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, 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'
})
}
})
使用 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.ts')
})
}
})
你也可以添加一个动态的服务器路由:
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.ts')
})
}
})
注入其他资源
如果你的模块需要提供其他类型的资源,也可以进行注入。这里有一个简单的示例模块通过 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
映射来钩住它们。
import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
// 通过 `hooks` 映射钩住 `app:error` 钩子
hooks: {
'app:error': (err) => {
console.info(`This error happened: ${err}`);
}
},
setup (options, nuxt) {
// 通过编程方式钩住 `pages:extend` 钩子
nuxt.hook('pages:extend', (pages) => {
console.info(`Discovered ${pages.length} pages`);
})
}
})
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) {
// 该文件将被添加到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: () => `// 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')) {
// 这将重新加载你注册的模板
updateTemplates({ filter: t => t.filename === 'my-module-feature.mjs' })
}
})
测试
测试有助于确保你的模块在各种设置下正常工作。在本节中,我们将介绍如何对你的模块进行各种类型的测试。
单元测试和集成测试
端到端测试
Nuxt Test Utils 是用来帮助你以端到端方式测试你的模块的首选库。下面是使用它的工作流程:
- 创建一个Nuxt应用程序,用作
test/fixtures/*
中的"fixture" - 在测试文件中使用这个fixture来设置Nuxt
- 使用
@nuxt/test-utils
中的工具与fixture进行交互(例如,获取页面) - 对fixture进行相关的检查(例如,"HTML包含...")
- 重复上述步骤
在实践中,fixture如下所示:
// 1. 创建一个Nuxt应用程序,用作"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'
describe('ssr', async () => {
// 2. 在测试文件中使用这个fixture来设置Nuxt
await setup({
rootDir: fileURLToPath(new URL('./fixtures/ssr', import.meta.url)),
})
it('renders the index page', async () => {
// 3. 使用`@nuxt/test-utils`中的工具与fixture进行交互
const html = await $fetch('/')
// 4. 对fixture进行相关的检查
expect(html).toContain('<div>ssr</div>')
})
})
// 5. 重复
describe('csr', async () => { /* ... */ })
使用Playground和外部进行手动QA
在开发模块时,拥有一个用于测试模块的Playground Nuxt应用程序非常有用。模块入门示例集成了一个用于这个目的的Playground应用程序。
你可以在本地使用其他Nuxt应用程序(不是模块仓库的一部分的应用程序)来测试你的模块。为此,你可以使用npm pack
命令或等效的包管理器命令,从你的模块创建一个tarball。然后在测试项目中,你可以将你的模块添加到package.json
的packages
中,如:"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依赖于原生的ES模块。请阅读原生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
)。