首页
/ Expo Brownfield 测试应用实战:expo-app 如何构建与分发 Android/iOS Brownfield 工件

Expo Brownfield 测试应用实战:expo-app 如何构建与分发 Android/iOS Brownfield 工件

2026-09-06 21:19:07作者:卓艾滢Kingsley

本篇以 Expo 仓库中的 brownfield 测试应用(apps/brownfield-tester/expo-app/README.md)为主体,讲解 expo-app 的用途、目录角色、运行方式,以及如何用 expo-brownfield 内置 CLI 将应用打包为可复用的 brownfield 工件(Android 的 Maven 制品、iOS 的 xcframework)。读完后,你将掌握 brownfield 模式的完整工作流:从 Metro 调试到工件构建、再到原生宿主应用消费工件的两种集成路径,并了解 CLI 各参数在源码中的实际定义。

brownfield-tester 的定位与目录角色

apps/brownfield-tester/expo-app 是 Expo 官方的 Expo Brownfield Test App,其职责有两个(见 README):

  1. 用于测试 expo-brownfield 包本身,以及它与 expo-routerexpo-updatesexpo-dev-menu 等包的集成;
  2. 作为 expo-brownfield E2E 测试的基础应用(base app)。

结合上层目录的 README 可以看到,整个 brownfield-tester 由三部分构成,三者关系是理解本文档的骨架:

目录 角色 关键点
expo-app/ React Native / Expo 应用 提供 JavaScript 源码,同时是 brownfield 工件的构建源头
integrated/ 原生 Android/iOS 应用 通过 Expo autolinking 直接集成 monorepo,构建期解析模块
isolated/ 原生 Android/iOS 应用 消费预构建的 brownfield 工件(Maven / xcframeworks),构建期不依赖 monorepo

其中 isolated/ 被明确标注为「推荐的分发方式」(the recommended distribution approach),即把 brownfield 能力以二进制工件形式交付给已有原生应用;而 integrated/ 则在其 README 中带有醒目警告:它把构建与项目根目录重定向到 ../expo-app,属于「不应复制」的临时性测试配置,仅用于 autolinking 场景的验证。

expo-app 的依赖与入口结构

package.json 可以看出,该应用的所有 Expo 相关依赖均以 workspace:* 方式引用 monorepo 内的本地包,核心依赖包括:

  • expoexpo-brownfield(brownfield 工具包)
  • expo-router(路由)、expo-dev-menu(开发菜单)、expo-splash-screenexpo-constantsexpo-deviceexpo-linkingexpo-fontexpo-imageexpo-system-uiexpo-symbolsexpo-web-browserexpo-glass-effect
  • 原生侧依赖 react-native 0.87.0、react 19.2.3,以及 react-native-reanimatedreact-native-screensreact-native-safe-area-contextreact-native-gesture-handler

入口文件 index.js 展示了 brownfield 测试的典型双注册模式:

import { registerRootComponent } from 'expo';
import { App } from 'expo-router/build/qualified-entry';
import { AppRegistry } from 'react-native';

import { CustomComponent } from './src/components';

// "main" component from expo-router
registerRootComponent(App);

// Additional custom component
AppRegistry.registerComponent('custom-component', () => CustomComponent);

这里把 expo-routerApp 注册为主组件,同时额外注册了一个 custom-component。这一设计对应 E2E 测试中验证「brownfield 宿主能否独立拉起非主入口组件」的场景,src/app/ 下的路由(apis/navigation.tsxapis/communication.tsxapis/state.tsxapis/dev-menu.tsx 等)则分别覆盖导航、原生通信、状态共享、开发菜单等功能面。

app.json 中的 plugins 配置声明了 expo-brownfield 插件及其关键参数:

[
  "expo-brownfield",
  {
    "ios": {
      "buildReactNativeFromSource": false
    },
    "android": {
      "publishing": [
        { "type": "localMaven" }
      ]
    }
  }
]
  • ios.buildReactNativeFromSource: false:iOS 构建时不重新编译 React Native 源码;
  • android.publishing 声明了 localMaven 发布目标——这正是后文 build:android --repo MavenLocal 命令能够把工件发布到本地 Maven 仓库的前提。

用 Metro 调试(Android)

README 给出的调试约束是:

若要在调试模式下通过 Metro Bundler 使用本应用,请在应用目录运行 yarn start,并且使用 DebugAll 构建类型(build type)产出的工件——该能力目前仅支持 Android

对应 package.json 中的 scripts.startexpo start)即可启动 Metro。这条限制说明 brownfield 工件中的 Debug 构建类型保留了开发服务器连接能力,而 Release 类型面向生产分发;Android 侧的 Debug/All 构建类型差异由工件中内嵌的启动逻辑控制(expo-brownfield 的 Android 插件会向 Gradle 工程注入相关配置,见 withProjectBuildGradlePlugin.ts 等插件实现)。

构建 brownfield 工件:CLI 完整命令与参数

README 中给出的最简构建命令是:

# Android
npx expo-brownfield build:android --repo MavenLocal --all --verbose

# iOS
npx expo-brownfield build:ios --release --verbose
npx expo-brownfield build:ios --debug --verbose

iOS 侧之所以要分两次执行 --release--debug,是因为宿主原生应用在 Xcode 中通常按 Debug/Release 两种 configuration 链接 xcframework,需要各一份对应构建类型的工件。

