跳到主要内容

Monorepo:依赖漏斗与平台边界

一、目录不是架构,依赖方向才是架构

根目录 pnpm-workspace.yamlapps/*packages/**/*、桌面 layer/* 和移动端 Web 子工程纳入同一个 workspace。根 package.json 再用 Turbo 统一 buildtypechecktestlint 和 Web 构建。

从工程角色看,可以画成五层:

这是一个“依赖漏斗”:越底层越稳定、越少知道平台;越靠上越接近产品和部署环境。

二、包的职责地图

包/目录角色不应该负责什么
packages/internal/types全局声明与共享类型入口不承载业务副作用
constants枚举、标签、视图和 App 常量不直接发请求
modelsRSSHub/模型形状与数据映射辅助不决定 UI 生命周期
databaseSQLite schema、迁移、服务查询不渲染界面
shared环境、认证、桥、设置、跨端工具不承载某个平台页面
utilsURL、HTML、语言、媒体、数据结构工具不隐藏网络请求
store领域状态、同步、hydrate、mutation不依赖 Electron API
components跨端可复用 UI 和编辑器组件不拥有整个应用路由
desktop/layer/renderer桌面/Web 主应用与页面不把数据库实现散落到页面
apps/ssrWeb 客户端、SSR、Meta/OG 处理不成为移动端业务层
apps/mobileExpo/React Native 壳与原生能力不复制服务端模型

三、依赖方向里最关键的三条规则

规则 A:共享层不反向依赖应用层

@follow/store 可以被桌面和移动端共同依赖,但不能 import apps/desktop 的页面。否则一旦移动端复用 Store,Metro 会把 Electron 特有代码拖入打包。

规则 B:平台差异通过条件入口解决

数据库存在 db.desktop.tsdb.rn.ts 两种实现;共享设置通过 env.common.tsenv.desktop.tsenv.rn.tsenv.ssr.ts 分流;工具也有 event-bus.tsevent-bus.rn.ts。这比在业务代码里到处写 if (Platform.OS) 更容易审计。

规则 C:第三方协议在边界处翻译

@follow-app/client-sdk 是远端协议;本地 Store 使用自己的 Model;Drizzle 使用自己的 schema。翻译点集中在 store/src/morph 和数据库服务中,避免 SDK 版本升级把整个 UI 变成“远端 DTO 的直接消费者”。

四、为什么根脚本偏向 Turbo,而不是一个巨型脚本

根脚本只表达跨包任务:build:packagestypechecktestlintbuild:web。具体应用如何构建写在各自 package.json,Turbo 负责任务依赖和缓存。

例如 typecheck 依赖 @follow/electron-main#build,说明类型检查不是纯静态动作:Renderer 依赖 Electron Main 导出的类型/声明,必须先让这条边界产出可消费的结果。

五、把 Folo 搬到新平台要复制什么

如果要增加一个新客户端,最小集合不是“复制一套页面”,而是:

  1. 接入共享 types/constants/models
  2. 初始化 databasestore,确定平台 DB 适配;
  3. 复用 client SDK、认证、设置、查询和内容解析;
  4. 只在平台壳层实现启动、导航、通知、文件、推送和支付等差异;
  5. 为该平台补构建和发布流水线。

这套边界的核心价值是让业务复杂度只增长一次,平台数量增长不必线性复制。