模块作者指南

学习如何创建 Nuxt 模块,以集成、增强或扩展任何 Nuxt 应用程序。

Nuxt 的配置钩子系统使得定制 Nuxt 的每个方面并添加任何集成都成为可能(Vue 插件、CMS、服务器路由、组件、日志记录等)。

Nuxt 模块 是在使用 nuxt dev 启动 Nuxt 开发模式或使用 nuxt build 构建生产项目时,按顺序执行的函数。利用模块,你可以封装、适当测试并作为 npm 包共享自定义解决方案,而无需给项目添加多余的样板代码,也不必修改 Nuxt 本身。

快速开始

我们推荐你使用我们的启动模板来开始创建 Nuxt 模块:

npm create nuxt -- -t module my-module

这将创建一个名为 my-module 的项目,并包含开发和发布模块所需的所有样板代码。

后续步骤:

  1. 在你选择的 IDE 中打开 my-module
  2. 使用你喜欢的包管理器安装依赖
  3. 使用 npm run dev:prepare 准备本地开发文件
  4. 按照本文档学习更多关于 Nuxt 模块的信息

使用启动模板

了解如何使用模块启动模板执行基本任务。

观看 Vue School 关于 Nuxt 模块启动模板的视频教程。

开发指南

虽然你的模块源代码位于 src 目录中,大多数情况下,开发模块时你需要一个 Nuxt 应用。这正是 playground 目录存在的意义。它是一个 Nuxt 应用,你可以在其中自由试验,并已配置为与你的模块一起运行。

你可以像使用任何 Nuxt 应用一样操作 playground。

  • 使用 npm run dev 启动它的开发服务器,修改 src 目录中的模块代码时,服务器会自动重载
  • 使用 npm run dev:build 构建应用
所有其他 nuxt 命令都可以针对 playground 目录使用(例如 nuxt <COMMAND> playground)。你也可以在 package.json 中自定义额外的 dev:* 脚本以方便调用。

测试指南

模块启动模板包含一个基础测试套件:

  • ESLint 提供支持的代码检查,用 npm run lint 运行
  • Vitest 提供支持的测试运行器,使用 npm run testnpm run test:watch 运行
你可以根据需要扩充默认测试策略。

构建指南

Nuxt 模块自带由 @nuxt/module-builder 提供的构建工具。该构建器无需任何配置,支持 TypeScript,并确保你的资源正确打包,以便分发到其他 Nuxt 应用中。

你可以通过运行 npm run prepack 构建模块。

虽然某些情况下构建模块很有用,但大多数时候你无需自己手动构建:开发时 playground 会自动处理,发布时的发布脚本也会完成构建任务。

发布指南

在将模块发布到 npm 之前,请确保你拥有一个 npmjs.com 账户,并在本地使用 npm login 进行了身份验证。

你可以简单地通过修改模块版本并使用 npm publish 命令发布模块,但模块启动模板提供了一个发布脚本,帮助你确保发布的是可用的模块版本,并更便捷地完成发布流程。

使用发布脚本前,请提交所有改动(我们建议遵循Conventional Commits,以便自动版本提升和更新变更日志),然后执行 npm run release

运行发布脚本时会发生以下操作:

  • 首先运行测试套件:
    • 执行代码检查 (npm run lint)
    • 执行单元测试、集成测试和端到端测试 (npm run test)
    • 构建模块 (npm run prepack)
  • 如果测试通过,则进行发布:
    • 根据 Conventional Commits 自动提升版本号并生成变更日志
    • 发布模块到 npm(为此模块将被再次构建,确保更新的版本号在发布产出物中生效)
    • 推送代表新版本的 git 标签到远程仓库
你可以在 package.json 中根据需要自定义默认的 release 脚本。

开发模块

Nuxt 模块拥有丰富且强大的 API 和模式,允许它们以几乎任何方式修改 Nuxt 应用。本节将教你如何利用这些能力。

模块结构

我们可以将 Nuxt 模块分为两类:

  • 发布的模块:分发于 npm 上 — 你可以在 Nuxt 网站 查看一些社区模块列表。
  • "本地" 模块:存在于 Nuxt 项目内部,要么内联于 Nuxt 配置中(内联模块),要么作为 modules 目录 的一部分。

无论哪种形式,其结构类似。

