从零构建并发布 Vue 3 脚手架 CLI 全记录
译注:本文编译自 dev.to 文章《How I Built and Published a Vue 3 Starter Kit CLI》,作者以 PR 为单位,完整记录了从初始化 Vue 应用到把 CLI 工具发布到 npm 的全过程。原文链接见文末。
create-vue-starter-with-test 是一个能在数秒内生成完整配置 Vue 3 项目的 CLI 工具。只需三条命令即可启动开发:
npx create-vue-starter-with-test my-app
cd my-app
yarn dev生成的项目内置 Vue 3、Vite、Vitest、Vue Router、Pinia、MSW(Mock Service Worker)、Sass、ESLint 与 Prettier,开箱即用。本文按 PR 顺序还原它的构建过程。
第一阶段:初始化模板应用
项目基于官方脚手架工具 create-vue 起步,运行 npm create vue@latest 后进入交互式选项,作者的选择如下:
✔ Project name: vue-starter
✔ Add TypeScript? → Yes
✔ Add JSX Support? → No
✔ Add Vue Router for Single Page Application development? → Yes
✔ Add Pinia for state management? → Yes
✔ Add Vitest for Unit Testing? → Yes
✔ Add an End-to-End Testing Solution? → No
✔ Add ESLint for code quality? → Yes
✔ Add Prettier for code formatting? → Yes
✔ Add Vue DevTools 7 extension for browser debugging? → Yes安装依赖并启动开发服务器后,脚手架已自动接好 TypeScript、Vue Router、Pinia、Vitest、ESLint 与 Prettier。接下来需要手动补齐 create-vue 默认不含的工具链:
yarn add -D msw sass技术栈选择:
- Vue 3:使用 Composition API 与
<script setup>语法 - Vite:构建工具与开发服务器
- TypeScript:全量类型支持
- Vitest:单元测试,配置为与 Vite 配置合并
- Vue Router:客户端路由
- Pinia:状态管理
- MSW:测试中的 API 模拟
- Sass:组件样式
配置文件:
vite.config.ts:配置 Vue 插件与@路径别名vitest.config.ts:合并 Vite 配置,以jsdom作为测试环境,同时支持__tests__/与*.test.ts两种文件模式tsconfig.json及tsconfig.app.json、tsconfig.node.json、tsconfig.vitest.json:通过 TypeScript 项目引用分离应用、Node 与测试代码eslint.config.ts、.prettierrc.json、.editorconfig:代码质量与格式统一
MSW 配置
MSW 通过 src/mock/serverSetup.ts 在 Node 环境(供 Vitest 使用)中配置:
import { setupServer } from 'msw/node';
import handlers from './handlers';
const server = setupServer(...handlers);
export { server };请求处理器按资源组织,例如 post 处理器拦截 GET /posts 并返回固定数据:
import { http, HttpResponse } from 'msw';
const postHandler = http.get('https://jsonplaceholder.typicode.com/posts', () => {
return HttpResponse.json([{ title: 'title a', body: 'body a' }]);
});
export default postHandler;这样测试永远不会访问真实网络,速度快、结果确定,也不依赖外部服务。
第二阶段:实现真实功能(Posts 视图)
骨架就绪后,第一个真实功能是 Posts 视图:从 API 拉取文章,加载时显示 loading,数据到达后渲染卡片,无数据时显示空状态。
PR #1 — Posts 空状态
PostView.vue 用 v-if / v-else-if / v-else 处理三种状态:
<div v-if="isLoadingPosts" class="loading">Loading...</div>
<div class="cards-wrapper" v-else-if="hasPosts">
<div class="card" v-for="(post, index) in posts" :key="index">...</div>
</div>
<div class="empty-state" v-else>Oops! Nothing to see here</div>hasPosts 计算属性把加载状态与数据状态干净地组合起来:
const hasPosts = computed(() => !isLoadingPosts.value && posts.value.length);对应测试 src/tests/pages/PostView.test.ts 使用 MSW 模拟 API,并用 @vue/test-utils 挂载组件:
beforeAll(() => server.listen());
afterAll(() => server.close());
afterEach(() => server.resetHandlers());
it('should render', async () => {
const wrapper = shallowMount(PostView);
expect(wrapper.find('.loading').exists()).toBe(true); // loading state
await flushPromises(); // wait for fetch + DOM update
const cards = wrapper.find('.cards-wrapper').findAll('.card');
expect(cards.length).toBe(1); // one card from mock handler
});其中 flushPromises() 是关键——它会清空所有异步队列,确保组件完全解析后再执行断言。
第三阶段:准备发布
PR #2 与 #3 处理文档与许可:包含使用说明的 README.md 和 MIT 协议的 LICENSE 文件。对开源 npm 包而言,这两项是必需的。
PR #4 — CLI 本体
这一步让项目从模板转变为可分发的 CLI,共添加三部分内容。
1. 面向 npm 的 package.json
{
"name": "create-vue-starter-with-test",
"version": "1.0.0",
"private": false,
"bin": {
"create-vue-starter-with-test": "dist/index.js"
},
"files": ["dist", "template"],
"type": "module"
}bin 字段告诉 npm 当用户执行 npx create-vue-starter-with-test 时运行什么;files 字段把发布内容限制为 dist/(编译后的 CLI)与 template/(项目脚手架)。
2. tsconfig.cli.json — CLI 专用的 TypeScript 配置
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}它只把 src/index.ts 编译为 dist/index.js,与模板应用的 TypeScript 配置完全分离。
3. src/index.ts — CLI 脚本
CLI 是一个 Node.js 脚本,运行时做四件事:
- 校验输入:未提供项目名时给出提示并退出
- 复制模板:递归把
template/内容复制到目标目录 - 清理:删除副本中的
.git目录 - 安装依赖:在新项目中执行
yarn install
#!/usr/bin/env node
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import chalk from 'chalk';
function copyDir(src: string, dest: string) {
fs.mkdirSync(dest, { recursive: true });
for (const file of fs.readdirSync(src)) {
const srcPath = path.join(src, file);
const destPath = path.join(dest, file);
fs.statSync(srcPath).isDirectory()
? copyDir(srcPath, destPath)
: fs.copyFileSync(srcPath, destPath);
}
}
async function main() {
const projectName = process.argv[2];
if (!projectName) { /* ... */ process.exit(1); }
const targetDir = path.resolve(process.cwd(), projectName);
// 复制模板、清理 .git、安装依赖
}小结
该项目展示了从 create-vue 起步、逐步叠加 MSW 与 Sass 等工具、编写带测试的真实功能,最终打包为 npm CLI 的完整路径。对希望沉淀团队脚手架或学习 CLI 发布流程的开发者而言,是一个结构清晰的参考案例。
原文链接
https://dev.to/hdjerry/how-i-built-and-published-a-vue-3-starter-kit-cli-28gc