PostHog 产品前端代码落位指南:products/<name>/frontend/与frontend/src/scenes/的分界线
【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog
本指南回答一个在 PostHog monorepo(products/README.md 定义的纵向切片架构)中每天都会遇到的问题:新增一个产品场景(scene)、组件或逻辑文件时,应该放进
products/<name>/frontend/,还是frontend/src/scenes/<name>/?目录选择不是风格问题,而是真实的工程边界——它决定你的 PR 在合并队列(merge queue)中占据哪条 lane、与多少其他前端 PR 串行化、以及产品的 UI 迁移进度如何被量化。读完本文,你将掌握决策表、迁移进度检查脚本、边界背后的数据依据,以及它与合并队列并行度的因果关系。
PostHog 将仓库组织为"产品(product)= 垂直切片":每个产品在products/<name>/下同时拥有 Django 后端、React/TypeScript 前端和可选的共享代码(见 products/README.md)。本文以.agents/skills/placing-product-frontend-code/SKILL.md为核心,结合仓库中的判定脚本、lane 分配脚本与相关文档,完整还原这条落位规范的来龙去脉。
核心结论:产品 UI 属于products/<name>/frontend/
一个产品的 UI 应放在products/<name>/frontend/,而不是frontend/src/scenes/<name>/。
这句话写进了 skill 的标题,也被 frontend/src/AGENTS.md(Rule 2)引用为前端 agent 的强制规则。当前约 18 个产品仍然同时在这两棵树中持有 UI,因此"scenes/下已经有一个同名文件夹"并不能作为新文件应该放那里的证据——落位前先检查。
要判断某个目录当前处于什么迁移状态,运行仓库自带的检查脚本:
# 查看某一个目录的迁移进度 python3 .agents/skills/placing-product-frontend-code/scripts/scene_product_split.py>bin/hogli product:bootstrap your_product_name它会生成完整结构(apps.py、package.json、manifest.tsx、backend/、frontend/等),详见 products/README.md 的 "Adding a new product" 一节。可用bin/hogli product:lint your_product_name校验结构是否符合约定。manifest.tsx描述产品的前端 scenes、routes、urls、文件系统类型与导航树条目,所有 manifest 会在构建时合并进frontend/src/products.tsx与frontend/src/products.json。
整体迁移是受欢迎的
skill 明确表示:整体迁移一个既有 scene 是受欢迎且正是这条约定的目的所在。只需预期一件事:迁移 PR 会把每个 merge-queue target 都上报一次——因为 scene 要注册进products/<name>/manifest.tsx,这是每次迁移的一次性成本。从 products/data_warehouse/manifest.tsx 可以看到,manifest 中的import: () => import('./DataWarehouseScene')之类的懒加载引用就是 scene 与产品树之间的注册纽带。
为什么目录是一条真实的边界
lane 分配完全按路径进行
仓库的 .github/scripts/trunk-impacted-targets.js 负责把 PR 的变更文件映射为 Trunk 并行合并队列的 lane 目标。其安全不变式是:Trunk 仅当两个 PR 的目标集合不相交时才让它们并行合并——这意味这两个 PR 从未一起测试就合入 master。因此"多报 target 只损失并行度,少报则可能让互相冲突的 PR 并肩落地、打破 master",脚本的每一条规则都刻意偏向于多报(见文件开头的注释)。
对前端路径的后果是:
frontend/下任何变更都会上报fe:core加上每一个fe:product:*target(见addJavaScriptLanes,trunk-impacted-targets.js),因此在队列中与所有其他前端 PR 串行化;- 限定在
products/<name>/frontend/内的变更只上报一个 target(fe:product:<name>)。
也就是说:每往frontend/src/放一个文件,就等于把你的 PR 放进与整个前端队列的串行依赖里;每往products/<name>/frontend/放一个文件,你只与同产品的 PR 串行。这是"目录选择 = 并行度选择"的直接证据。
为什么不用前端依赖图代替路径
最"显然"的修复——用前端静态依赖图收窄 lane、替代路径规则——被实测否决了。skill 记录了完整测量数据:
- 对前端(8430 个文件、43924 条边)构建静态导入图;
- 2226 个模块落入同一个强连通分量(SCC),占图的 27%,横跨 31 个产品;
- SCC 的每个成员按定义拥有完全相同的反向可达集,因此从
frontend/src下任意文件出发的反向可达都会触及 73/79 个产品——replay player 里嵌套五层的叶子 tab 组件与types.ts给出逐字节相同的答案; - 剪掉全部 498 条
lib/**→scenes|products回边,只把数量从 6918 移到 6909——数百条冗余环,而非某一条坏边。
换言之,前端导入图高度纠缠,任何基于图的可达性信号都无法区分文件之间的影响范围,路径是唯一能产生区分度的信号。
佐证之一是 bin/find-affected-stories:这个 Storybook 视觉回归选择脚本在FULL_RUN_PATTERNS中把frontend/src/lib/列为全量运行失效器(bin/find-affected-stories)——即只要lib/有变更就退化为全量 story 运行。它通过承认"静态导入图覆盖不了lib/的全局副作用",从侧面印证了图信号的不充分。
移动文件带来的收益边界
因此:路径是唯一能区分 lane 的信号,移动文件是唯一能收窄 lane 的手段。但 skill 特别提醒要看清这件事买到了什么、没买到什么:
- 买到的是merge-queue 并行度——你的前端 PR 不再被整个前端队列阻塞;
- 没买到的是解耦——导入图依旧和之前一样纠缠,
lib/的全量失效语义、模块间的强连通性都不会因换目录而改变。
"搬目录"是 CI/协作层面的收窄,不是架构层面的解耦。
实测:在仓库中验证判定脚本
以上全部结论都可以在当前仓库中复现。运行判定脚本(无参数列出所有"有产品对应物"的 scene 目录,单参数查看某目录),并对照真实目录结构:
# 查看 contenteditable="false">【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.
项目地址: https://gitcode.com/GitHub_Trending/po/posthog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考