模块定义

使用启动模板时,你的模块定义位于 src/module.ts

模块定义是模块的入口点。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 语义化版本范围
      nuxt: '>=3.0.0'
    }
  },
  // 模块的默认配置选项,也可以是返回这些选项的函数
  defaults: {},
  // 注册 Nuxt 钩子的简写语法
  hooks: {},
  // 包含模块逻辑的函数,可以是异步的
  setup(moduleOptions, nuxt) {
    // ...
  }
})

最终,defineNuxtModule 返回一个封装函数,该函数实现低级 (inlineOptions, nuxt) 模块函数签名。该封装函数在调用你的 setup 函数前,会执行默认值等必要步骤:

  • 支持 defaultsmeta.configKey,自动合并模块选项
  • 类型提示和自动类型推断
  • 添加基本的 Nuxt 2 兼容性补丁
  • 确保模块只安装一次,使用由 meta.namemeta.configKey 计算的唯一键
  • 自动注册 Nuxt 钩子
  • 根据模块元信息自动检测兼容性问题
  • 暴露 getOptionsgetMeta 给 Nuxt 内部使用
  • 只要模块使用来自最新版本 @nuxt/kitdefineNuxtModule,就保证前后向兼容
  • 集成模块构建工具链

运行时目录

使用启动模板时,运行时目录位于 src/runtime

模块本身以及 Nuxt 配置中的其它内容不包含在应用的运行时中。但你可能希望模块为被安装的应用提供或注入运行时代码。运行时目录就是为了此目的。

运行时目录下,可以提供任何与 Nuxt 应用相关的资源,包括:

对于 服务器引擎 Nitro:

  • API 路由
  • 中间件
  • Nitro 插件

或任何你想注入到用户 Nuxt 应用中的其他资源:

  • 样式表
  • 3D 模型
  • 图片
  • 等等

你可以在模块定义中注入这些资源到应用。

了解更多关于资源注入的内容,请参阅使用示例章节
发布的模块无法利用运行时目录内资源的自动导入功能。它们必须显式从 #imports 等路径导入。

原因是出于性能考虑,自动导入不会针对 node_modules 中的文件(发布模块的最终所在位置)启用。

如果你使用模块启动模板,playground 中也不会启用自动导入。

工具

模块开发配备了一套官方工具,帮助你管理开发流程。

@nuxt/module-builder

Nuxt 模块构建工具是一款零配置的构建工具,负责处理构建和发布模块的大部分复杂工作,确保模组产物与 Nuxt 应用的兼容性。

@nuxt/kit

Nuxt Kit 提供了组合式工具,辅助模块与 Nuxt 应用交互。建议你在可能的情况下使用 Nuxt Kit 工具,确保模块兼容性和代码可读性。

Read more in Docs > Guide > Going Further > 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

观看 Vue School 关于修改 Nuxt 配置的视频教程。

向运行时暴露选项

模块不在应用运行时中,因此其选项也不会被包含。但通常你可能需要在运行时代码中访问模块选项。建议通过 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 密钥),因为它们会包含在最终公共包中。
Read more in Docs > Guide > Going Further > Runtime Config.
观看 Vue School 关于传递和暴露 Nuxt 模块选项的视频教程。

使用 addPlugin 注入插件

插件是模块添加运行时代码的常用方式。你可以使用 addPlugin 工具从模块中注册插件。

import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    // 创建路径解析器
    const resolver = createResolver(import.meta.url)

    addPlugin(resolver.resolve('./runtime/plugin'))
  }
})
Read more in Docs > Guide > Going Further > Kit.

使用 addComponent 注入 Vue 组件

如果模块提供 Vue 组件,可以使用 addComponent 工具将它们添加为 Nuxt 的自动导入组件。

import { defineNuxtModule, addComponent, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options, nuxt) {
    const resolver = createResolver(import.meta.url)

    // 从 runtime 目录注入组件
    addComponent({
      name: 'MySuperComponent', // Vue 模板中使用的组件名
      export: 'MySuperComponent', // (可选)若组件为具名导出而非默认导出
      filePath: resolver.resolve('runtime/components/MySuperComponent.vue')
    })

    // 来自一个库
    addComponent({
      name: 'MyAwesomeComponent',
      export: 'MyAwesomeComponent',
      filePath: '@vue/awesome-components'
    })
  }
})

