Vue 3 工具库与业务组件库有一个明显区别:它通常以函数、组合式 API 或轻量指令的形式提供能力,很少直接导出大型视图组件。这决定了测试策略需要更关注逻辑正确性、生命周期边界和模块产物兼容性,而不是依赖复杂的浏览器渲染快照。自动化的意义也在这里被放大,因为工具函数一旦被多个项目依赖,任何一次未经测试的发布都可能放大回归风险。要让工具库的迭代稳定且可追溯,最合理的做法是把测试、构建、版本管理、发布全部纳入 CI/CD 流水线。

用 Vitest 建立适合 Vue 3 工具库的测试基线
Vitest 与 Vite 共享同一套配置体系,对于 Vue 3 工具库来说几乎没有额外学习成本。它原生支持 ESM、TypeScript 和 jsdom 环境,也能通过插件扩展 Vue 组件挂载能力。工具库如果涉及 DOM 操作、响应式副作用或生命周期钩子,需要把测试环境设置为 jsdom;如果只是纯函数转换、状态计算,则可以使用 node 环境来获得更快的执行速度。为了保证测试结果可量化,建议在配置中同时启用覆盖率阈值,避免测试文件流于形式。
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'jsdom',
globals: true,
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
thresholds: {
lines: 85,
functions: 90,
statements: 85,
branches: 80
}
}
}
})
设置 globals: true 后,测试文件里可以直接使用 describe、it、expect 而不需要逐个导入,减少样板代码。覆盖率阈值则强制核心逻辑至少被覆盖到设定比例,一旦低于阈值,测试命令会以非零状态退出,便于在 CI 中直接拦截。对于 Vue 3 工具库,建议把 @vue/test-utils 作为开发依赖,用来测试那些依赖组件生命周期或模板上下文的组合式 API。
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { defineComponent, nextTick } from 'vue'
import { useDebounce } from '../src/useDebounce'
describe('useDebounce', () => {
it('should debounce callback execution', async () => {
const callback = vi.fn()
const wrapper = mount(defineComponent({
setup() {
const { run } = useDebounce(callback, 100)
return { run }
},
template: '<div />'
}))
wrapper.vm.run()
wrapper.vm.run()
expect(callback).not.toHaveBeenCalled()
await new Promise(resolve => setTimeout(resolve, 150))
expect(callback).toHaveBeenCalledTimes(1)
wrapper.unmount()
})
})
这个测试演示了工具库中常见的组合式 API 场景。通过挂载一个极简组件来触发 useDebounce 的内部逻辑,再验证回调是否按预期延迟执行。需要注意,工具库的测试不应过度依赖组件渲染细节,而是把组件当作驱动逻辑的宿主。测试完成后,执行 pnpm test 就能得到覆盖率报告和失败信息,这是后续 CI 流水线中最先运行的一道关卡。
在 GitHub Actions 中实现多版本自动测试
本地测试稳定之后,下一步是让每次推送都自动运行同一套检查。GitHub Actions 的矩阵策略非常适合工具库,因为工具库通常需要兼容多个 Node.js 版本。通过矩阵可以并行运行同一套测试,快速发现不同运行时下的行为差异。比如 Node.js 18、20、22 都跑一遍,能够提前暴露对全局对象、模块解析或定时器实现的依赖问题。
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm test
- run: pnpm build
- name: Upload coverage report
if: matrix.node-version == '20'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
这份工作流在每次推送到 main 分支以及每个 pull request 时触发,安装依赖时使用 --frozen-lockfile 保证依赖树与锁文件完全一致。缓存通过 cache: 'pnpm' 自动处理,不需要手动配置路径。构建步骤放在测试之后,确保只有逻辑通过后才会检查产物生成是否正常。覆盖率报告只在 Node.js 20 的矩阵任务中上传,避免重复上传同一份报告。
如果工具库需要针对 Vue 3 的不同小版本做回归,可以在矩阵中增加一个 vue-version 维度,然后在安装依赖后通过 npm 的别名机制临时切换 Vue 版本。例如用 pnpm add -D vue@3.4 vue@3.5 分别运行测试。这样能在不维护多个仓库的前提下,覆盖更多下游使用场景。矩阵任务越全面,正式发布前的信心就越高。
引入 changesets 管理语义化版本与变更日志
多条测试通过并不意味着版本号和变更日志会自动更新。很多工具库维护者会手动改版本、写 changelog,一旦遗忘就可能导致发布内容与版本语义不符。changesets 把版本决策从“发布时临时决定”前移到“提交变更时描述”,每个功能或修复都对应一个变更说明文件。合并到主干后,changesets 根据这些说明自动计算下一个版本号,并生成结构化的 changelog。
pnpm add -Dw @changesets/cli pnpm changeset init
初始化后,项目根目录会出现 .changeset 文件夹。开发者在提交一个修复 PR 时,可以执行 pnpm changeset,根据提示选择变更类型和描述。生成的文件大致如下:
--- "@scope/vue3-utils": patch --- 修复 useDebounce 在组件卸载后仍然触发回调的问题。
patch 表示这是一个向后兼容的 bug 修复,minor 表示新增了向后兼容的功能,major 则提示存在破坏性变更。当所有变更文件随 PR 合并到 main 分支后,changesets 会汇总这些变更并创建一个版本提升的 PR。这个 PR 被合并时,CI 流水线会自动执行发布,并推送新的 git tag。整个过程不需要维护者手动修改 package.json 里的 version 字段。
name: Release
on:
push:
branches:
- main
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Create Release Pull Request or Publish
uses: changesets/action@v1
with:
publish: pnpm run release
commit: 'chore: bump versions'
title: 'chore: publish new versions'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
这段工作流的核心是 changesets/action。当 main 分支存在未处理的变更文件时,它会自动创建版本提升 PR;当该 PR 合并且没有新的变更文件后,它会执行 publish 命令。需要留意 permissions 字段必须同时包含 contents: write 和 pull-requests: write,否则无法推送版本提交或创建 PR。
发布前构建校验与 npm 自动化发布细节
有了自动化测试和版本管理,发布链路的最后一环是确保构建产物可以被下游正确消费。Vite 的库模式可以把工具库打包成 ESM 和 CommonJS 两种格式,同时通过 vite-plugin-dts 生成类型声明文件。构建配置中需要把 Vue 设为 external,避免把整个 Vue 运行时打包进产物,导致重复引入和版本冲突。
import { defineConfig } from 'vite'
import dts from 'vite-plugin-dts'
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`
},
rollupOptions: {
external: ['vue']
}
},
plugins: [dts({ rollupTypes: true })]
})
产物生成后,还需要检查 package.json 中的 exports、types 和 files 字段是否配置正确。一个常见问题是类型声明路径与 JS 入口不一致,导致使用方在 TypeScript 中报错。可以使用 publint 自动校验包的发布结构,它会扫描 exports 映射、文件存在性以及常见配置错误。同时,arethetypeswrong 可以从多个模块解析模式验证类型指向是否准确。
pnpm add -Dw publint pnpm publint
npm 发布时还需要准备认证信息。建议在 npm 账号中生成一个只读发布权限的 token,添加到 GitHub 仓库的 Secrets 中,命名为 NPM_TOKEN。在发布动作执行前,通过 .npmrc 文件注入 token,保证 token 不会写入仓库历史。
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
package.json 中还要设置 "publishConfig": { "access": "public" },并明确 files 字段只包含 dist 和类型文件,防止把测试文件或源码快照发布到 npm。完整的发布脚本可以定义为 "release": "pnpm build && pnpm publint && changeset publish"。这样每次真正推送包之前,都会重新构建并校验产物结构。
经过以上配置,一个 Vue 3 工具库从代码提交到 npm 发布就形成了闭环:推送触发多版本测试,测试通过后执行构建与类型检查,所有变更文件汇总后自动生成版本提升 PR,最终合并发布并附带完整 changelog。维护者只需要专注实现功能和撰写变更说明,重复性的验证与发版工作全部交给流水线。