项目沉淀、组件库拆分与 CLI 工程化导学
概述
项目"跑起来"只是起点。本文从工程化视角讲如何把一次性项目转化为可复用资产:模板项目瘦身、基础组件抽离成独立库、用 CLI 把重复易错的初始化动作自动化,并给出后续"模板 → 组件库 → TS 库 → 发布 → CLI → Monorepo"的演进主线。
学习目标
- 建立"沉淀可复用资产"的工程化思维,而非只追求单项目交付
- 掌握模板项目瘦身的思路:示例页与开发骨架分离
- 理解基础组件何时该抽成独立库,以及 Vite Library Mode 的关键配置
- 能识别适合 CLI 自动化的高频动作,并看懂
create-page这类脚手架的本质 - 了解多包沉淀到一定规模后如何用 Monorepo 组织
一、从"做完项目"到"沉淀资产"
当一个组件化或功能性项目完成后,真正产生复利价值的不是代码量,而是稳定复用的模式。沉淀方向通常是:提炼共性 → 沉淀模板(template-app)→ 抽离组件库(ui-library)→ 封装 CLI(create-app)→ 形成团队工程资产。前提不是"代码多",而是"已出现稳定复用模式";优先提炼高复用、低业务耦合的部分,目标是降低未来重复劳动,而非增加复杂度。
二、模板项目瘦身:示例与骨架分离
模板项目里常见的 pages/ 示例页本质是给使用者看的 demo,不是模板作者长期保留的业务骨架。熟悉模板后,这些示例不应作为默认内容留在真实项目里,否则新项目初始化会带出一堆无效页面。原则是:
- 示例型内容(demo-table、demo-form)→ 放到文档站 / demo 项目 / playground;
- 真实开发骨架(核心配置、基础页面结构、layouts、router)→ 留在
template项目。
典型结构是把应用与文档拆开:apps/template 放真实骨架,apps/docs/demos 放示例组件。模板越轻,初始化越快,维护成本越低。
三、基础组件抽离为独立库
components 里的基础组件若在多个项目复用,就不该只作为某模板的内部私有代码。更合理的做法是抽成独立库,建立单独的构建、测试、发布流程,再通过包依赖统一接入多个项目,避免"每项目复制一份再各自修改"的分叉失控。
打包时用 Vite Library Mode,并务必把 vue 等标记为 external,否则会把 Vue 重复打进产物,导致体积异常或重复实例:
import { defineConfig } from 'vite'
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
name: 'MyUILibrary',
fileName: 'my-ui'
},
rollupOptions: {
external: ['vue']
}
}
})抽离后,组件的语义、命名、类型定义与文档要求都会更严格;同时要控制边界,不要把业务状态耦合进基础组件。
四、CLI:把重复且易错的初始化自动化
单纯 git clone / degit 只能拿到模板,解决不了真实开发中的后置动作:新建 .vue、配置标题与图标、补国际化文案、处理多级目录、写入 definePage 元信息。这些高频、标准化、可规则化的动作最适合做成 CLI。
一个最小的 create-page 命令本质是"读取参数 + 操作文件系统":解析 --path/--title/--title-en/--icon,用 toPascalCase 生成统一组件名,渲染含 defineOptions 与 definePage 的 SFC,并把标题写入 zh-CN.json / en.json。写文件前用 fs.access 检查避免覆盖已有页面。CLI 不必一开始就大而全,先从"创建页面 / 创建模块 / 初始化仓库"这类高频动作做起,并保持与模板版本的对应关系。
function getArg(flag: string) {
const i = process.argv.indexOf(flag)
return i > -1 ? process.argv[i + 1] : ''
}
function toPascalCase(input: string) {
return input.split(/[\\/_-]/).filter(Boolean)
.map(p => p.charAt(0).toUpperCase() + p.slice(1)).join('')
}
async function createPage(opts: {
pagePath: string; titleZh: string; titleEn: string; icon: string
}) {
const vueFile = path.join(cwd, 'src/pages', `${opts.pagePath}.vue`)
await fs.mkdir(path.dirname(vueFile), { recursive: true })
await fs.writeFile(vueFile, buildVueSfc(opts), 'utf-8')
// 同步写入 zh-CN.json / en.json 标题文案 …
}五、后续工程化主线与术语校正
本篇是导学,后续主线是一条典型的前端工程化进阶链路:模板项目 → 组件抽离 → TS 基础库构建 → Vue 组件库构建 → 发布交付 → CLI 自动化 → Monorepo 管理。这些能力最终都服务于"复用、协作、交付、效率",不要当成孤立主题。
整理时需注意转写误差,以官方文档为准的几个关键点:
- 项目初始化优先记
npm create vue@latest(底层脚手架create-vue);npm init vue@latest多为口语化旧表达。 - 组件库构建优先参考 Vite 官方 Library Mode。
- 术语校正:
Git 仓库(非"get 仓库")、.vue文件、locales、en.json/zh-CN.json、Monorepo(非"model rebel")。
六、Monorepo:多包沉淀的组织方式
当沉淀物变多——模板、UI 库、CLI、TS 库各自独立——用多个零散仓库管理会迅速出现版本错配、重复依赖、发布繁琐的问题。pnpm Workspace 是把它们组织成一个 Monorepo 的轻量方案:根目录 pnpm-workspace.yaml 声明 packages/*,各包通过 workspace:* 互相引用,统一安装、统一构建、统一发包。
# pnpm-workspace.yaml
packages:
- 'packages/*'// packages/admin/package.json
{ "dependencies": { "@company/ui": "workspace:*" } }何时该上 Monorepo 的判断:包之间存在相互引用(如 admin 依赖 @company/ui)、需要统一版本与 Changelog、希望一次提交联动多处改动。若只是单个组件库,单仓单包反而更简单,不必为"看起来专业"而提前引入 Monorepo 的复杂度。反过来,当 CLI 要同时生成模板页并引用 UI 库类型时,多包协作的收益就开始显现;此时再引入 changesets 之类的版本与发布工具,就能把"沉淀—复用—发布"闭环跑通。
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 模板项目越来越臃肿 | demo / 实验页长期留在模板 | 示例迁到文档站或 demo 应用,模板只留最小骨架 |
| 基础组件复用差、改不动 | 长期内嵌单项目、无独立版本 | 抽离为独立库,建单独构建 / 测试 / 发布流程 |
| 只会拉模板不会自动化 | 把 clone / degit 当完整工程化 | 识别重复动作,逐步封装成 CLI |
| 新增页面漏配标题 / 图标 / i18n | 手工步骤多、无统一规范 | 用 CLI 统一生成骨架与语言包 |
| 组件库打包体积异常 | 未正确 external 依赖 | 将 vue 等标记 external |
| 多包版本错配、依赖重复 | 零散仓库各自维护 | 用 pnpm Workspace 组织为 Monorepo |
延伸阅读
- 上一篇:文档站与项目初始化 — VitePress 与 Git 高级克隆
- 下一篇:图标资源接入与复制能力 — 进入图标体系模块
- 相关:自研组件库 — 组件库模块总览
- 相关:create-vue 仓库 · Vite Library Mode · pnpm Workspaces