Skip to content

模块开发

本文聚焦于开发可发布到 npm 的 Nuxt 模块

如果你只需要项目内使用,请参阅《本地模块》。两者核心 API 相同,但发布模块需要额外的构建配置、测试和发布流程。

什么时候需要开发 npm 模块?

  • 你的功能需要跨项目复用
  • 你想分享给社区使用
  • 你需要版本化管理和独立迭代

如果只是单个项目内复用,本地模块就够了,不需要走完整的发布流程。

模块结构

text
my-module/
├── src/
│   ├── module.ts          # 模块定义(核心)
│   └── runtime/
│       ├── plugin.ts       # 运行时插件
│       ├── composables/    # 运行时组合式函数
│       └── components/     # 运行时组件
├── playground/             # 测试项目
│   ├── app/
│   │   └── pages/
│   └── nuxt.config.ts
├── package.json
├── tsconfig.json
└── build.config.ts         # 构建配置

为什么有 src/runtime/ 的区分?

src/module.ts 是构建时代码,在 Nuxt 构建过程中执行;runtime/ 是运行时代码,在浏览器/服务器中执行。构建产物只会包含 module.ts 的编译结果和对 runtime/ 文件的引用。

defineNuxtModule

ts
// src/module.ts
import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit'

// 定义模块选项的类型
export interface ModuleOptions {
  enabled: boolean
  prefix: string
}

export default defineNuxtModule<ModuleOptions>({
  meta: {
    name: 'my-module',
    configKey: 'myModule',
    // 声明兼容的 Nuxt 版本,版本不匹配时给出警告
    compatibility: {
      nuxt: '^3.0.0 || ^4.0.0',
    },
  },

  // 默认配置——会被用户配置覆盖
  defaults: {
    enabled: true,
    prefix: 'My',
  },

  // 模块设置(构建时执行)
  setup(options, nuxt) {
    // options:合并后的最终配置(defaults + 用户配置)
    // nuxt:Nuxt 实例,可以修改配置、注册钩子

    const { resolve } = createResolver(import.meta.url)

    // 1. 添加插件
    addPlugin(resolve('./runtime/plugin'))

    // 2. 添加自动导入目录
    nuxt.hook('imports:dirs', (dirs) => {
      dirs.push(resolve('./runtime/composables'))
    })

    // 3. 添加组件(带前缀)
    nuxt.hook('components:dirs', (dirs) => {
      dirs.push({
        path: resolve('./runtime/components'),
        prefix: options.prefix,  // 组件名前缀,如 MyButton
      })
    })

    // 4. 添加服务端工具
    nuxt.hook('nitro:config', (nitroConfig) => {
      nitroConfig.autoImports?.push({
        from: resolve('./runtime/server/utils'),
        imports: ['myServerUtil'],
      })
    })

    // 5. 添加类型声明
    nuxt.hook('prepare:types', ({ references }) => {
      references.push({
        path: resolve('./runtime/types/index.d.ts'),
      })
    })
  },
})

options 合并机制

ts
// 模块定义的 defaults
defaults: { enabled: true, prefix: 'My' }

// 用户在 nuxt.config.ts 中的配置
myModule: { prefix: 'Custom' }

// 最终 options
{ enabled: true, prefix: 'Custom' }  // defaults 被用户配置覆盖

常用 Kit 工具

函数说明典型用法
defineNuxtModule定义模块每个模块必须使用
addPlugin添加插件addPlugin(resolve('./runtime/plugin'))
addLayout添加布局addLayout({ src: resolve('./layout.vue'), filename: 'custom.vue' })
addComponent添加单个组件addComponent({ name: 'MyButton', filePath: resolve('./Button.vue') })
addComposable添加组合式函数addComposable(resolve('./runtime/useFeature'))
addServerPlugin添加服务端插件addServerPlugin(resolve('./runtime/server/plugin'))
addServerHandler添加服务端路由addServerHandler({ route: '/api/health', handler: resolve('./runtime/server/health') })
createResolver创建路径解析器const { resolve } = createResolver(import.meta.url)
addTemplate添加模板文件生成虚拟模块
addTypeTemplate添加类型模板生成 .d.ts 类型文件

TIP

addPlugin/addComponentnuxt.hook 的快捷方式 功能等价但更简洁。例如 addPlugin(resolve('./runtime/plugin')) 等同于 nuxt.hook('plugins:dirs', dirs => dirs.push(...))

构建配置

为什么需要构建?

