Skip to content

从零构建并发布 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 工具。只需三条命令即可启动开发:

shell
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 后进入交互式选项,作者的选择如下:

plaintext
✔ 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 默认不含的工具链:

shell
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 使用)中配置:

typescript
import { setupServer } from 'msw/node';
import handlers from './handlers';

const server = setupServer(...handlers);
export { server };

请求处理器按资源组织,例如 post 处理器拦截 GET /posts 并返回固定数据:

typescript
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 处理三种状态:

vue
<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 计算属性把加载状态与数据状态干净地组合起来:

typescript
const hasPosts = computed(() => !isLoadingPosts.value && posts.value.length);

对应测试 src/tests/pages/PostView.test.ts 使用 MSW 模拟 API,并用 @vue/test-utils 挂载组件:

typescript
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

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 配置

json
{
  "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 脚本,运行时做四件事:

  1. 校验输入:未提供项目名时给出提示并退出
  2. 复制模板:递归把 template/ 内容复制到目标目录
  3. 清理:删除副本中的 .git 目录
  4. 安装依赖:在新项目中执行 yarn install
typescript
#!/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