首页
/ Expo monorepo 中的 bare-expo:跑通全本地 SDK 的裸原生应用与 E2E 测试工作流

Expo monorepo 中的 bare-expo:跑通全本地 SDK 的裸原生应用与 E2E 测试工作流

2026-09-06 18:41:56作者:翟萌耘Ralph

bare-expo 是 Expo monorepo(apps/bare-expo)内的一个“裸工程”(bare workflow)开发载体:它不像其他应用那样从 npm 拉取发布版依赖,而是以 workspace:* 方式直接消费仓库内正在开发的全部 expo 模块,用于在真实的原生工程(Android / iOS / macOS / Web)中做集成开发、调试与端到端测试。本文以 apps/bare-expo/README.md 的 Usage 章节为骨架,结合 scripts 目录 下的启动脚本、package.jsonapp.json 的实际实现,逐条展开 pnpm androidpnpm iospnpm test:iospnpm openpnpm nuke 五个核心命令背后的完整执行链路,并补充关键配置项的说明,读完即可在本地独立搭建起一套“源码即 SDK”的 Expo 开发调试环境。

一、bare-expo 的定位:把整个仓库当作 SDK 来运行

理解所有命令之前,先明确这个应用依赖的组织方式。查看 apps/bare-expo/package.json 可以发现:

  • expoexpo-routerexpo-cameraexpo-videoexpo-updatesexpo-dev-clientexpo-dev-menu 等数十个核心包全部声明为 workspace:*,即指向 monorepo 内的本地源码,而不是 npm 上的发布版本;
  • 同时引入了 test-suitenative-component-list 两个 workspace:* 应用包,它们为 bare-expo 提供了各模块的测试屏幕与原生组件清单页面;
  • codegenConfig 字段声明了 LocalModulesjsSrcsDir 指向 ./modules,说明仓库内正在本地开发的原生模块会被直接生成并链接进本工程;
  • expo.autolinking 配置中 android.buildFromSource: [".*"] 表示 Android 侧所有原生代码均从源码构建;exclude 排除了 expo-ads-facebookexpo-ads-admobexpo-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 可以看到完整的执行链路:

  1. 环境检测:读取 NODE_ENV(缺省为 development)与 ANDROID_SDK_ROOT(回退到 ANDROID_HOME);若命令行带有 --device / -d 参数则跳过模拟器环节(用于连接真机调试)。
  2. 项目准备:调用 setup-android-project.sh 完成依赖安装与原生工程配置。
  3. 模拟器管理
    • 通过 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.shadb wait-for-device 等待系统就绪,接着用 adb shell settings put globalwindow_animation_scaletransition_animation_scaleanimator_duration_scale 三项动画缩放全部置 0(加快 UI 自动化与开发迭代体验),最后广播 BOOT_COMPLETED 模拟开机完成事件。
  4. 启动应用:开发模式下执行 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

simscripts/start-simulator.sh,脚本开头有 set -euo pipefail,任一步失败即中止;Metro 端口由第二个参数控制,缺省 8081。开发模式下的流程是:

  1. 执行 setup-ios-project.sh,自动完成 pod install、依赖安装与工程准备;
  2. 执行 npx expo run:ios --port "${port}",构建 Xcode 工程、启动 Metro 并在模拟器中打开应用。

对应的辅助命令:

  • pnpm clear-ios-buildrm -rf ios/build/kill $(lsof -t -i:8081),即清除 iOS 构建产物并释放 8081 端口上残留的 Metro 进程;
  • pnpm clear-metrowatchman watch-del-all 加删除 /tmp/metro-bundler-cache-*/tmp/haste-map-react-native-packager-*,彻底清掉 Metro 的 watchman 监视与 Haste 图缓存,用于文件监听失灵的疑难场景;
  • pnpm edit:iosxed ./ios/BareExpo.xcworkspace 打开 Xcode 工作区。

四、pnpm test:ios / pnpm test:android:面向 E2E 的测试模式

