5月27日 14:25

Expo应用怎么用EAS Build完成从构建到上架的全流程?

从本地开发到用户手机,中间隔了几座山

写完一个Expo应用只是开始。要让用户真正用上它,你需要把JavaScript代码编译成原生二进制包,签名、提交应用商店、再走完审核流程——每一步都有可能卡住。Expo Application Services(EAS)就是Expo团队给出的答案:把构建、提交、更新这三件事分别交给EAS Build、EAS Submit、EAS Update来处理。

本文会从eas.json的每一行配置讲起,覆盖开发构建/预览构建/生产构建的区别、商店提交流程、OTA更新机制、环境变量管理、CI/CD集成,以及最常见的报错和排查思路。

EAS Build的核心配置:eas.json

EAS的所有构建行为都由项目根目录下的eas.json控制。运行eas build:configure会自动生成一份基础配置,但实际项目通常需要自定义。

一个典型的eas.json长这样:

json
{ "cli": { "version": ">= 13.0.0", "appVersionSource": "remote" }, "build": { "development": { "developmentClient": true, "distribution": "internal", "channel": "development", "env": { "APP_ENV": "development" } }, "preview": { "distribution": "internal", "channel": "preview", "env": { "APP_ENV": "staging" } }, "production": { "channel": "production", "autoIncrement": true, "env": { "APP_ENV": "production" } } }, "submit": { "production": {} } }

几个关键字段的含义:

  • cli.version:约束EAS CLI的最低版本,避免因CLI版本差异导致构建失败。
  • cli.appVersionSource:设为"remote"时,版本号以EAS服务器记录为准,配合autoIncrement自动递增build number。
  • autoIncrement:每次构建自动+1,防止因build number重复被应用商店拒绝。
  • channel:绑定EAS Update的更新通道,决定这个构建能收到哪个通道的OTA推送。
  • env:构建时注入的环境变量,注意这和运行时环境变量是两回事。

开发构建、预览构建、生产构建有什么区别

EAS Build的三种构建类型对应不同的使用场景,搞混了会浪费大量构建时间。

开发构建(Development Build)

json
"development": { "developmentClient": true, "distribution": "internal" }

developmentClient: true是关键标志——它会在包里内置Expo Dev Client,支持热重载和开发者菜单。这个构建只用于开发调试,体积比生产包大,性能也差。distribution: "internal"意味着包不经过商店,而是通过链接直接安装。

使用场景:开发者在真机上调试原生模块、测试推送通知等模拟器无法覆盖的功能。

预览构建(Preview Build)

json
"preview": { "distribution": "internal", "channel": "preview" }

没有developmentClient,所以是完整的 Release 模式运行,但分发方式仍然是内部的。QA团队和产品经理通常用这个构建来验收。它和生产的区别仅在于分发渠道——预览构建不提交商店,但运行时行为和生产一致。

生产构建(Production Build)

json
"production": { "channel": "production", "autoIncrement": true }

最终提交到App Store和Google Play的包。没有developmentClient,没有distribution: "internal",走的是商店分发流程。

三者之间的核心差异:开发构建包含调试工具、体积大、运行慢;预览构建是生产代码但内部分发;生产构建就是上架的最终产物。构建时间上,生产构建因为要做代码混淆和优化,通常比开发构建多花2-5分钟。

EAS Submit:怎么把构建提交到应用商店

构建完成后,下一步是提交商店。EAS Submit把这个过程简化成一行命令。

iOS提交准备

  1. App Store Connect创建应用记录。
  2. 生成App Store Connect API Key(角色选Admin或Developer),记下Issuer ID、Key ID和.p8文件。
  3. 在EAS CLI中配置凭证:
bash
eas credentials
  1. 提交构建:
bash
eas submit --platform ios --latest

--latest自动取最近一次成功构建。你也可以指定build ID:eas submit --platform ios --id xxxx

Android提交准备

  1. Google Play Console创建应用。
  2. 创建服务账号并下载JSON密钥文件。
  3. 授予服务账号必要的权限(至少需要"发布到测试轨道"权限)。
  4. 提交构建:
bash
eas submit --platform android --latest

在eas.json中预配置提交参数

json
"submit": { "production": { "ios": { "ascAppId": "1234567890" }, "android": { "serviceAccountKeyPath": "./pc-api-key.json", "track": "internal" } } }

ascAppId是App Store Connect中的应用ID,填上后提交时不再需要手动输入。track控制发布轨道:internal(内部测试)、alpha(公开测试)、beta(公测)、production(正式发布)。

EAS Update:如何做OTA更新

这是EAS最实用的功能之一。当你只改了JavaScript/TypeScript代码和资源文件,没有动原生依赖,就可以通过OTA直接把更新推到用户手机,跳过整个应用商店审核流程。

运行时版本策略

app.json中配置:

json
"runtimeVersion": { "policy": "appVersion" }

这行配置的含义:每当version字段变化,runtime version跟着变。只有runtime version完全匹配时,OTA更新才会生效。这就保证了你改了原生代码后,旧版本的应用不会收到不兼容的JS更新。

另一种策略是"fingerprint",它基于项目原生文件的内容生成指纹,精度更高但需要更频繁地构建新包。SDK 55及以后版本推荐使用fingerprint策略。

发布更新

bash
eas update --channel production --message "修复登录页闪退问题"

--channel必须和构建时绑定的channel一致,否则用户收不到。--message是更新说明,方便回溯。

渐进式发布

bash
eas update:rollout --channel production --percent 25

先推给25%的用户,观察错误率,再逐步扩大到50%、100%。这是生产环境必须有的安全网。

常见OTA问题

更新不生效:90%的原因是channel不匹配。用eas channel:list确认构建绑定的channel和发布时的--channel参数一致。

"No compatible updates found":runtime version不匹配。检查构建的runtime version和更新的target runtime version是否相同。

更新下载了但不生效:需要确认expo-updatescheckAutomatically配置,以及是否在合适的时机调用了Updates.reloadAsync()

App Store和Google Play上架流程

App Store上架

  1. 构建eas build --platform ios --profile production
  2. 提交eas submit --platform ios --latest,构建会自动上传到App Store Connect
  3. TestFlight测试:上传后自动出现在TestFlight中,可以邀请内部/外部测试员
  4. 填写上架信息:在App Store Connect中完成应用描述、截图、隐私政策URL、审核信息等
  5. 提交审核:Apple审核周期通常24-48小时,首次审核可能更长
  6. 发布:审核通过后选择"手动发布"或"自动发布"

Google Play上架

  1. 构建eas build --platform android --profile production
  2. 提交eas submit --platform android --latest,构建上传到Google Play Console
  3. 内部测试轨道:先推到internal track验证
  4. 逐步升级轨道:internal → alpha → beta → production
  5. 填写商店信息:应用描述、截图、内容分级等
  6. 发布:Google审核通常几小时到几天

两个商店的关键差异:Apple审核严格但可预期,Google审核快但有时会因不明原因拒绝。建议两个平台都先做内部测试再逐步开放。

环境变量和Secrets怎么管理

EAS提供了两层环境变量机制,搞混了会踩坑。

构建时环境变量

eas.jsonenv字段中定义:

json
"production": { "env": { "APP_ENV": "production", "API_URL": "https://api.example.com" } }

这些变量在云端构建过程中可用,用于构建脚本。但注意:它们不会打包进最终的JS bundle。

Secrets

在Expo后台(expo.dev → 项目 → Environment variables)中创建,勾选"Sensitive"标记。这些值不会出现在git记录中,适合存放API Key、签名密码等敏感信息。

运行时环境变量

要让JS代码在运行时读到环境变量,需要用react-native-dotenv或Babel宏在构建时把值内联到代码中。Expo SDK 49+推荐的做法是:

js
// 在app.config.js或app.config.ts中读取环境变量 export default { extra: { apiUrl: process.env.API_URL, sentryDsn: process.env.SENTRY_DSN, }, };

然后在代码中通过Constants.expoConfig.extra访问。这比硬编码安全,也比运行时读取可靠。

EAS Update的环境变量陷阱