或者使用 addComponentsDir 一次添加整个目录:

import { defineNuxtModule, addComponentsDir, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options, nuxt) {
    const resolver = createResolver(import.meta.url)

    addComponentsDir({
      path: resolver.resolve('runtime/components')
    })
  }
})

使用 addImportsaddImportsDir 注入组合函数 (composables)

如果模块提供组合函数,可以使用 addImports 工具添加自动导入支持:

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')
    })
  }
})

你也可以添加动态路径的服务器路由:

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, 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'))
  }
})

更高级地,可通过 NitropublicAssets 选项暴露一整文件夹的资源:

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 年
      })
    })
  }
})

在模块中使用其他模块

如果你的模块依赖其他模块,可以使用 Nuxt Kit 的 installModule 工具添加它们。例如,若你的模块需要使用 Nuxt Tailwind:

import { defineNuxtModule, createResolver, installModule } from '@nuxt/kit'

export default defineNuxtModule({
  async setup (options, nuxt) {
    const resolver = createResolver(import.meta.url)

    // 注入包含 Tailwind 指令的 CSS
    nuxt.options.css.push(resolver.resolve('./runtime/assets/styles.css'))

    await installModule('@nuxtjs/tailwindcss', {
      // 模块配置
      exposeConfig: true,
      config: {
        darkMode: 'class',
        content: {
          files: [
            resolver.resolve('./runtime/components/**/*.{vue,mjs,ts}'),
            resolver.resolve('./runtime/*.{mjs,js,ts}')
          ]
        }
      }
    })
  }
})

使用钩子

生命周期钩子允许你扩展 Nuxt 的各个方面。模块可通过 hooks 对象声明或编程式调用 nuxt.hook 注册钩子。

import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  // 通过 `hooks` 对象监听 `app:error` 钩子
  hooks: {
    'app:error': (err) => {
      console.info(`发生错误:${err}`);
    }
  },
  setup (options, nuxt) {
    // 编程式监听 `pages:extend` 钩子
    nuxt.hook('pages:extend', (pages) => {
      console.info(`发现了 ${pages.length} 个页面`);
    })
  }
})
Read more in Docs > API > Advanced > Hooks.
观看 Vue School 关于在模块中使用 Nuxt 生命周期钩子的视频。
模块清理

如果你的模块开启了监听器或其他资源,应在 Nuxt 生命周期结束时关闭它们。可以使用 close 钩子实现。
import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    nuxt.hook('close', async nuxt => {
      // 你的清理代码
    })
  }
})

添加模板/虚拟文件

如果需要添加可被用户应用导入的虚拟文件,可使用 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 !"'
    })
  }
})

服务端使用 addServerTemplate

