Nuxt 3 模块系统

nuxt3
Created 3/7/2022
Updated 3/9/2023

Nuxt 3 模块系统

参考

Nuxt 3 提供了一个模块系统,它其实是一些异步函数,它们会在 nuxt devnuxi build 命令运行时依次被调用。得益于 Nuxt 提供了灵活的配置选项和强大的 hook 钩子函数,通过模块系统可以方便地对 Nuxt 3 的各种核心功能进行修改和扩展,例如在 Nuxt 应用构建的特定时期执行操作 hook into lifecycle、为页面提供模板 provide runtime app templates、覆盖配置参数 update the configuration 等。

以下是一个模块示例

modules/module.mjs
mjs
// 这个简单的模块直接写在 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 中引入该模块

nuxt.config.ts
ts
export default defineNuxtConfig({
  modules: [
    // 在导入模块时,同时(以行内 inline 的方式)传递参数
    ['./modules/example', { token: '123' }]
  ]
})
对比

模块系统与插件系统类似,都是为了扩展 Nuxt 的功能,但是插件系统与项目更加耦合,而模块系统则更独立,可以通过 npm 包的方式来发布,这样方便在不同项目之间使用

Nuxt 官方专门设有一个页面用以收集和展示(包括官方和社区开发的)模块,模块系统让 Nuxt 有更加丰富的社区生态。

使用模块

在配置文件 nuxt.config.ts 的属性 modules 中进行相应的设置

nuxt.config.ts
ts
// 为项目添加各种模块
// 如果所使用的模块是以 npm package 的形式导入的
// 记得先在项目中安装相应的 packages
export default defineNuxtConfig({
  modules: [
    // 以 package 名称的形式导入
    // 这种方式首先需要在项目中安装相应的依赖包
    '@nuxtjs/axios',
    // 使用本地模块,以相对路径导入
    './modules/example',
    // 在导入模块的同时提供配置参数
    ['@nuxtjs/google-analytics', { ua: 'X1234567' }],
    // 直接在此定义一个模块
    async (inlineOptions, nuxt) => { }
  ]
})

模块开发

输入以下命令,使用一个名为 module模板作为起手式,开始构建一个模块

bash
# 将 <module-name> 替换为模块的名称
npx nuxi init -t module <module-name>

在模板的目录结构中,文件夹 📁 /src 就是开发模块的主要工作区,其中在文件 📄 module.ts 中定义了模块的功能。

推荐

在开发 Nuxt Module 时推荐在项目中使用 @nuxt/kit 依赖包,它提供了很多实用和标准的 API 以便加速模块的开发,例如函数 defineNuxtModule() 用于定义模块

该函数对前文所说的异步函数 (inlineOptions, nuxt)(模块其实就是一个异步函数)进行封装,整合了一些实用的功能。当它被调用时会执行一些操作,其中核心是调用它的 setup() 方法。

ts
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 应用的运行时上下文添加额外的功能

module.ts
ts
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 库

ts
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,让开发体验更佳

ts
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,让开发体验更佳

ts
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 来添加

ts
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 生命周期结束时将其关闭,以便释放性能

ts
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
  setup (options, nuxt) {
    // close 钩子,在 Nuxt 应用生命周期结束时执行回调函数
    nuxt.hook('close', async nuxt => {
      // Your custom code here
      // 在这里执行特定操作,如取消监听器等
    })
  }
})

Copyright © 2024 Ben

Theme BlogiNote

Icons from Icônes