目录分层、设计系统与微前端:规模变大后怎么组织

项目刚开始时,components 里十个文件很好找。半年后堆到三百个,想改“文章收藏”却要翻 components、hooks、api、utils 四个大目录。此时问题是业务边界被目录摊散了。

从按技术分类,走到按功能聚合

下面是用于讨论边界的示意,不要求把现有 Nuxt 文件路由整体搬家:

Text
app/ pages/ 页面入口与路由组合 features/ article-search/ api.ts 请求与响应转换 model.ts 搜索条件与纯函数 SearchPanel.vue 交互界面 search.test.ts 可观察行为 components/ui/ 按钮、输入、对话框 shared/ 真正跨功能的少量能力

关键约束是 shared 不反向导入具体业务功能,功能之间不要任意穿透内部文件。跨功能协作可以由页面组合,或通过明确的公共接口。循环依赖会让初始化顺序和修改影响变得难预测,不能靠换文件夹名解决。

架构方法当作讨论工具

Feature-Sliced Design 用层、切片和公共入口约束依赖;Bulletproof React 展示按功能组织实际应用的做法。它们值得研究的是边界如何被维持。小型个人知识库直接复制全部层级,可能增加大量空文件。先记录真实痛点,再增加一条能解决它的规则。

可以写一个小型决策记录:“文章功能对外只暴露查询函数、摘要类型和列表组件;不要直接导入内部缓存对象。”半年后这句话比一张没人维护的漂亮架构图更管用。

设计系统不只是收集按钮

设计 token 定义颜色、间距、圆角等基础值;组件层规定交互状态和无障碍行为;业务页面组合这些组件。一个按钮至少有默认、悬停、焦点、禁用和提交中状态。如果每页独立发明一套,用户就要不断重新学习。

先从重复出现且行为明确的组件开始沉淀。业务规则复杂的“文章发布表单”可以留在功能目录,不必因为出现两次就放进全局 ui 库。设计系统的目标是体验一致,不是消灭所有重复。

什么时候才考虑微前端

微前端主要回应团队独立开发、部署和历史系统整合需求。它同时带来运行时隔离、路由协调、登录共享、CSS 冲突、依赖重复和跨应用可观测性成本。一个人维护的知识库,通常先做好模块化就足够。

Monorepo 是仓库组织方式,不自动等于微前端;多个包也可以一起部署。反过来,独立应用可以放在不同仓库。不要把代码放哪里、运行时如何组合、谁负责发布这三件事混成同一个决定。

练习与参考

给“收藏文章”设计目录,规定允许依赖的模块,再模拟把接口供应商换掉。若需要修改按钮样式文件,说明数据访问与视觉边界还缠在一起。

目录规则如何落到一次修改里

假设上游接口把 title 改名为 heading。理想情况下在 article-search/api.ts 将外部字段转换成内部 Article.title,组件继续读 title。这样“隔离变化”才有具体意义。如果所有组件直接读取原始响应字段,目录虽按功能分组,接口边界仍没建立。

公共入口可以只导出 searchArticles、Article 类型和 SearchPanel;内部缓存键和请求实现不对外暴露。不要让别的功能为了省一行代码,深层导入某个临时工具文件。需要复用时先确认概念真的相同,再提取到明确位置。两个功能恰好都叫 status,不意味着它们属于同一业务状态机。

设计 token 也要有语义:用 --color-text-muted 表达次要文字用途,比每页硬写某个灰色更容易统一主题。token 不会自动满足对比度要求,仍要检查实际前景与背景组合,尤其是悬停、禁用和错误状态。

微前端的评估应从发布责任开始:哪支团队必须独立发布哪块功能,宿主怎样发现新版本,登录过期怎样传播,子应用失败是否影响主导航?这些问题若没有现实需求,先用普通模块边界解决代码组织,能省掉大量运行时协调。