Expo monorepo 中的 bare-expo:跑通全本地 SDK 的裸原生应用与 E2E 测试工作流
bare-expo 是 Expo monorepo(apps/bare-expo)内的一个“裸工程”(bare workflow)开发载体:它不像其他应用那样从 npm 拉取发布版依赖,而是以 workspace:* 方式直接消费仓库内正在开发的全部 expo 模块,用于在真实的原生工程(Android / iOS / macOS / Web)中做集成开发、调试与端到端测试。本文以 apps/bare-expo/README.md 的 Usage 章节为骨架,结合 scripts 目录 下的启动脚本、package.json 与 app.json 的实际实现,逐条展开 pnpm android、pnpm ios、pnpm test:ios、pnpm open 与 pnpm nuke 五个核心命令背后的完整执行链路,并补充关键配置项的说明,读完即可在本地独立搭建起一套“源码即 SDK”的 Expo 开发调试环境。
一、bare-expo 的定位:把整个仓库当作 SDK 来运行
理解所有命令之前,先明确这个应用依赖的组织方式。查看 apps/bare-expo/package.json 可以发现:
expo、expo-router、expo-camera、expo-video、expo-updates、expo-dev-client、expo-dev-menu等数十个核心包全部声明为workspace:*,即指向 monorepo 内的本地源码,而不是 npm 上的发布版本;- 同时引入了
test-suite与native-component-list两个workspace:*应用包,它们为 bare-expo 提供了各模块的测试屏幕与原生组件清单页面; codegenConfig字段声明了LocalModules,jsSrcsDir指向./modules,说明仓库内正在本地开发的原生模块会被直接生成并链接进本工程;expo.autolinking配置中android.buildFromSource: [".*"]表示 Android 侧所有原生代码均从源码构建;exclude排除了expo-ads-facebook、expo-ads-admob、expo-module-template三个不参与本工程的包。
也就是说,bare-expo 的意义在于:当你在 packages/expo-xxx 里改了一行原生代码后,可以直接在这个应用里真机/模拟器验证效果,而不需要走发版流程。README 开头指向的 CONTRIBUTING.md 也说明了它在整体贡献流程中的位置。
应用本身的界面由 app/ 目录下的 expo-router 路由组织,包括 apis(各模块入口)、components(组件清单)、test-suite(测试选择、批量运行、单测运行等页面)、playground 等 Tab,配合根目录的 Playground.tsx 等文件构成完整的调试界面。
二、pnpm android:Android 模拟器一键启动全流程
README 中第一条命令是 pnpm android,其官方描述为:按需执行 npm install、构建 React Android 二进制、创建模拟器、启动 Metro 并在模拟器中打开应用。对照 package.json 中的脚本定义,它实际执行的是:
NODE_ENV="development" ./scripts/start-emulator.sh
进入 scripts/start-emulator.sh 可以看到完整的执行链路:
- 环境检测:读取
NODE_ENV(缺省为development)与ANDROID_SDK_ROOT(回退到ANDROID_HOME);若命令行带有--device/-d参数则跳过模拟器环节(用于连接真机调试)。 - 项目准备:调用 setup-android-project.sh 完成依赖安装与原生工程配置。
- 模拟器管理:
- 通过
android list avd检查是否存在名为bare-expo的 AVD,不存在时调用 create-emulator.sh 以 API 34 创建; - 若
adb devices中已有 emulator 在运行则直接复用;否则以如下参数启动模拟器(后台运行、关动画、固定端口、无快照、1GB 分区):emulator -avd "bare-expo" -no-audio -no-boot-anim -port 5554 -no-snapshot -partition-size 1024 - 随后执行 wait-for-emulator.sh 并
adb wait-for-device等待系统就绪,接着用adb shell settings put global将window_animation_scale、transition_animation_scale、animator_duration_scale三项动画缩放全部置 0(加快 UI 自动化与开发迭代体验),最后广播BOOT_COMPLETED模拟开机完成事件。
- 通过
- 启动应用:开发模式下执行
npx expo run:android --port 8081 "$@",由 Expo CLI 完成 Gradle 构建、Metro 启动与应用安装。
此外 package.json 还提供了配套的辅助脚本,可在此流程基础上做精细控制:
pnpm start:等价于expo start --reset-cache,单独以清缓存方式启动 Metro;pnpm android:clean:删除android/.gradle并执行./gradlew --configure-on-demand,用于 Gradle 状态异常时重建配置;pnpm edit:android:用 Android Studio 打开./android工程(macOS 专用)。
三、pnpm ios:iOS 模拟器一键启动
第二条命令 pnpm ios 对应脚本:
NODE_ENV="development" ./scripts/start-simulator.sh
sim 即 scripts/start-simulator.sh,脚本开头有 set -euo pipefail,任一步失败即中止;Metro 端口由第二个参数控制,缺省 8081。开发模式下的流程是:
- 执行 setup-ios-project.sh,自动完成 pod install、依赖安装与工程准备;
- 执行
npx expo run:ios --port "${port}",构建 Xcode 工程、启动 Metro 并在模拟器中打开应用。
对应的辅助命令:
pnpm clear-ios-build:rm -rf ios/build/并kill $(lsof -t -i:8081),即清除 iOS 构建产物并释放 8081 端口上残留的 Metro 进程;pnpm clear-metro:watchman watch-del-all加删除/tmp/metro-bundler-cache-*、/tmp/haste-map-react-native-packager-*,彻底清掉 Metro 的 watchman 监视与 Haste 图缓存,用于文件监听失灵的疑难场景;pnpm edit:ios:xed ./ios/BareExpo.xcworkspace打开 Xcode 工作区。
四、pnpm test:ios / pnpm test:android:面向 E2E 的测试模式
README 说明 pnpm test:ios 与 pnpm ios 相同,但“为 E2E 测试做准备”。差异全部体现在 NODE_ENV 上:
"test:ios": "NODE_ENV=\"test\" ./scripts/start-simulator.sh"
"test:android": "NODE_ENV=\"test\" ./scripts/start-emulator.sh"
在 start-simulator.sh 中,当 NODE_ENV = test 时脚本切换到 E2E 分支:
- 测试工具链准备:检测
maestro是否已安装,未安装则通过curl -Ls "https://get.maestro.mobile.dev" | bash安装 Maestro,并brew tap facebook/fb后安装facebook/fb/idb-companion(iOS 模拟器 E2E 所需的底层通信组件); - 构建检测:若
ios/build/BareExpo.app已存在则视为已构建,否则调用 start-ios-e2e-test.ts 的--build模式构建应用; - 运行测试:调用
start-ios-e2e-test.ts --test执行默认 E2E 用例。
Android 侧同理,start-emulator.sh 在 test 模式下检查 android/app/build/outputs/apk/release/app-release.apk 是否已产出,未构建则调用 start-android-e2e-test.ts 的 --build,随后以 --test 运行默认 E2E 测试。E2E 用例本体位于 apps/bare-expo/e2e/ 目录(含 Maestro 场景、junit 报告脚本等),配套工具代码如 scripts/lib/e2e-common.ts、scripts/lib/maestro-junit-report.ts 负责场景组织与报告生成。
值得注意的细节:metro.config.js 中 config.resolver.blockList.push(/^e2e/) 专门把 e2e 目录排除在 Metro 打包之外,注释说明是为了避免截图时 Metro 刷新横幅干扰自动化截屏——这体现了 E2E 基建与开发调试基建在本工程中的耦合设计。
五、pnpm open:深链直达指定测试模块
README 中的第四条命令允许在应用已在运行的前提下,通过深链直接打开 test-suite 并执行指定模块的测试:
pnpm open <ios | android> <...Modules>
# 示例:pnpm open ios Constants Crypto
# 示例:pnpm open android Random
其实现为 package.json 中的 ./scripts/deep-link.sh test-suite,即第一个实参固定为 test-suite。查看 scripts/deep-link.sh 可还原其拼链逻辑:
APP=$1 # test-suite
PLATFORM=$2 # ios 或 android
MODULES=$(join_by , "${@:3}") # 剩余参数以逗号拼接
LINK="bareexpo://${APP}/run?tests=${MODULES}"
因此 pnpm open ios Constants Crypto 实际打开的 URL 是 bareexpo://test-suite/run?tests=Constants,Crypto,分发方式按平台区分:
- Android:
adb shell am start -W -a android.intent.action.VIEW -d "$LINK" dev.expo.payments,显式指定了包名dev.expo.payments; - iOS:
xcrun simctl openurl booted $LINK,对已启动的模拟器生效。
这里的 URI Scheme 与包名并非随意:app.json 中 expo.scheme 为 bareexpo,android.package 为 dev.expo.payments,ios.bundleIdentifier 为 dev.expo.Payments,三者共同保证了深链在两端都能被正确路由到应用的 test-suite 页面。
六、pnpm nuke:测试配置脚本的“核弹”级清理
README 最后一条命令 pnpm nuke 用于“删除所有生成文件,以便重新测试配置脚本”。对照 package.json 的定义:
"nuke": "rm -rf ios/Pods/ && rm -rf ios/build/ && rm -rf android/.gradle"
它一次性移除 iOS 的 CocoaPods 产物与构建目录、Android 的 Gradle 缓存。由于 bare-expo 的整套价值就在于“每次从源码重新走一遍 autolinking、Podfile、Gradle 配置管线”,nuke 正是验证 setup-ios-project.sh、setup-android-project.sh 这类配置脚本幂等性与完整性的标准手段:核掉生成物后再跑 pnpm ios / pnpm android,可以确认从零开始的引导流程仍然可用。
七、关键配置速览:app.json 与 metro.config.js
理解这套工作流,还需要知道几个影响运行行为的配置点:
platforms: ["ios", "android", "web", "macos"]配合experiments.outOfTreePlatforms: true,允许工程在 iOS/Android 之外承接 macOS 构建(配套脚本 setup-macos-project.sh 与 scripts/fixtures/macos/patches 中的 React Native 补丁);experiments.inlineModules.watchedDirectories指向../native-component-list下的若干目录与xcodeProjectTargets: ["BareExpo"],表示这些 TS/TSX 源码目录会被内联编译进原生 Xcode 工程,用于验证 inline-modules 能力;plugins数组演示了大量 Config Plugin 的真实用法,包括expo-router、expo-app-intents(指向../native-component-list/app-intents)、expo-splash-screen、expo-font、expo-location(开启双端后台定位)、expo-notifications、expo-media-library、expo-build-properties(开启 release 混淆与预编译头)以及两个工程本地插件./plugins/withAndroidNetworkSecurityConfig、./plugins/withBenchmarkModules(plugins 目录);updates.url与extra.eas.projectId说明该工程同时接入了expo-updates的更新服务,可用于验证 OTA 更新链路。
metro.config.js 在 expo/metro-config 默认配置之上做了四处定制,均带有明确用途注释:
assetExts追加kml(供 native-component-list 中 expo-maps 的 KML 示例)与wasm(供 expo-sqlite on web);rewriteRequestUrl针对platform=macos请求剥离transform.engine=hermes参数,因为 macOS 目标尚不支持 Hermes V1 的私有属性语法;blockList屏蔽^e2e/(见第四节的 E2E 截图场景);enhanceMiddleware为 web 响应注入Cross-Origin-Embedder-Policy: credentialless与Cross-Origin-Opener-Policy: same-origin头,满足 expo-sqlite 在 web 端使用SharedArrayBuffer的要求。
八、小结
bare-expo 的价值可以概括为一句话:它是 Expo 仓库内部的“集成验证沙盒”。五个命令各司其职——pnpm android / pnpm ios 走 scripts/start-emulator.sh 与 scripts/start-simulator.sh 完成“依赖安装 → 模拟器准备 → Metro 启动 → 应用安装”的完整引导;pnpm test:ios / pnpm test:android 以 NODE_ENV=test 切换进 Maestro 驱动的 E2E 分支;pnpm open 通过 bareexpo://test-suite/run?tests=... 深链精确命中待测模块;pnpm nuke 则负责清空生成物以回归验证配置脚本。所有行为均可在 apps/bare-expo/scripts、apps/bare-expo/package.json 与 apps/bare-expo/app.json 中逐行对照源码复核。适用前提:需具备 pnpm workspaces 环境、Android SDK(ANDROID_SDK_ROOT/ANDROID_HOME)与 Xcode 工具链,open/clear-ios-build 等命令依赖 macOS 与 adb、xcrun 的本地可用性。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00