十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

React Native Firebase 单仓构建工具链设计解析:prepare 依赖图、Nx 本地缓存与 watch 机制

React Native Firebase 单仓构建工具链设计解析:prepare 依赖图、Nx 本地缓存与 watch 机制 React Native Firebase 单仓构建工具链设计解析prepare 依赖图、Nx 本地缓存与 watch 机制【免费下载链接】react-native-firebase A well-tested feature-rich modular Firebase implementation for React Native. Supports both iOS Android platforms for all Firebase services.项目地址: https://gitcode.com/gh_mirrors/re/react-native-firebaseReact Native FirebaseRNFB是一个以 19 个 npm 包形式组织在单仓库monorepo中的模块化 Firebase 实现。本文基于仓库内 prepare-and-cache.md 这一核心设计文档系统讲解其构建工具链的完整设计包依赖图如何决定构建顺序、Nx 本地缓存如何让无改动构建近乎零耗时、声明映射如何打通 IDE 跳转源码、dependency-cruiser 如何守住依赖边界以及开发者 watch 与事件驱动的 e2e TDD 循环为何被设计为延后。读完本文你将掌握这套单仓构建体系的设计意图、每个配置项的取舍理由以及仓库中对应的真实落地文件。文档定位设计细节的唯一事实来源在 RNFB 的 monorepo-tooling 文档体系中事实被刻意地做了所有权切分见 documentation-policy.md 的单一事实所有者约定是什么 为什么决策与备选方案由 architecture-decisions.md 拥有即 MonoTool-AD-1 至 MonoTool-AD-12 系列 ADR具体怎么做设计细节、配置、命令由本文对应的 prepare-and-cache.md 拥有滚动排期与验收标准属于易变信息归 work-queue.md 所有。三条纪律贯穿全文标准的 prepare 入口命令以 agent-command-policy.md 为准本文不重新定义入口其他文档引用包数量时一律引用本文而非自行复述所有设计的目标Goals在 ADR 中明确定义为干净工作树上确定性的、顺序正确的包构建快速本地重建避免重复转译未变更包发布可独立消费的 npm 包IDE 跳转到 TypeScript 源码低摩擦的开发内循环watch / TDD。包依赖图19 个包、一个 star hub 与一条链包清单其他文档引用的计数源头仓库packages/*下共有19 个包其依赖关系决定了整个构建顺序app是枢纽包hub不依赖任何内部包17 个包需要显式声明react-native-firebase/app为devDependency——即除app和vertexai之外的所有包16 个普通卫星包加aivertexai不需要显式的app边它已在dependencies中声明了react-native-firebase/ai因此会传递性地排在ai进而排在app之后18 个包以app为peerDependencies除app外的全部。peer 边是运行时契约任务运行器会忽略它们——这与上述用于构建排序的 devDeps 不是同一集合。编译期图bob / tsc 真正需要什么消费者需要先构建证据16 个普通卫星包 ai除app、vertexai外全部react-native-firebase/app→packages/app/dist/**各包tsconfig.json的paths将react-native-firebase/app映射到../app/dist/typescript/liblib/**中有react-native-firebase/app/dist/module/...的导入aiappauthapp-checkpackages/ai/tsconfig.json 的pathspackages/ai/lib/service.ts 导入app与authvertexaiai传递性地 →appdependencies[react-native-firebase/ai]并对其做 re-exportin-app-messaging、remote-config仅 peer 依赖analytics无lib/**导入运行时/peer 约束不是构建边整体形状是一个 star hubapp 一条链(auth, app-check) → ai → vertexai。运行器图Lerna/Nx 排序依据Lerna/Nx 只按dependenciesdevDependencies做拓扑排序peerDependencies被忽略。在这项工作之前只有aidevDeps 含auth、app-check和vertexaidep 含ai有可见的边于是app与它的依赖者们全部落在同一个并行波次里——干净树构建时卫星包的tsc/bob完全可能抢在packages/app/dist/**生成之前启动这就是 MonoTool-AD-3 所描述的干净树竞态clean-tree race当前之所以碰巧能跑只是依赖时序运气和残留的dist/。修复方案把编译边声明为 devDependencies给上述17 个包16 个普通卫星 ai的devDependencies加上react-native-firebase/app: versionvertexai有意排除——它已有的ai依赖边已足够排序。得到的运行器图关键点在于devDependencies会从发布 tarball 中被剥离npm 消费者不受影响peerDependencies继续作为运行时契约存在。仓库中 packages/ai/package.json 的 devDependencies 已包含react-native-firebase/app、react-native-firebase/app-check、react-native-firebase/auth三项版本与 monorepo 固定版本一致当前为26.4.0见 lerna.json而 packages/vertexai/package.json 只在dependencies中声明react-native-firebase/ai与设计完全吻合。Nx 本地缓存让无改动构建退化为缓存回放设计决策 MonoTool-AD-1 与 MonoTool-AD-4 的核心是不引入 Turborepo、不连接 Nx Cloud仅通过新增nx.json解锁 Nx 的本地计算缓存同时保持yarn lerna:prepare这个入口名不变。仓库根 package.json 第 9 行的实际落地命令为yarn lerna:prepare # 展开为 cross-env NX_NO_CLOUDtrue NX_CACHE_DIRECTORY.nx/cache NX_WORKSPACE_DATA_DIRECTORY.nx/workspace-data lerna run prepare即入口保持一行内联没有scripts/*.sh包装脚本只是加上了NX_NO_CLOUDtrue这一运行时的禁云守卫neverConnectToCloud只阻止连接/配置阶段命令运行时必须靠NX_NO_CLOUD或--no-cloud并显式把缓存目录钉在.nx/cache与.nx/workspace-data。仓库根目录的 nx.json 与设计文档给出的配置逐字一致{ neverConnectToCloud: true, namedInputs: { jsSource: [ {projectRoot}/lib/**/*, {projectRoot}/plugin/**/*, {projectRoot}/tsconfig.json, {projectRoot}/plugin/tsconfig.json, {projectRoot}/package.json, {workspaceRoot}/tsconfig.packages.base.json, {workspaceRoot}/yarn.lock ] }, targetDefaults: { prepare: { dependsOn: [^prepare], inputs: [jsSource], cache: true, outputs: [ {projectRoot}/dist/**, {projectRoot}/plugin/build/**, {projectRoot}/lib/version.ts ] } } }三个关键设计点dependsOn: [^prepare] devDependency 边 正确的构建顺序。^prepare表示上游项目的 prepare 先跑Nx 会自动拉取上游prepare的输出与哈希。配合 MonoTool-AD-3 的 devDep 边hub 先于卫星的次序由任务图强制保证而不是靠手动分阶段脚本prepare:hub→prepare:satellites这种方案被明确否决因为它暴露无用的中间状态。inputs: [jsSource]把缓存键收敛到 prepare 真正消费的输入MonoTool-AD-11。若不加显式inputsNx 默认哈希整个{projectRoot}——那么对__tests__/**、e2e/**、android/**、ios/**或文档的无关修改都会使该包的prepare缓存失效并经由^prepare级联失效所有依赖者。jsSource只包含 JS 源码、插件源码、包级与基座 tsconfig、携带 bob 配置的package.json以及 workspace 根yarn.locklockfile 变更会触发工具链升级导致的缓存失效。命名刻意不用 Nx 惯例的production因为这是 RN 库包而非部署型应用。一个精妙的细节lib/version.ts虽匹配lib/**通配但它是 gitignored 文件Nx 的文件哈希器会把它排除出输入——不会自我失效。outputs必须列出 prepare 写出的每一个文件包括生成的、gitignored 的MonoTool-AD-10。否则缓存命中只恢复dist/**干净树如 CI 拉取新 checkout 后恢复.nx/cache上会缺生成文件导致后续tsc:compile/lint/tests:jest因解析不到./version而失败。详见下文生成文件输出。patch-package 的 prepare 绝不能走 Nx 缓存多数包的prepare是 bob 转译lib/**→dist/**可安全缓存。但tests与tests-macos两个 e2e 应用工作区的prepare是patch-package它会改动该应用的node_modules/**属于输入≠副作用的场景tests/移动端 RN0.86.2仍为若干非 fmt 补丁运行patch-package例如firebaserules-unit-testing对应补丁文件 tests/patches/firebaserules-unit-testing5.0.0.patch移动端 RN 上游自带 fmt12.1.0没有tests/patches/react-native*.patch的 fmt 升级补丁tests-macos/RN0.78.3react-native-macos0.78.6应用 tests-macos/patches/react-native0.78.3.patchfmt12.1.0与 macos 补丁。这两类目标的 Nx 项目级prepare覆盖必须设置cache: false。设计文档给出三点理由输入 ≠ 副作用e2e 应用没有lib/**jsSource哈希在 yarn 重链接前后保持稳定热缓存恒报命中——这正是当年那个 bugyarn 重链接会重置改动根yarn把包重新链接回未打补丁的内容随后lerna:prepare若因 Nx 缓存跳过了tests:prepare/tests-macos:prepare补丁永远不再应用把patches/**加入 inputs 不够重链接后补丁文件内容未变 → 仍是缓存命中 → 仍然跳过。仓库中 tests/package.json 与 tests-macos/package.json 均已落地该覆盖prepare: patch-package加上项目级nx.targets.prepare的cache: false。根目录自身的prepare也是patch-package但它经由 Yarn 生命周期postinstallDev→yarn prepare见根 package.json 第 5-8 行运行不经过Lerna/Nx无需覆盖。安装后 Agent 对 fmt 的验证仍是强制门槛见 agent-command-policy.md。生成文件输出缓存正确性的关键prepare会产出 gitignored 的生成文件下游工具依赖它们要保证缓存回放能还原完整工作树全部必须声明为outputs生成文件由谁产出被谁消费声明位置{projectRoot}/lib/version.ts全部 19 个包build→genversion18 个包的lib源码import { version } from ./versionJest/eslint 编译lib/**共享targetDefaults.prepare.outputspackages/app/ios/RNFBApp/RNFBVersion.mapp的build:version→genversion-iosiOS 原生构建app项目级覆盖packages/app/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseVersion.javaapp的build:version→genversion-androidAndroid 原生构建app项目级覆盖只有app产出原生版本文件因此它们放在app作用域的目标覆盖中项目级outputs会替换targetDefaults.outputs所以四项要全部重列而不是放进共享默认值让卫星包免受 app 专属 glob 的污染。packages/app/package.json 的落地实现为// packages/app/package.json nx: { targets: { prepare: { outputs: [ {projectRoot}/dist/**, {projectRoot}/plugin/build/**, {projectRoot}/lib/version.ts, {projectRoot}/ios/RNFBApp/RNFBVersion.m, {projectRoot}/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseVersion.java ] } } }配套地app的build脚本是genversion --esm --semi lib/version.ts npm run build:versionbuild:version再调genversion-ios/genversion-android与文档描述一致。为什么用 outputs 而不是提交这些文件提交会导致每次版本号变更都产生一次文件 churn这些文件在每次版本提升时都会变化并重新引入文件头部# do not modify or commit横幅早已警告过的生成文件进 git坏味道。声明为缓存输出后miss 时生成、hit 时恢复且不产生被跟踪文件的变动。正确性上有三重保障三者均为 gitignored → 排除出输入哈希无自我失效恢复进lib/与ios//android/是安全的本就是可再生产物genversion-ios/-android读取的lib/version.ts在同一次prepare中更早产出、命中时一并恢复集合内部自洽。ai包特例测试夹具从构建步骤迁到 Jest 前置ai:prepare必须可缓存、确定性。设计MonoTool-AD-7把 mock 下载移出prepare、变成 Jest 前置# packages/ai/package.json - prepare: yarn tests:ai:mocks yarn run build yarn compile prepare: yarn run build yarn compile仓库中 packages/ai/package.json 的prepare已是yarn run build yarn compile符合设计。夹具vertexai-sdk-test-data*gitignored只在 AI Jest 测试运行时下载。Jest 接线约束根 jest.config.js 是单一扁平配置一个testMatch无projects无globalSetup。若直接加裸globalSetup每次yarn tests:jest都会去拉 AI mock所有套件都走网络正好重新引入 AD-7 从prepare中移除的非确定性。两个可接受的形态中基于 AI 测试检测的 gating首选更轻globalSetup在本次运行不含packages/ai/__tests__/**例如检查解析后的测试路径 /testPathPattern时直接 no-op非 AI 运行零开销projects拆分把 jest.config.js 改成projects数组、给ai单独一个带自己globalSetup的项目隔离更干净但改动更大。拉取必须快退/幂等scripts/fetch_ai_mock_responses.ts 目前虽在目标目录存在时跳过 clone但每次调用仍会执行一次网络git ls-remote --tags以计算最新 tag。设计要求增加本地 clone 快路径——若已有vertexai-sdk-test-data_*clone 则直接返回、跳过ls-remote——使重复 AI 运行完全离线只有刷新或显式 opt-in才触网。本地缓存的实际收益场景工作流效果yarn且无包变更大——postinstallDev的 prepare 走缓存回放yarn lerna:prepare且无编辑大——所有包缓存命中编辑一个lib/**文件中——只重建该包及依赖者其余回放切换分支、输入哈希相同中——跨分支缓存命中全新 clone / 清理后的dist首次无收益第二次起快缓存位于.nx/cachegitignored用nx reset清空CI 可通过actions/cache共享发布流程不共享MonoTool-AD-8——发布构建必须保持干净的发布 checkout 语义热任务缓存绝不能影响发布产物。声明映射IDE 一键跳到 lib 源码启用声明映射只涉及三处文件MonoTool-AD-5// tsconfig.packages.base.json 17 个包继承 // packages/ai/tsconfig.json 独立 // packages/vertexai/tsconfig.json 独立 declaration: true, declarationMap: true仓库的 tsconfig.packages.base.json 第 5-6 行已确认启用。bob 的typescripttsc目标会在dist/typescript/**旁产出.d.ts.d.ts.mapIDE 的 go-to-definition 将直达lib/**源码替代旧的bundler tsc --emitDeclarationOnly两步式。由于每个包的files发布时已同时带上lib源码与dist外部消费者也能解析映射——验收方式是打开一个已发布的.d.ts.map确认其sources指向随包发布的lib/*.ts且无绝对路径泄漏见 work-queue.md 的 MT2 验收标准。依赖循环 lintdependency-cruiser 守住依赖边界采用dependency-cruiser作为静态架构 lint命令为yarn lint:depsMonoTool-AD-6。根 package.json 第 20 行已落地lint:deps: depcruise --config .dependency-cruiser.cjs packages并已并入yarn lint全链路。它验证packages/*/lib/**的导入图no-circular——禁止导入环not-to-own-dist——作用域化规则lib/**不得通过相对路径../dist/**/./dist/**导入自己包的构建产物但它必须不禁止有意的枢纽 APIreact-native-firebase/app/dist/module/...——约 15 个卫星包正是靠它消费已构建的 hub该 specifier 经各包 tsconfig 的paths映射到类型桩。一条一刀切的no lib → **/dist/**规则会直接在当前正确的代码树上失败已被否决图允许清单allowlist——卫星包只允许导入react-native-firebase/app及其已发布子路径ai→auth/app-checkvertexai→ai。仓库 .dependency-cruiser.cjs 是完整的落地实现其中规则更细除上述三条外还有hub-no-internalapp作为枢纽不得导入其他内部包、satellites-only-hub卫星包动态 require 也受限crashlytics→analytics可选集成除外、ai-graph、vertexai-graph等。配置作用域必须做对否则运行不正确/慢/误报doNotFollow/exclude排除node_modules、packages/*/dist/**、packages/*/__tests__/**、packages/*/e2e/**避免把构建产物和测试当源码分析options.tsConfigenhancedResolveOptions让react-native-firebase/*路径别名可解析——否则跨包边静默不可解析MT4 允许清单形同虚设必须验证能真正检测到至少一条真实跨包边。注意配置文件在运行时动态生成tsconfig.depcruise.json基于packages/*下实际存在lib的目录枚举出全部路径别名再交给 dependency-cruiser 的tsConfig选项使用确保别名解析始终与仓库现状同步。Agent/CI 接线命令与门禁见 validation-checklist.md 与 change-authoring-workflow.md另有可选的、非 CI 的lint:deps-reportHTML用于调试。开发者 watch 与 e2e TDD hook 链明确延后的设计延后状态。当前不存在增量 dev-watch / 热重载循环。整个 watch 事件驱动重跑设计被延后到gap-analysis 前置阶段见 work-queue.md 的 MT-WATCH 条目该阶段先产出详细、经验证的分析再谈实现。本文之前的所有 prepare/cache/graph/declaration-map/lint 工作都不依赖它。下面的命令草图只是该分析的输入不是已采纳命令——草图本身带有已标注的已知缺陷。打包前提依据 running-e2e.md 的规则 #3该文件是规范所有者内循环因此是lib编辑 →dist重建 → Metro 重新打包 → 测试重跑MonoTool-AD-9。Tier A —— prepare watch 单元 TDD草图待验证# 先跑首轮再进入 watch不要依赖 --all --initialRun见注意事项 nx run-many -t prepare nx watch --all -- nx run-many -t prepare -p $NX_PROJECT_NAME监视packages/*/lib/**增量 bob 重建进dist/草图中的命令注意事项须在前置阶段解决--all --initialRun曾是 no-op bug直到 2025 年 8 月才被修复nx PR #32282须锁定/验证随附的 nx 版本升级 Lerna 到最新会带进较新的 nx见 work-queue.md 的 MT0.0 前置。首轮显式用nx run-many -t prepare不要信任--initialRun$NX_PROJECT_NAME在一次 watch 批次内多个项目变更时是逗号拼接的nx run a,b:prepare是非法命令——要改用nx run-many -t prepare -p $NX_PROJECT_NAME单元 TDDyarn tests:jest --watch -- packages/pkg/__tests__/file.test.ts同包导入解析到../lib跨包需要上游dist。Tier C —— 事件驱动的 e2e 重跑spikenx 重建完成的时间点早于Metro 完成打包所以不能链一个sleep。重跑应以 Metro 的bundle-ready 事件为触发信号来源性质HMRupdate-doneWebSocket 消息Metro HMR 协议update-start→update-done客户端可观测的更新已在应用内生效onBundleBuilt回调MetrocreateConnectMiddleware服务端的打包完成App 侧重载 hookRNFB 拥有 Jet应用内客户端可发 reloaded进程内、精确链条为lib编辑 →nx watch重建dist→ Metro 重新打包 →update-done→ 宿主向 Jet 控制面HTTP端口JET_REMOTE_PORT 1默认8091见 running-e2e.mdPOST一个重跑动作 → Jet 在应用内重新执行已加载收窄后的mocha 套件。RNFB 拥有 Jet因此在既有/launch-ready、/orchestrate-state端点旁新增POST /rerun控制动作是可行的。原生/codegen/spec 变更仍需要:build快速重跑可能需要绕过覆盖率拆除coverage-teardown握手。同一个Metro bundle-ready 信号也是未来 Appium 驱动 e2e 重跑的正确触发源——Appium 驱动 UI 而非打包无论用哪个 runnerMetro 始终是共享的bundle ready来源。Benchmark 方法论用数据守住缓存/图论主张一个 macOS 专属 shell 脚本 scripts/benchmark-prepare.sh 以3 次取中位数的方式记录耗时防止缓存/图相关的收益主张沦为口说。场景#名称前置命令A冷安装Cold install清理node_modules、.nx/cache、packages/*/dist、packages/*/plugin/buildyarnB全量重建Full rebuild在 A 之后保留node_modules清理packages/*/dist、packages/*/plugin/buildyarn lerna:prepareC无操作重建No-op rebuild在 B 之后不做任何编辑yarn lerna:prepareD单包编辑Single-package edit在 B 之后touch 一个packages/firestore/lib/*文件yarn lerna:prepare脚本会记录 pre-Nx 基线nx.json存在前跑 B/C/D与 post-Nx 数据C 和 D 正是本地缓存显威力的地方脚本在检测到nx.json存在时会打印警告提示结果不再是 pre-Nx 基线。只做 macOS 脚本对 CI 也有代表性RNFB 的 CI 大多跑在 macOS runner 上本地 macOS 中位数足以证明 CI 收益无需单独采集 Linux 数据。结果存放于易变的 work-queue.md 或 benchmark 笔记中不进本文这类持久文档。脚本内部实现reset_build_outputs、setup_cold_install、median_of_three、run_timed等与上述场景一一对应可直接阅读源码核对。与仓库落地的对照总结设计项设计文档出处仓库落地位置单一 prepare 入口 禁云prepare-and-cache § Nx local cachepackage.json 第 9 行lerna:prepareNx 本地缓存配置§ Nx local cachenx.json与文档逐字一致devDeps 声明编译边§ 包依赖图packages/ai/package.json 等 17 个包app 原生版本文件 outputs§ 生成文件输出packages/app/package.json 的nx.targets.prepare.outputspatch-package 禁缓存§ patch-package preparetests/package.json、tests-macos/package.json声明映射§ Declaration mapstsconfig.packages.base.json 第 5-6 行依赖图 lint§ Dependency-cycle linting.dependency-cruiser.cjs package.json 第 20 行lint:deps基准测试§ Benchmark methodologyscripts/benchmark-prepare.sh这套设计体现的总原则是把构建顺序、缓存键、输出声明都做成运行器可见、可验证、可度量的显式事实而不是依赖时序运气或手工脚本——从干净树到可信树一条命令yarn lerna:prepare即可完成且无改动时近乎瞬时。相关文档导航主题文档决策与已否决的备选方案architecture-decisions.md滚动排期、门禁、验收work-queue.md规范的 prepare/e2e 命令agent-command-policy.md、running-e2e.md变更循环与门禁change-authoring-workflow.md文档与提交策略documentation-policy.md【免费下载链接】react-native-firebase A well-tested feature-rich modular Firebase implementation for React Native. Supports both iOS Android platforms for all Firebase services.项目地址: https://gitcode.com/gh_mirrors/re/react-native-firebase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表