import { defineNuxtModule, addServerTemplate } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    // 文件加入 Nitro 虚拟文件系统,服务器代码中可通过 '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, addTypeTemplate } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    addTypeTemplate({
      filename: 'types/my-module.d.ts',
      getContents: () => `// 由 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 模块的单元和集成测试。

查看此 RFC 并加入讨论

端到端测试

Nuxt 测试工具 是模块端到端测试的首选库。使用流程:

  1. test/fixtures/* 下创建 Nuxt 应用用作“测试用例”
  2. 在测试文件中用该用例初始化 Nuxt
  3. 使用 @nuxt/test-utils 的工具对用例操作(例如请求页面)
  4. 进行断言(例如验证 HTML 内容)
  5. 重复以上步骤

示例用例:

test/fixtures/ssr/nuxt.config.ts
// 1. 创建 Nuxt 应用用作测试用例
import MyModule from '../../../src/module'

export default defineNuxtConfig({
  ssr: true,
  modules: [
    MyModule
  ]
})

测试代码:

test/rendering.ts
import { describe, it, expect } from 'vitest'
import { fileURLToPath } from 'node:url'
import { setup, $fetch } from '@nuxt/test-utils/e2e'

describe('ssr', async () => {
  // 2. 用此测试用例设置 Nuxt
  await setup({
    rootDir: fileURLToPath(new URL('./fixtures/ssr', import.meta.url)),
  })

  it('渲染首页', async () => {
    // 3. 使用 `@nuxt/test-utils` 与用例交互
    const html = await $fetch('/')

    // 4. 执行断言
    expect(html).toContain('<div>ssr</div>')
  })
})

// 5. 可以重复编写其它测试
describe('csr', async () => { /* ... */ })
模块启动模板 中有此流程的示例。

使用 Playground 和外部手动测试

拥有 playground Nuxt 应用,方便你开发时测试模块。模块启动模板集成了 playground

你也可以在其他 Nuxt 应用(非模块仓库内)本地测试模块。使用 npm pack 或包管理器创建模块压缩包,再在测试项目中将模块添加到 package.json,如 "my-module": "file:/path/to/tarball.tgz"

这样,你就可以像普通项目一样引用 my-module

最佳实践

强大的工具也意味着责任。模块虽强大,创建模块时请牢记以下最佳实践,以保证应用性能和良好的开发体验。

异步模块

如前所述,Nuxt 模块可以是异步的,方便调用某些 API 或异步函数。

但是要注意,Nuxt 会等待模块完成 setup 后才继续加载下一个模块和启动开发服务器或构建等。因此应尽量将耗时逻辑延迟到 Nuxt 钩子中。

如果模块 setup 超过 1 秒,Nuxt 会给出警告。

始终给导出接口添加前缀

模块应为所有导出的配置、插件、API、组合函数或组件添加显式前缀,避免与其他模块或内核冲突。

理想情况下,使用你的模块名作为前缀(例如模块名为 nuxt-foo,则导出 <FooButton>useFooBar(),而非 <Button>useBar())。

支持 TypeScript

Nuxt 对 TypeScript 支持一流。暴露类型和使用 TypeScript 编写模块即使用户未直接使用 TypeScript,也能带来更佳体验。

避免 CommonJS 语法

Nuxt 依赖原生 ESM。请查阅原生 ES 模块获取更多信息。

编写模块使用文档

在 README 中文档化模块使用方式:

  • 为什么要用此模块?
  • 如何使用此模块?
  • 模块实现了什么功能?

并附上集成网站和文档链接,效果更佳。

提供 StackBlitz 示例或样板项目

推荐在模块 README 中附上最小复现示例的 StackBlitz 链接。

这不仅给潜在用户快速体验模块的途径,也方便用户遇到问题时提供最小复现给你。

避免针对特定 Nuxt 版本宣传模块

Nuxt、Nuxt Kit 及其它新工具都考虑了向前和向后兼容。

请使用“X for Nuxt”而非“X for Nuxt 3”,以避免生态碎片化,并使用 meta.compatibility 来设置 Nuxt 版本约束。

保持启动模板默认配置

模块启动模板遵循一套默认工具和配置(如 ESLint),若打算开源模块,建议保持默认设置,使模块与其它社区模块保持一致风格,便于贡献者参与。

生态系统

Nuxt 模块生态累计每月超过 1500 万 NPM 下载量,提供丰富功能和各种工具的集成。你也可以加入这个生态!

观看 Vue School 关于 Nuxt 模块类型的视频。

模块类型

官方模块@nuxt/ 命名空间为前缀(如 @nuxt/content),由 Nuxt 团队开发维护。社区贡献亦欢迎!

社区模块@nuxtjs/ 命名空间为前缀(如 @nuxtjs/tailwindcss),由社区成员维护,欢迎各方贡献。

第三方及其它社区模块 通常以 nuxt- 前缀命名,任何人均可创建,使用这种前缀便于 npm 上模块的发现。适合试验和构想新思路。

私有或个人模块 针对个人或公司需求创建,不须遵循特定命名规则,通常位于 npm 组织命名空间下(如 @my-company/nuxt-auth)。

在社区模块列表中注册你的模块

欢迎将社区模块添加到模块列表。提交模块注册请求,请在 Nuxt 官方仓库 nuxt/modules 中创建 issue。Nuxt 团队可帮助你应用最佳实践后再加入列表。

加入 nuxt-modules@nuxtjs/

将模块移至 nuxt-modules 组织,意味着有更多人协助维护,共同打造完美解决方案。

如果你已有公开模块并想移交给 nuxt-modules,请在 Nuxt 模块仓库开 issue。

加入 nuxt-modules 后,我们可将你的社区模块重命名为 @nuxtjs/ 命名空间,并为其提供子域(如 my-module.nuxtjs.org)方便托管文档。