README 说明 pnpm test:iospnpm 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 分支:

  1. 测试工具链准备:检测 maestro 是否已安装,未安装则通过 curl -Ls "https://get.maestro.mobile.dev" | bash 安装 Maestro,并 brew tap facebook/fb 后安装 facebook/fb/idb-companion(iOS 模拟器 E2E 所需的底层通信组件);
  2. 构建检测:若 ios/build/BareExpo.app 已存在则视为已构建,否则调用 start-ios-e2e-test.ts--build 模式构建应用;
  3. 运行测试:调用 start-ios-e2e-test.ts --test 执行默认 E2E 用例。

Android 侧同理,start-emulator.shtest 模式下检查 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.tsscripts/lib/maestro-junit-report.ts 负责场景组织与报告生成。

值得注意的细节:metro.config.jsconfig.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,分发方式按平台区分:

  • Androidadb shell am start -W -a android.intent.action.VIEW -d "$LINK" dev.expo.payments,显式指定了包名 dev.expo.payments
  • iOSxcrun simctl openurl booted $LINK,对已启动的模拟器生效。

这里的 URI Scheme 与包名并非随意:app.jsonexpo.schemebareexpoandroid.packagedev.expo.paymentsios.bundleIdentifierdev.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.shsetup-android-project.sh 这类配置脚本幂等性与完整性的标准手段:核掉生成物后再跑 pnpm ios / pnpm android,可以确认从零开始的引导流程仍然可用。

七、关键配置速览:app.json 与 metro.config.js

理解这套工作流,还需要知道几个影响运行行为的配置点:

app.json

  • platforms: ["ios", "android", "web", "macos"] 配合 experiments.outOfTreePlatforms: true,允许工程在 iOS/Android 之外承接 macOS 构建(配套脚本 setup-macos-project.shscripts/fixtures/macos/patches 中的 React Native 补丁);
  • experiments.inlineModules.watchedDirectories 指向 ../native-component-list 下的若干目录与 xcodeProjectTargets: ["BareExpo"],表示这些 TS/TSX 源码目录会被内联编译进原生 Xcode 工程,用于验证 inline-modules 能力;
  • plugins 数组演示了大量 Config Plugin 的真实用法,包括 expo-routerexpo-app-intents(指向 ../native-component-list/app-intents)、expo-splash-screenexpo-fontexpo-location(开启双端后台定位)、expo-notificationsexpo-media-libraryexpo-build-properties(开启 release 混淆与预编译头)以及两个工程本地插件 ./plugins/withAndroidNetworkSecurityConfig./plugins/withBenchmarkModulesplugins 目录);
  • updates.urlextra.eas.projectId 说明该工程同时接入了 expo-updates 的更新服务,可用于验证 OTA 更新链路。

metro.config.jsexpo/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: credentiallessCross-Origin-Opener-Policy: same-origin 头,满足 expo-sqlite 在 web 端使用 SharedArrayBuffer 的要求。

八、小结

bare-expo 的价值可以概括为一句话:它是 Expo 仓库内部的“集成验证沙盒”。五个命令各司其职——pnpm android / pnpm iosscripts/start-emulator.shscripts/start-simulator.sh 完成“依赖安装 → 模拟器准备 → Metro 启动 → 应用安装”的完整引导;pnpm test:ios / pnpm test:androidNODE_ENV=test 切换进 Maestro 驱动的 E2E 分支;pnpm open 通过 bareexpo://test-suite/run?tests=... 深链精确命中待测模块;pnpm nuke 则负责清空生成物以回归验证配置脚本。所有行为均可在 apps/bare-expo/scriptsapps/bare-expo/package.jsonapps/bare-expo/app.json 中逐行对照源码复核。适用前提:需具备 pnpm workspaces 环境、Android SDK(ANDROID_SDK_ROOT/ANDROID_HOME)与 Xcode 工具链,open/clear-ios-build 等命令依赖 macOS 与 adb、xcrun 的本地可用性。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
924
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
599
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
394