{T}

项目沉淀、组件库拆分与 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 重复打进产物,导致体积异常或重复实例:

ts
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 生成统一组件名,渲染含 defineOptionsdefinePage 的 SFC,并把标题写入 zh-CN.json / en.json。写文件前用 fs.access 检查避免覆盖已有页面。CLI 不必一开始就大而全,先从"创建页面 / 创建模块 / 初始化仓库"这类高频动作做起,并保持与模板版本的对应关系。

ts
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 文件、localesen.json / zh-CN.jsonMonorepo(非"model rebel")。

六、Monorepo:多包沉淀的组织方式

当沉淀物变多——模板、UI 库、CLI、TS 库各自独立——用多个零散仓库管理会迅速出现版本错配、重复依赖、发布繁琐的问题。pnpm Workspace 是把它们组织成一个 Monorepo 的轻量方案:根目录 pnpm-workspace.yaml 声明 packages/*,各包通过 workspace:* 互相引用,统一安装、统一构建、统一发包。

yaml
# pnpm-workspace.yaml
packages:
  - 'packages/*'
jsonc
// 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

延伸阅读