eas.jsonenv定义的变量只在eas build时生效,eas update时不会读取。如果你在OTA更新中依赖某个环境变量,需要在eas update命令中显式传入:

bash
eas update --channel production --env API_URL=https://api.example.com

CI/CD集成:怎么把构建自动化

手动跑eas build容易忘步骤,接入CI/CD后一切自动化。

GitHub Actions示例

yaml
name: EAS Build and Submit on: push: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci - run: npx eas-cli build --platform all --profile production --non-interactive --no-wait env: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}

几个要点:

  • EXPO_TOKEN:在expo.dev生成Personal Access Token,存到GitHub Secrets中。这是CI认证的唯一方式。
  • --non-interactive:CI环境下必须加,否则命令会等待用户输入而挂起。
  • --no-wait:不等待构建完成,EAS构建通常5-20分钟,加了这行CI立刻结束,省CI分钟数。如果需要等构建结果再执行后续步骤(比如自动提交),去掉这个flag。

自动提交商店

构建成功后自动提交:

bash
npx eas-cli build --platform ios --profile production --auto-submit

--auto-submit会在构建完成后自动触发eas submit,使用eas.json中配置的submit profile。

自动OTA更新

对main分支的JS改动自动推送更新:

yaml
- run: npx eas-cli update --channel production --message "${{ github.event.head_commit.message }}" env: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}

完整的CI/CD流程是:push到main → 自动构建 → 自动提交商店 → 自动推送OTA更新给已有用户。

常见报错和排查方法

构建失败:Gradle daemon disappeared unexpectedly

这是OOM(内存不足)的典型表现。Android构建特别容易触发。

解决方案:在eas.json中指定更大的资源规格:

json
"production": { "resourceClass": "large" }

large规格提供更多内存,构建耗时略长但稳定性大幅提升。同时用Expo Atlas分析bundle体积,找出过大的依赖。

构建失败:None of the files exist

通常是文件名大小写问题。macOS文件系统默认不区分大小写,但EAS Build的Linux环境严格区分。本地能跑的import路径到了云端就报错。

排查方法:仔细检查import路径和实际文件名的大小写是否完全一致。

提交失败:Missing App Store Connect API Key

iOS提交需要App Store Connect API Key,而且Key的权限必须足够。常见错误是给了只读权限的Key。

解决:重新生成Key,角色选Admin,然后确认Issuer ID和Key ID没有填反。

提交失败:Google Play拒绝:Version code already used

每个版本号只能提交一次。如果之前提交了build number 1的包被拒绝,修改后再次提交必须递增build number。

解决:使用autoIncrement: true自动管理版本号,永远不要再手动填build number。

OTA更新静默失败

用户报告没收到更新,但EAS后台显示发布成功。

排查步骤:

  1. eas channel:list确认channel映射正确
  2. 检查构建的runtime version和更新的target runtime version
  3. 确认expo-updates配置了自动检查:checkAutomatically: "ON_LOAD"
  4. 在代码中添加日志:Updates.checkForUpdateAsync()查看返回结果

构建速度太慢

EAS Build每次都从零开始安装依赖和编译。减少构建时间的方法:

  1. 锁定依赖版本:用npm ci代替npm install
  2. 指定构建镜像:"image": "latest"使用预装了常用工具的镜像
  3. 减少原生依赖:每多一个原生模块就多一份编译时间
  4. 合理使用--no-wait:CI中不阻塞等待

写在最后

Expo EAS把React Native应用从构建到上架的流程拆成了三个独立环节:Build负责编译打包,Submit负责商店提交,Update负责OTA推送。理解eas.json中build profile和channel的关系是掌握EAS的关键——它决定了你的构建类型、分发方式和更新通道。

对于刚上手的团队,建议从development构建开始验证原生功能,用preview构建给QA验收,确认无误后再跑production构建提交商店。接入CI/CD后,构建和提交变成代码推送的自动后续动作,开发者只需要关注代码本身。遇到问题时,先看EAS构建日志中[stderr]前缀的输出,大部分错误的根因都藏在那里。

标签:Expo