Files
what-to-eat/docs/decisions/0003-native-ios-monorepo.md
T

72 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 0003 原生 iOS App 与 monorepo 结构
- 状态:已采纳
- 日期:2026-10-18
- 关联:调整 [#98](https://github.com/YunYouJun/cook/issues/98) 的阶段 4
## 背景
- 目标从「只维护一套网页」变成「打磨一个能上架收费的 iOS App」,Web 保留并继续服务浏览器和云乐坊容器。
- 现在的独立 App 是 Capacitor 壳,界面是 Ionic 画的网页:拿不到 iOS 26 的 Liquid Glass、Widget、App Intents、Spotlight,CloudKit 同步也要自己写插件,作为付费 App 说服力和过审把握都不够。
- 仓库已经半个 monorepo:根目录是 Nuxt 应用,`packages/cook` 里有类型、CSV 转换、推荐(`recommend.ts`)和搜索(`search.ts`),核心匹配逻辑不到 200 行。
## 方案对比
| 问题 | 方案 | 取舍 |
| ------------ | -------------------------------------------------- | ---------------------------------------------------------- |
| iOS 客户端 | Capacitor 壳(现状) | 一套代码;体验上限低,系统能力要逐个写插件 |
| | **SwiftUI 原生** | 体验和系统能力最好;核心逻辑要用 Swift 再写一份 |
| 逻辑如何共享 | iOS 内嵌 JavaScriptCore 跑 TS 核心 | 零重复;跨语言调试、类型和并发都别扭 |
| | **共享数据 + 共享测试用例,Swift 移植核心** | 要写两份,但核心很小;同一套用例保证两端推荐结果一致 |
| 仓库结构 | 原生单独开仓库 | 数据和用例要跨仓库同步,容易漂移 |
| | **参考 starter-monorepo:`apps/*` + `packages/*`** | Web、iOS、数据、核心在一个仓库,一个 PR 能同时改数据和两端 |
| 同步 | 只用 CloudBase | 三端通用,但要登录,服务器有成本 |
| | **CloudKit 免费 + CloudBase 会员跨平台** | 免费用户零成本零登录;会员费覆盖服务器成本 |
## 决定
### 目录结构
```
apps/
web/ Nuxt 应用(现在根目录的 app/、public/、nuxt.config.ts 等)
ios/ SwiftUI 工程(替换现在 Capacitor 的 ios/)
packages/
data/ recipe.csv、incompatible-foods.csv、生成的 JSON、JSON Schema、dataVersion
core/ 纯 TS:类型、推荐、搜索、别名、相克判断,不依赖 Vue / Node
cook/ CLI(convert / fetch / validate / eval),沿用 @yunyoujun/cook 包名
fixtures/ 两端共用的测试用例:输入食材 → 期望的菜谱 id 顺序
cloudfunctions/ 不变
docs/ 不变
```
- 搬迁只做 `git mv` 和路径修改,不改逻辑;搬完 `pnpm lint`、`typecheck`、`test`、`build` 全绿再合并。
- `packages/core` 从 `packages/cook` 里拆出;`@yunyoujun/cook/recommend` 保留重新导出,已有用法不受影响。
- iOS 侧有一个 Swift Package(`CookCore`),读同一份 JSON、跑同一份 `packages/fixtures`。推荐逻辑改动必须同时改两端,CI 里两边都跑用例。
> **实施时的调整(2026-10-19)**:`packages/cook` 的推荐逻辑本来就不依赖 Vue / Node,并已通过 `@yunyoujun/cook/recommend` 单独导出,所以不再拆 `packages/core`;共享用例放在 `packages/data/fixtures/`,不单独建 `packages/fixtures`。食材词典、常备调料和别名也改成 `packages/data` 里的 JSON,Web 和 iOS 读同一份。
### 数据
- JSON 加 `schemaVersion`(结构)和 `dataVersion`(内容)。App 内置一份,启动后可从 CDN 拉更新的 `dataVersion`,改菜谱不用发版。
- 菜谱 `id` 规则不变(见 [0002](./0002-feedback-and-ids)):收藏、历史、同步都以 id 为主键。
### 同步与收费
- 本地数据库是唯一数据源:iOS 用 SwiftData,CloudKit 同步本地数据。
- 会员开启 CloudBase 作为第二个同步通道,与 Web 共用账号;两个云之间不直接同步。每条记录带 `updatedAt` 和软删除标记,冲突时取最新。
- iOS 里的会员走 StoreKit 内购;Web 购买的会员登录后可在 iOS 解锁(App Store 审核指南 3.1.3(b))。
- App 免费下载 + 会员,不做付费下载,方便沿用现有商店条目和用户。
### 与 #98 的关系
- 阶段 1~3(云乐坊容器、数据同步)照常进行,Web 仍是浏览器和云乐坊的唯一代码。
- 阶段 4 改为:独立 App 不下架,而是用 SwiftUI 版本替换 Capacitor 版本,沿用 `cn.yunyoujun.cook`,老用户直接收到更新。Capacitor 相关代码在 SwiftUI 版上架后移除;`android/` 是否保留另行决定。
## 影响
- iOS 开发、构建和 UI 调试需要 macOS + Xcode 26,云端环境只能写代码和跑纯 Swift / TS 测试。
- 推荐逻辑的改动成本变成两份,靠共享用例兜底;核心逻辑应尽量保持小而纯。
- 搬迁会和进行中的 PR 冲突,需要等它们合并后再开始,且搬迁 PR 要尽快合并。
- 打磨计划和验收标准见 [iOS App 打磨目标](/dev/ios-roadmap)。