Nuxt 3 模块系统
参考
- Modules
- Modules Directory
- Module Author Guide 💡 所参照的教程原文依然在持续更新中,所以需要留意本文的时效性
Nuxt 3 提供了一个模块系统,它其实是一些异步函数,它们会在 nuxt dev
或 nuxi build
命令运行时依次被调用。得益于 Nuxt 提供了灵活的配置选项和强大的 hook 钩子函数,通过模块系统可以方便地对 Nuxt 3 的各种核心功能进行修改和扩展,例如在 Nuxt 应用构建的特定时期执行操作 hook into lifecycle、为页面提供模板 provide runtime app templates、覆盖配置参数 update the configuration 等。
以下是一个模块示例
// 这个简单的模块直接写在 Nuxt 项目的 📁 `modules` 目录中
// 它导出一个异步函数
// 该函数接受两个参数
// * 第一个参数 inlineOptions 是使用模块时传递进来的配置参数
// * 第二个参数 nuxt 就是构建应用时生成的上下文 Build Context
export default async (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.config.ts
中引入该模块
export default defineNuxtConfig({
modules: [
// 在导入模块时,同时(以行内 inline 的方式)传递参数
['./modules/example', { token: '123' }]
]
})
对比
模块系统与插件系统类似,都是为了扩展 Nuxt 的功能,但是插件系统与项目更加耦合,而模块系统则更独立,可以通过 npm 包的方式来发布,这样方便在不同项目之间使用
Nuxt 官方专门设有一个页面用以收集和展示(包括官方和社区开发的)模块,模块系统让 Nuxt 有更加丰富的社区生态。
使用模块
在配置文件 nuxt.config.ts
的属性 modules
中进行相应的设置
// 为项目添加各种模块
// 如果所使用的模块是以 npm package 的形式导入的
// 记得先在项目中安装相应的 packages
export default defineNuxtConfig({
modules: [
// 以 package 名称的形式导入
// 这种方式首先需要在项目中安装相应的依赖包
'@nuxtjs/axios',
// 使用本地模块,以相对路径导入
'./modules/example',
// 在导入模块的同时提供配置参数
['@nuxtjs/google-analytics', { ua: 'X1234567' }],
// 直接在此定义一个模块
async (inlineOptions, nuxt) => { }
]
})
模块开发
输入以下命令,使用一个名为 module
的模板作为起手式,开始构建一个模块
# 将 <module-name> 替换为模块的名称
npx nuxi init -t module <module-name>
在模板的目录结构中,文件夹 📁 /src
就是开发模块的主要工作区,其中在文件 📄 module.ts
中定义了模块的功能。
推荐
在开发 Nuxt Module 时推荐在项目中使用 @nuxt/kit
依赖包,它提供了很多实用和标准的 API 以便加速模块的开发,例如函数 defineNuxtModule()
用于定义模块
该函数对前文所说的异步函数 (inlineOptions, nuxt)
(模块其实就是一个异步函数)进行封装,整合了一些实用的功能。当它被调用时会执行一些操作,其中核心是调用它的 setup()
方法。
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
// 设置关于该模块的元信息
meta: {
// 名称,一般于是与该模块的 npm package 名称一致
name: '@nuxtjs/example',
// 在配置中的键名
// 即在其他 Nuxt 项目中使用该模块时
// 如果需要在 nuxt.config.ts 文件中设置该模块的参数时
// 需要采用哪一个键名
// ❓ 对于简单的参数配置,可以在引用该模块的同时以 inline options 的方式进行设置
configKey: 'sample',
// 兼容性约束
// 即该模块适用于哪一个 Nuxt 版本
compatibility: {
// 以 Semver version 的方式来表示该模块支持哪一个 Nuxt 版本
// 关于 Semver version 的介绍可以查看 npm 官方文档
// refer to https://docs.npmjs.com/about-semantic-versioning
nuxt: '^3.0.0'
}
},
// 设置该模块配置参数的默认值
defaults: {},
// 一些 hook 函数,在 Nuxt 项目构建的不同时期执行相应的操作
hooks: {},
// 在调用该模块时会运行该模块
async setup(moduleOptions, nuxt) {
// 在此处添加模块的逻辑代码
}
})
可以将模块的开发主要分为两个阶段:
- 开发模块
其实模块本身的核心就是一系列异步函数。
当编写完成源码后,需要执行以下代码,使用将源码编译为一个可以让其他 Nuxt 项目使用的 Nuxt Module说明
在模板的依赖包中有一个名为
@nuxt/module-builder
package 其作用是基于项目源码构建/编译出(可供其他 Nuxt 项目使用的)模块。bash# 编译模块 npm run dev:prepare
编译生成的模块会输出到 📁/dist
目录中注意
因为模块需要使用 module builder 构建后才可用于其他 Nuxt 项目中,所以在开发时,如果模块源码更改了,记得要再执行以上命令,重新构建出模块,这样所调试的模块才是最新的
- 调试模块
需要将模块导入到一个 Nuxt 项目中进行调试。
该模板已经提供了一个「试验场」,即 📁playground
文件夹,它是一个极简的 Nuxt 项目,而且它的配置文件nuxt.config.ts
的属性modules
已经预设好了,会引入本地编译生成模块modules: ['../src/module']
执行以下命令即可启动这个 Nuxt 项目对模块进行调试bash# 启动 playground npm run dev
最佳实践
以下是关于开发模块的最佳实践:
- 异步操作
模块可以执行异步操作,例如 fetch API 或执行异步函数注意
使用命令
nuxi dev
构建 Nuxt 应用时,如果遇到模块在setup()
函数中有异步操作,会等待操作完成后,然后再去调用下一个模块,最后才会启动开发服务器。所以对于耗时的异步操作,可以将逻辑定义在 Nuxt hook 中,避免阻塞开发服务器的启动
- 添加前缀
对于从模块导出的配置、插件、API、组合式函数、组件等,应该添加上相应的前缀,以避免和其他模块或 Nuxt 的核心相冲突。
一般使用模块名称作为这些导出功能的前缀,例如对于名为nuxt-foo
的模块,导出的组件应该命名为<fooButton>
、组合式 API 应该命名为useFooBar()
- 采用 Typescript
使用 Typescript 开发模块,并导出相应的类型 types,可以更方便使用者 - 避免 CommonJS
避免使用 CommonJS 语法开发模块,因为 Nuxt 3 是基于 native ESM,以便让代码适用于浏览器和 Node.js 环境
模块分发
如果希望将开发的模块分发到社区生态中,需要遵循以下指南:
- 为模块提供说明文档 ReadMe 文件,说明为何开发该模块、如何使用它、模块的作用
- 使用
nuxt-
作为模块 npm package 名称的前缀,以便检索发现 - 可以在官方的 Github 仓库
nuxt/modules
新开一个 issue 推荐自己开发的模块,经过 Nuxt 团队审核后,会进入官网的模块列表页面,便于其他开发者发现和使用 - 应该在模块的源码中使用
meta.compatibility
设置它的兼容性约束,而不是在模块命名上说明适用的版本,即应该使用X for Nuxt
(而不是X for Nuxt 3
)
模块示例
提供插件
模块可以为项目提供插件 plugins,插件的作用是为 Nuxt 应用的运行时上下文添加额外的功能
import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit'
// 定义模块
export default defineNuxtModule<ModuleOptions>({
// 在 setup 方法中设置模块的逻辑
setup (options, nuxt) {
// Create resolver to resolve relative paths
// 创建一个路径解析器 resolve
// 接受一个参数作为路径的起点
// import.meta 是一个对象,它暴露了 JavaScript 模块特定上下文的元数据
// 其中 import.meta.url 是模块/脚本的 URL(取决于所在的环境)
const { resolve } = createResolver(import.meta.url)
// resolve 解读传入的相对路径
// 通过 addPlugin() 方法为项目添加插件
addPlugin(resolve('./runtime/plugin'))
}
})
添加 CSS 库
模块可以为项目添加 CSS 库
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// 官方样例直接在构建时上下文 nuxt 添加 CSS 库
// 可以先检查是否已经有该库
// 此外还可以通过模块的配置参数 options 控制是否添加该库
nuxt.options.css.push('font-awesome/css/font-awesome.css')
}
})
提示
应该检查用户是否已经在项目中包含了该 CSS 库,避免重复添加
推荐在模块中提供相应的配置选项,以便一间取消添加
添加组件
模块可以为项目添加组件,而且这些组件支持自动导入 auto-import,让开发体验更佳
import { defineNuxtModule, addComponent } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
// 通过 addComponent() 方法为项目添加组件
addComponent({
// 组件的名称,在项目的模板中使用
name: 'MyComponent',
// 如果组件以具名方式导出(而不是以默认导出),则该属性是可选的
export: 'MyAwesomeComponent',
// 组件的路径,这里采用 package 名称,因为假设组件来源于依赖包
// 如果组件是一个本地文件(一般在 `runtime` 目录中),也可以是解一个析得到的路径
// resolve(runtimeDir, 'components', 'MyComponent.vue')
filePath: '@vue/awesome-components'
})
}
})
添加工具函数
模块也可以为项目添加组合式 composable API,如果使用 addImports
来添加工具函数,它们就会支持自动导入 auto-import,让开发体验更佳
import { defineNuxtModule, addImports, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const resolver = createResolver(import.meta.url)
// 通过 addImports() 方法为项目添加组合式 API
addImports({
name: 'useComposable', // 该组合式 API 的名称
as: 'useComposable', // 别名
// 组合式 API 所在的文件路径
from: resolver.resolve('runtime/composables/useComposable')
})
}
})
如果希望添加一系列的组合式 API,可以将它们放在一个文件夹中,再使用 addImportsDir
来添加
import { defineNuxtModule, addImportsDir, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const resolver = createResolver(import.meta.url)
// 将定义组合式 API 的文件都放在 runtime/composables 目录中
addImportsDir(resolver.resolve('runtime/composables'))
}
})
模块清理
如果模块设置了监听器 watcher,应该在 Nuxt 生命周期结束时将其关闭,以便释放性能
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// close 钩子,在 Nuxt 应用生命周期结束时执行回调函数
nuxt.hook('close', async nuxt => {
// Your custom code here
// 在这里执行特定操作,如取消监听器等
})
}
})