模块源码是 TypeScript,npm 包需要编译为 JavaScript。@nuxt/module-builder 封装了 unbuild,自动处理 Nuxt 模块的构建需求。

bash
npm install -D @nuxt/module-builder
ts
// build.config.ts
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  entries: [
    'src/module',
  ],
  externals: ['vue', '@nuxt/kit', 'nuxt'],
})
json
// package.json scripts
{
  "scripts": {
    "dev": "nuxi dev playground",
    "build": "nuxt-module-build",
    "prepack": "npm run build",
    "release": "npm run build && npm publish"
  }
}

package.json

json
{
  "name": "@myorg/nuxt-module",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/module.mjs",
  "types": "./dist/types.d.ts",
  "exports": {
    ".": {
      "import": "./dist/module.mjs",
      "types": "./dist/types.d.ts"
    }
  },
  "files": ["dist"],
  "nuxt": {
    "configKey": "myModule"
  },
  "devDependencies": {
    "@nuxt/kit": "^3.0.0",
    "@nuxt/module-builder": "^0.8.0",
    "nuxt": "^3.0.0"
  },
  "peerDependencies": {
    "nuxt": "^3.0.0 || ^4.0.0"
  }
}

关键字段说明:

字段说明
main / exports指向构建产物,Nuxt 通过此入口加载模块
typesTypeScript 类型文件,IDE 自动补全依赖它
files只发布 dist/ 目录,避免发布源码和测试文件
nuxt.configKey告诉 Nuxt 用户的配置键名,用于自动合并
peerDependencies声明兼容的 Nuxt 版本,避免安装不兼容的版本

命名规范

npm 模块推荐用 @org/nuxt-xxxnuxt-xxx 格式,方便在 Nuxt 模块生态中被发现。

测试模块

Playground 开发测试

playground/ 目录中创建测试项目:

ts
// playground/nuxt.config.ts
export default defineNuxtConfig({
  modules: ['../../src/module'],
  myModule: {
    enabled: true,
  },
})
bash
# 在模块根目录运行,自动使用 playground 项目
npm run dev

单元测试

bash
npm install -D @nuxt/test-utils vitest
ts
// tests/module.spec.ts
import { describe, it, expect } from 'vitest'
import { setupTest } from '@nuxt/test-utils'

describe('my-module', () => {
  const ctx = setupTest({
    rootDir: './playground',
  })

  it('registers plugin', async () => {
    const html = await ctx.$fetch('/')
    expect(html).toContain('my-module-plugin')
  })
})

发布流程

bash
# 1. 确保构建成功
npm run build

# 2. 本地测试(在 playground 中验证)
npm run dev

# 3. 版本号更新(遵循 semver)
npm version patch  # 1.0.0 → 1.0.1(修复 Bug)
npm version minor  # 1.0.0 → 1.1.0(新增功能,向后兼容)
npm version major  # 1.0.0 → 2.0.0(破坏性变更)

# 4. 发布
npm publish

# 首次发布 scoped 包需要加 --access public
npm publish --access public

INFO

发布前检查清单:

  • compatibility 中声明了 Nuxt 版本范围
  • package.jsonfiles 只包含 dist
  • TypeScript 类型完整导出
  • README 中有安装和使用说明
  • 在不同 Nuxt 版本中测试过兼容性

最佳实践

  1. 始终声明 compatibility——避免用户安装不兼容的版本
  2. 为配置提供完整 TypeScript 类型——ModuleOptions 接口 + 泛型
  3. 使用 playground/ 开发时测试——比发布后调试效率高得多
  4. 运行时代码放在 runtime/——构建时代码不应出现在客户端包中
  5. 模块应该是声明式的——setup 中避免耗时操作和副作用
  6. 遵循命名约定——@org/nuxt-xxxnuxt-xxx
  7. 提供 configKey——让用户可以在 nuxt.config.ts 中直接配置

常见问题

问题原因解决方案
发布后安装报错找不到模块exports 配置不正确确保 mainexports 指向构建产物
TypeScript 类型不生效未发布类型文件检查 types 字段和 files 是否包含类型文件
构建后 createResolver 路径错误构建后 import.meta.url 指向 dist/确保构建配置正确处理路径映射
playground 无法加载模块modules 路径不正确使用相对路径 '../../src/module'
CI 环境报错但本地正常路径大小写敏感问题(Linux 区分大小写)确保所有 import 路径大小写一致

基于 Nuxt 4 官方文档整理编写