4、项目结构设计:packages 目录划分、共享模块设计、版本管理策略
好,咱们聊聊项目结构。说实话,很多团队一开始不重视这个,觉得「能跑就行」。结果项目一大了,依赖关系乱成一锅粥,改一个公共组件恨不得全项目都崩。我经历过这种痛苦,所以今天把这部分掰开揉碎了讲清楚。
4.1 packages 目录划分:从「大泥球」到「乐高积木」
先问个问题:你的项目里,是不是所有代码都堆在一个 src 目录下?如果是,那你大概率已经踩过坑了。我个人习惯,从一开始就用 monorepo 结构,把项目拆成多个 package。
为什么要这么干?说白了,就是让每个模块职责清晰,互不干扰。你想想看,一个电商项目,有用户模块、商品模块、订单模块、支付模块……如果全混在一起,改个支付逻辑,不小心影响了商品展示,这锅谁背?
我建议的 packages 目录结构长这样:
packages/
├── shared/ # 共享工具库(工具函数、常量、类型定义)
├── ui/ # 基础 UI 组件库(按钮、输入框、弹窗等)
├── utils/ # 业务工具函数(日期格式化、金额计算等)
├── hooks/ # 通用 hooks(useAuth、useFetch 等)
├── api/ # API 接口层(请求封装、拦截器、mock 数据)
├── config/ # 项目配置(环境变量、路由配置、主题配置)
├── app-admin/ # 后台管理应用
├── app-h5/ # H5 移动端应用
└── app-wechat/ # 微信小程序应用
每个 package 都是一个独立的「小项目」,有自己的 package.json、tsconfig.json、vite.config.ts。它们之间通过 workspace 协议互相引用,而不是直接 copy 代码。
核心原则:每个 package 只做一件事,并且做好。如果某个 package 开始变得臃肿,就考虑拆分成更小的 package。
4.2 共享模块设计:别让「共享」变成「灾难」
共享模块听起来很美,但用不好就是灾难。我在项目中遇到过,一个共享组件被十几个业务方引用,结果改了一个参数类型,全项目报错。那场面,啧啧……
所以,共享模块设计要遵循几个铁律:
- 稳定优先:共享模块的 API 要尽量稳定,不要频繁变动。如果非要改,先发一个 deprecation warning,给业务方留足迁移时间。
- 最小依赖:共享模块尽量不要依赖其他业务模块,否则容易形成循环依赖。我见过一个项目,shared 依赖了 utils,utils 又依赖了 shared……直接死循环。
- 版本独立:每个共享模块都有自己的版本号,业务方可以按需升级,而不是被迫跟着主项目升级。
举个例子,共享的 UI 组件库:
// packages/ui/Button/index.tsx
import React from 'react';
import type { ButtonProps } from './types';
export const Button: React.FC<ButtonProps> = ({
children,
variant = 'primary',
size = 'md',
...rest
}) => {
return (
<button
className={`btn btn-${variant} btn-${size}`}
{...rest}
>
{children}
</button>
);
};
这个 Button 组件只依赖 React 和自身的类型定义,不依赖任何业务逻辑。任何应用都可以直接引用它,而不用担心引入不必要的依赖。
小技巧:共享模块的入口文件要尽量精简。我习惯只暴露必要的 API,内部实现细节全部隐藏。这样即使内部重构,对外部的影响也最小。
4.3 版本管理策略:别让版本号变成「数字游戏」
版本管理这事儿,说简单也简单,说复杂也复杂。简单来说,就是遵循 semver(语义化版本)规范。但实际落地时,有很多细节需要注意。
我建议的版本管理策略:
| 版本号 | 含义 | 示例 | 变更类型 |
|---|---|---|---|
| major | 主版本号 | 2.0.0 | 不兼容的 API 修改 |
| minor | 次版本号 | 1.3.0 | 向下兼容的功能新增 |
| patch | 修订号 | 1.2.5 | 向下兼容的问题修正 |
| pre-release | 预发布版本 | 1.2.5-alpha.1 | 测试中的版本 |
嗯,这里要注意:不要随意 bump 主版本号。我曾经见过一个项目,半年内从 1.0.0 升到了 9.0.0,每次升级都是「重构了代码结构」。说白了,这就是把主版本号当成了「大版本迭代」的计数器,完全违背了 semver 的初衷。
正确的做法是:
- 修复 bug → 升 patch 版本
- 新增功能(向下兼容)→ 升 minor 版本
- 破坏性变更 → 升 major 版本
对于 monorepo 项目,我推荐使用 Changesets 来管理版本。它会自动分析每个 package 的变更,生成对应的 changelog,并且支持一键发布。
避坑指南:我曾经在项目中遇到一个问题——两个 package 互相依赖,结果升级时出现了「先有鸡还是先有蛋」的困境。解决方案是:确保依赖关系是单向的,或者使用 workspace 协议让 pnpm 自动处理依赖顺序。
4.4 知识体系结构图
下面这张图,是我对项目结构设计的核心理解。它展示了 packages 目录、共享模块和版本管理三者之间的关系:
这张图想表达的核心思想是:目录结构是骨架,共享模块是血肉,版本管理是神经系统。三者缺一不可,共同构成了一个健壮的项目结构。
4.5 实战建议:从零开始搭建
如果你现在要开始一个新项目,我建议按这个步骤来:
- 先搭架子:用 pnpm workspace 初始化 monorepo,创建 packages 目录
- 再建共享:把公共的工具函数、类型定义、UI 组件抽到 shared 包里
- 后接应用:在 app-* 目录下创建具体的业务应用
- 最后管版本:引入 Changesets,配置好自动发布流程
嗯,这里有个小细节:不要一开始就把所有东西都抽成共享模块。我见过有人项目刚启动,就搞了十几个共享包,结果大部分都是空的。正确的做法是:先让业务跑起来,发现有重复代码了,再抽到共享模块里。这叫「按需抽取」,而不是「过度设计」。
我的习惯:每次抽取共享模块时,我都会问自己三个问题:1)这个模块会被多少个应用使用?2)它的 API 是否足够稳定?3)它是否依赖了不该依赖的东西?如果三个答案都是肯定的,那就抽。否则,先放一放。
好了,项目结构设计这块就聊到这儿。记住一句话:好的结构不是设计出来的,是演化出来的。别追求一步到位,保持迭代的心态,你的项目结构会越来越合理。