结合 isolated/README.md 给出的前置步骤,完整构建流程应为(先 prebuild 生成原生工程,再构建发布):

cd apps/brownfield-tester/expo-app

# Android — 构建并发布到 MavenLocal
npx expo prebuild --clean -p android
npx expo-brownfield build:android --repo MavenLocal --all --verbose

# iOS — 构建 xcframeworks
npx expo prebuild --clean -p ios
npx expo-brownfield build:ios --release --verbose
npx expo-brownfield build:ios --debug --verbose

npx expo-brownfield 实际执行的是 packages/expo-brownfield 包中的 CLI(其 package.json 声明 "bin": "./bin/cli.js")。从 CLI 入口源码 cli/src/index.ts 可以看到各命令的完整参数集:

build:android(构建并发布 Android 工件)

参数 含义
-d, --debug 构建 debug 变体
-r, --release 构建 release 变体
-a, --all 同时构建 debug 和 release 变体
--fused 通过 AGP Fused Library 以每个变体一个 fat AAR 的形式发布
--verbose 把 Gradle 全部输出转发到终端
-l, --library <name> 指定 brownfield 库名
-t, --task <task...> 指定要执行的发布任务,可传多个
--repo, --repository <repo...> 指定发布目标仓库,可传多个(如 MavenLocal
--dry-run 仅打印将执行的命令,不实际运行

build:ios(构建 iOS 工件)

参数 含义
-d, --debug 构建 debug configuration
-r, --release 构建 release configuration
--verbose 转发全部 Xcode 构建输出
-s, --scheme <scheme> 指定 iOS scheme 名
-x, --xcworkspace <path> 指定 Xcode workspace 路径(.xcworkspace)
-a, --artifacts <path> 指定工件输出目录
-p, --package [name] 将工件打包为 Swift Package,可附带包名
--host-provided <frameworks...> 宿主 App 已提供的 framework 列表,构建时从工件中剥离(如 SDWebImage,SDWebImageWebPCoder),避免符号冲突
--dry-run 仅打印命令

两个命令都支持 --dry-run,这是排查「CLI 到底会执行什么」时非常实用的手段。此外 CLI 还提供 tasks:android 命令,用于列出当前工程可用的发布任务与仓库,配合 -t/--task--repo 使用。

工件如何被消费

  • Android:工件发布到 Maven Local 后,宿主应用在 Gradle 仓库配置中声明 mavenLocal() 即可解析(见 isolated/README.md),打开 isolated/android/ 工程即可正常构建;
  • iOS:构建出 xcframeworks 后,可借助 E2E 脚本 add_xcframeworks.rb 把包含框架的本地 Swift Package 加入 Xcode 工程,也可以手动通过 Xcode 的 “Add Package Dependencies” 指向本地包路径。

integrated 与 isolated:两条验证路径

expo-app 生成的工件最终要回答一个问题:brownfield 能力放进一个独立的原生宿主后是否可用。仓库为此提供了两条验证路径:

  1. integrated(autolinking 路径):原生工程把项目根指向 ../expo-app,模块在构建期从 monorepo 的 node_modules 解析。其 README 记录了初始化细节:Android 侧基于 Android Studio 2025.1.3 的 Empty Activity 项目创建,并按 Brownfield Integration 指南集成 Expo 模块;由于 React Native 面向 Java 17,需移除 app/build.gradle.kts 中默认的 compileOptions,并移除 settings.gradle.ktsdependencyResolutionManagementrepositoriesMode(因为 react-native 插件会自行配置 maven 仓库)。iOS 侧基于 Xcode 26 的 SwiftUI 项目创建,且因 Swift 6 支持尚不完整,需在项目设置中将 “Default Actor isolation” 设为 “nonisolated”。
  2. isolated(工件路径,推荐):原生工程完全自包含,Android 从 mavenLocal() 解析 brownfield 库,iOS 通过本地 Swift Package 消费 xcframeworks,构建期不接触 monorepo。

这种「先工件、后消费」的结构,也解释了为什么 E2E 测试需要以 expo-app 为 base app:E2E 脚本(如 run-e2e-android.shrun-e2e-ios.sh)先构建工件,再将其安装到原生宿主应用中执行 Maestro 场景(e2e/maestro 下的 navigation.ymlstate.ymldev-menu.ymlcommunication.yml 等流程),端到端验证 expo-app 中注册的导航、状态、通信与开发菜单功能在 brownfield 环境下的行为。

小结

apps/brownfield-tester/expo-app 是验证 expo-brownfield 的核心试验场:它既是 expo-router / expo-updates / expo-dev-menu 集成的测试宿主,也是 E2E 流水线中工件的构建源头。掌握它的两条主线命令即可复现完整 brownfield 工作流——Android 用 npx expo-brownfield build:android --repo MavenLocal --all --verbose 发布 Maven 工件,iOS 分别用 build:ios --release/--debug --verbose 产出 xcframeworks;调试期则依赖 Metro 与 Android 的 Debug/All 构建类型。所有 CLI 参数均可在 packages/expo-brownfield/cli/src/index.ts 中逐条核对,--dry-runtasks:android 是调试构建流程时的可靠帮手。

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