从单体 CLI 到模块化插件:应用绞杀树模式
I’m happy to help translate the article, but I’ll need the full text you’d like translated. Could you please paste the content (excluding the source line you’ve already provided) here? Once I have it, I’ll translate it into Simplified Chinese while preserving the formatting, markdown, and technical terms as requested.
TL;DR – 安全迁移策略
- 将命令提取到 外部插件(以 beta 形式发布)
- 在核心中将插件添加为 依赖(v2.x – 零破坏性更改)
- 收集反馈,迭代,稳定
- 移除依赖 在 v3.0.0(破坏性更改,但已做好准备)
- 用户仅安装 他们需要的部分
结果:安全迁移 + 独立插件演进
问题:CLI 如何变得难以维护
Your CLI probably started like this:
cli/
├── commands/
│ ├── deploy.js
│ └── status.js
├── utils/
│ └── helpers.js
└── index.js
干净。简洁。一切井然有序。
Six months later:
cli/
├── commands/ (37 files)
├── utils/ (23 files)
├── services/ (15 files)
├── integrations/ (12 files)
└── shared/ (who even knows?)
Now you’re dealing with:
- 紧耦合 – 每个命令都会导入 10+ 个工具文件
- 共享状态噩梦 – 全局配置被所有东西触及
- 风险发布 – 一处改动需要测试整个 CLI
- 开发缓慢 – 添加功能需要数周的协调
- 破坏性变更地狱 – 无法在不破坏其他部分的情况下演进某一部分
Sound familiar? 你已经构建了一个单体系统。
后端课程:从单体到模块化
旧方式(单体 API)
Everything in one codebase
↓
Tightly coupled domains
↓
Coordinated releases
↓
High‑risk deployments
↓
Slow feature velocity
现代方式(微服务 / 模块化)
Thin API Gateway
↓
Isolated domain services
↓
Independent deployments
↓
Versioned contracts
↓
Fast, safe iterations
关键洞见: 隔离领域,建立清晰边界,支持独立演进。这同样适用于 CLI。
解决方案:核心 + 插件 架构
轻量核心
Your core should be boring and stable:
core/
├── auth/ // Auth logic
├── config/ // Config management
├── dispatcher/ // Command routing
├── utils/ // Cross‑cutting concerns
└── plugin-loader/ // Plugin discovery
核心提供基础设施,并且不干扰业务逻辑。
插件
Each plugin is a self‑contained domain:
plugins/
├── deploy-plugin/
│ ├── commands/
│ ├── tests/
│ ├── README.md
│ └── package.json // Independent versioning!
├── logs-plugin/
└── backup-plugin/
每个插件:
- 只承担唯一职责
- 拥有自己的语义化版本号
- 可独立安装(
npm i @cli/deploy-plugin) - 变更时不影响其他插件
- 在完全隔离的环境中进行测试
Source: …
迁移策略:绞杀树模式
绞杀树模式(Martin Fowler)让你能够逐步迁移,而无需冒险进行一次性的大改写。
步骤 1 – 以 Beta 版发布外部插件
# Release your first plugin in beta
npm publish @my-cli/deploy-plugin@1.0.0-beta.1
npm publish @my-cli/logs-plugin@1.0.0-beta.1
为什么是 beta? 用户可以自行选择加入测试,你可以快速迭代,且破坏性变更是可以预期的。
// deploy-plugin/package.json
{
"name": "@my-cli/deploy-plugin",
"version": "1.0.0-beta.1",
"main": "dist/index.js",
"peerDependencies": {
"@my-cli/core": "^2.0.0"
}
}
步骤 2 – 在 Core 中临时将插件作为依赖添加
// core/package.json
{
"name": "@my-cli/core",
"version": "2.5.0",
"dependencies": {
"@my-cli/deploy-plugin": "^1.0.0-beta.1",
"@my-cli/logs-plugin": "^1.0.0-beta.1"
}
}
- 用户安装
core→ 插件会自动随之安装 - 对已有用户没有破坏性变更
- 插件可以独立使用,方便早期采用者
步骤 3 – 逐步迁移命令
v2.5.0(初始 Beta)
deploy → Plugin (bundled as dependency)
logs → Legacy in core
backup → Legacy in core
v2.8.0(Beta 2)
deploy → Plugin (bundled as dependency)
logs → Plugin (bundled as dependency)
backup → Legacy in core
每一步都安全发布,用户感受不到差异,插件在真实环境中逐渐成熟。
步骤 4 – 大切换:移除依赖(v3.0.0)
// core/package.json (v3.0.0)
{
"name": "@my-cli/core",
"version": "3.0.0",
"dependencies": {
// No more plugin dependencies!
},
"peerDependencies": {
// Plugins are now optional
}
}
用户迁移指南
# Old way (v2.x) – everything bundled
npm install -g @my-cli/core
# New way (v3.x) – install what you need
npm install -g @my-cli/core
npm install -g @my-cli/deploy-plugin # 只在需要 deploy 时安装
npm install -g @my-cli/logs-plugin # 只在需要 logs 时安装
为什么是大版本号: 破坏性变更——插件不再自动安装,用户拥有更多控制权,安装体积更小,安装速度更快。
步骤 5 – 用户按需安装
# Minimal installation (just core utilities)
npm install -g @my-cli/core
# À la carte (only what I use)
npm install -g @my-cli/core @my-cli/deploy-plugin
# Everything (opt‑in to all plugins)
npm install -g @my-cli/all # metapackage
收益
- 安装体积更小(例如 15 MB 对比 250 MB)
- 启动速度更快
- 每个插件可以独立演进
- 发布更安全、更可控
通过把你的 CLI 当作微服务生态系统来对待,并运用绞杀树模式,你可以在最小风险和最大灵活性的前提下,从单体代码库平滑过渡到模块化、插件驱动的架构。