EAS 构建
移动端工程做到最后都绕不开两件事:把 JS 和原生代码编译成能安装的二进制,把它变成商店里可下载的东西。EAS(Expo Application Services)就是 Expo 官方托管的这一段服务:云端构建(EAS Build)、提审(EAS Submit)、热更新(EAS Update)、观测(EAS Observe)等。这一章聚焦 EAS Build——eas-cli 怎么用、eas.json 怎么配、签名凭证怎么管、CI 怎么接,以及 SDK 58 带来的构建侧变化。发布与热更新见 07-EAS发布与热更新.md。
先回答一个常见质疑:我本地 npx expo run:ios 也能出包,为什么要用云端构建?
EAS 是什么,和 expo CLI 怎么分工
一句话原则:本地能跑通的不必上云,上云是为了消灭"你机器和我机器不一样"。
npx expo start/npx expo run:android|ios是 Expo CLI 的活,全在本地,不经过任何云服务。- EAS Build 把同样一条流水线搬到 Expo 提供的 macOS/Linux 构建机上跑:远端
npm ci、npx expo prebuild生成原生工程(CNG 项目甚至不需要把ios/、android/提交进仓库)、跑 CocoaPods 和 Gradle、签名、产出.ipa/.aab/.apk。
值得上云的三个理由:
- 不养一堆 Mac。团队做 iOS 构建,传统方案是维护几台 macOS 机器或自建 fastlane;EAS 构建机自带各版本 Xcode/Ruby/CocoaPods,按
image选环境即可。 - 构建环境可复现。本地偶发的 “clean 一下就好了” 在云端不存在——每次都是干净机器加受控缓存;团队协作时 Android Studio 版本差异之类的噪音直接被抹平。
- 签名凭证托管。keystore、发布证书、描述文件由 EAS 加密保管(可选自管),新同事入职不用互相传
.p12。
类比一下:CNG 之于原生工程 ≈ 构建产物之于 dist/;EAS Build ≈ “托管的 CI runner + 签名保险柜 + 产物分发页”。
截至 2026-10-20 的时点事实:eas-cli npm 最新版为 25.2.0(registry 实测);SDK 58 构建要求 Node 22 线 ≥22.13、24 线 ≥24.3 或 26+(奇数版本不支持)。定价与构建额度随账号类型不同,一律以 expo.dev/pricing 为准,本文不写具体数字。
安装与登录
官方文档推荐全局安装:
npm install --global eas-cli
# 或者不想装全局,每次用 npx(CI 里常见):
npx eas-cli build -p android --profile preview账号相关三条命令:
eas login # 浏览器登录(SDK 58 起 expo login 默认走浏览器,EAS CLI 同理)
eas whoami # 确认当前账号
eas logoutCI 上没有浏览器,用 personal access token:在 expo.dev 账号设置里生成,注入环境变量 EXPO_TOKEN,所有 eas-cli 命令会自动用它认证,无需 eas login。注意这个坑:token 属于账号而不只是项目,泄露等于把整个 EAS 交出去,团队 CI 建议用专用账号或组织级权限最小化(docs.expo.dev 的 Programmatic access 章节)。
eas init:projectId 绑定
EAS 的一切资源都挂在一个 project 下。首次使用:
eas init # 创建新 EAS 项目或链接已有项目,交互选择所属账号
eas build:configure # 顺便生成/完善 eas.json(老项目常用这条一起跑)
eas project:info # 查看当前绑定的 projectId/slugeas init 做的事是把 projectId 写进 app config 的 extra.eas.projectId。为什么 projectId 不该随意换:
- 云端构建的凭证、环境变量、渠道(channel)、构建历史全部按 projectId 存储,换 ID 等于一切从零;
- 更致命的在 OTA:
eas update:configure写入的二进制更新地址是https://u.expo.dev/<projectId>,已经装在用户手机里的老版本只认这个地址。换 projectId 后,旧客户端永远收不到任何热更新,只能等商店包覆盖。
要重置只能 eas init --id <新项目ID> --force,生产项目基本不会这么干。顺带一提:eas.json 里 profile 名字可以随便起,projectId 不行。
eas.json 全解
eas.json 放在项目根目录(monorepo 里放 Expo 项目目录),三个顶层键:
{
"cli": { "version": ">= 12.0.0", "appVersionSource": "remote" },
"build": { "development": {}, "preview": {}, "production": {} },
"submit": { "production": {} }
}注意:不少旧文章写成
buildProfiles/submitProfiles,现行 schema 是build与submit(字段全表见 docs.expo.dev/eas/json.md)。以 docs 为准。
cli 段
docs schema 列出的字段:version(semver 范围,团队里有人 eas-cli 太旧会直接报错,相当于给 CLI 版本上锁)、requireCommit(要求工作区干净才允许构建,防止"构建成果对不上任何 commit")、appVersionSource(remote 或 local,下文构建号自增详解)、promptToConfigurePushNotifications。
build 段:三档 profile 的语义
eas build:configure 生成的默认三档不是摆设,它们对应三种用途(development 档需要 expo-dev-client,见 05-Expo-Go与开发构建.md):
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"env": { "APP_VARIANT": "development" }
},
"development-simulator": {
"extends": "development",
"ios": { "simulator": true }
},
"preview": {
"distribution": "internal",
"channel": "preview",
"android": { "buildType": "apk" }
},
"production": {
"autoIncrement": true,
"channel": "production",
"env": { "APP_VARIANT": "production" }
}
}
}- development:
developmentClient: true把 Gradle 任务切成:app:assembleDebug、iOS 配置切成Debug,产物带开发者工具,永远不上商店。iOS 真机与模拟器需要两个 profile(模拟器加"ios": { "simulator": true });Android 一个.apk真机模拟器通吃。 - preview:不带 dev tools 的内部测试包,
distribution: "internal"意味着产物拿到直接安装的分享链接(必须是 apk/ipa 形态才有意义)。它是热更新预览的载体,channel字段指向哪个渠道就收哪批 OTA(详见07-EAS发布与热更新.md)。 - production:
distribution默认store,产物是上传商店用的.aab/.ipa,下载链接不可分享。商店包无法直接安装到设备,这是和 preview 的本质区别。
extends 支持 profile 间继承(平台无关),是避免四五个 profile 复制粘贴的正规手段。
profile 级常用字段
真正高频的这批,按用途分组过一遍(完整语义以 docs.expo.dev/eas/json.md 为准):
工具链与环境
node/yarn/pnpm/bun/corepack:云端构建机的版本钉。node: "22.23.3"这种写法对 CI 差异排查非常重要——本机 Node 与构建机不一致是隐形事故源,SDK 58 还改了 Node 支持矩阵(22.13+/24.3+)。image:构建机基础镜像(default/latest或具名镜像)。镜像决定了 OS 版本、Xcode 版本等没法单独指定的工具链;SDK 项目一般不用碰,EAS 会按 SDK 版本挑合适镜像,特殊需求查 build-reference/infrastructure.md 的镜像清单。ios下还有bundler/fastlane/cocoapods/ndk(Android):对应工具的版本覆盖。env:构建期间注入的环境变量。只放可以进 git 的值,密钥类应走 EAS 环境变量(见下文 secrets 小节),文档原话是"仅用于你会提交到仓库的值"。environment:取值为development/preview/production,告诉 EAS 构建时套用哪一组服务端管理的环境变量。SDK 58 起expo config/prebuild/expo-doctor在 EAS Build 上按 production 加载.env(changelog 实测条目),配合使用别搞混。
产物与分发
distribution:store/internal;android.buildType:app-bundle(.aab,默认)/apk;gradleCommand可指定任意 Gradle 任务(优先级高于前两者,慎用)。ios.simulator(模拟器.app)、ios.enterpriseProvisioning(企业签universal/adhoc)。scheme/ios.buildConfiguration:多 Xcode scheme 时必填 scheme,否则 EAS 会交互问你。buildArtifactPaths/applicationArchivePath:产物 glob 搜索路径,一般只有 custom builds 才需要动。uploadSourceMaps:设为true时把 JS source map 传上 EAS,供 EAS Observe 符号化线上 JS 报错堆栈(云端构建专属,local build 不上传)。做生产应用建议开,埋一行配置换线上可读堆栈,见07-EAS发布与热更新.md的 Observe 部分。
OTA 相关
channel:这个 profile 产出的二进制去哪个更新渠道拉 OTA。developmentClient: true时该字段无效(dev build 可以加载任意渠道的兼容更新,这正是它适合验证 OTA 的原因)。autoIncrement:构建号自增。Android 默认 bumpandroid.versionCode,iOS bumpios.buildNumber最后一段;也接受显式值"versionCode"/"buildNumber"/"version"。商店拒包的高频原因就是"重复构建号",production 档开autoIncrement: true一劳永逸。
性能
resourceClass:default/medium/large(large免费计划不提供,docs 原话)。构建慢/内存爆时先升档再查原因。cache:{ key, paths, disabled }——构建产物级缓存,成功后保存、下次构建开始时恢复;改key即整体失效。注意官方提示:node_modules 不适合放这里(缓存是远端的,下载速度约等于从 npm registry 重装)。credentialsSource:remote(默认,EAS 托管凭证)/local(用本地 credentials.json,见下文)。withoutCredentials: 允许无凭证构建(dev/debug 产物常见),默认false。config:指定自定义构建工作流(.eas/build/xxx.yml),属于 custom builds 进阶,见 docs custom-builds 章节,本文不展开。
构建号管理:remote vs local
cli.appVersionSource 决定 autoIncrement 往哪写:
remote(EAS CLI 12.0.0+ 的推荐姿势):版本号存在 EAS 服务器,构建时注入原生工程,本地 app config 里的versionCode/buildNumber会被忽略——你可以放心把它们从 app.json 删掉,git diff 干净,多人/CI 并行构建不会互相覆盖。local:EAS 直接改你项目里的数字,每次构建后要 commit,否则下次又回到旧值。CI 场景很难协调,基本只在"公司政策必须本地留档"时用。
两个配套命令:eas build:version:set(把商店里已有的版本号同步为远端起点,首次从旧项目迁移到 remote 时必做,否则从 1 开始撞商店)、eas build:version:sync(把远端值拉回本地,供本机 Xcode/Android Studio 出包时用同一版本号)。
一个与下一章强相关的限制:远端 autoIncrement 与 runtimeVersion 的 nativeVersion 策略互斥(docs app-versions.md 原文列为 Limitation);如果要用它,runtimeVersion 请用 appVersion 或 fingerprint 策略——为什么,07-EAS发布与热更新.md 展开。
缓存全景与 Gradle cache
EAS 的构建提速是分层做的,容易配混,梳理一次:
- 依赖缓存内置:npm 缓存服务器(npm/Yarn 2+ 默认启用;Yarn 1 要按 docs 的 package.json workaround)、Maven 缓存(maven-central/google/plugins-gradle)、CocoaPods 产物缓存。对应的关闭开关:
EAS_BUILD_DISABLE_NPM_CACHE/EAS_BUILD_DISABLE_MAVEN_CACHE/EAS_BUILD_DISABLE_COCOAPODS_CACHE设"1"(写在 profile 的env里)。 - 锁文件策略:默认按
npm ci/--frozen-lockfile安装;想用可变安装设EAS_NO_FROZEN_LOCKFILE=1(这也是"lockfile 与 package.json 对不上直接构建失败"的解闸阀)。 - ccache(C/C++ 编译缓存):
EAS_USE_CACHE=1同时恢复+保存,EAS_RESTORE_CACHE/EAS_SAVE_CACHE单独控制。生产构建的安全姿势见 docs caching.md:共享 token 触发构建时用户级缓存会互相污染,建议 production profile 设EAS_RESTORE_CACHE: "0"+EAS_SAVE_CACHE: "1",只由受信任的 job 产出干净缓存。 - Gradle build cache(2026-04-30 上线):设
EAS_GRADLE_CACHE=1开启。官方 changelog 的结论:Gradle 任务输出按缓存 key 复用,缓存 key 取自包管理 lockfile 的哈希;验证是否命中看构建日志 “Run Gradle” 步骤里标FROM CACHE的任务。原生模块越多的项目收益越大,与 ccache 两层叠加。 - 自定义产物缓存走
cache.paths,典型用法是把./ios/Podfile.lock加进去换取确定性构建(docs 提醒:缓存它偶尔会带来需要清缓存的构建错误,权衡使用);eas build --clear-cache是无脑重置入口。
eas build 命令族
eas build -p android|ios|all # -p 平台
eas build -e production # -e/--profile,缺省时若 eas.json 定义了 production 就用它
eas build -m "fix: 登录页闪退" # 给构建加备注,dashboard 可见
eas build --platform all --profile production --auto-submit # -s:构建成功直接提审
eas build --auto-submit-with-profile=production --profile preview # 分别指定构建/提审 profile
eas build --platform ios --local # 本机跑云端同款流水线
eas build --profile production --non-interactive --no-wait # CI 触发姿势
eas build --profile production --clear-cache
eas build --json # 机器可读输出(隐含 --non-interactive)几个值得单独说的:
- 没有
--id这个旗标(旧文常见);要操作某个构建,用eas build:view <BUILD_ID>/eas build:cancel <BUILD_ID>/eas build:download --build-id <ID>/eas submit --id <ID>,ID 从eas build:list或 dashboard 拿。 - 非交互不是环境变量而是旗标:
--non-interactive(--json自动带上)。docs 上没有EAS_NON_INTERACTIVE这种写法。前提是首次构建已在本地交互跑过(凭证、scheme 选择等配置项都已落定),CI 只是复用这些配置。 --what-to-test配合--auto-submit直接写 TestFlight 的"What to test"文案;--freeze-credentials防止非交互构建"顺手"改了凭证;ad hoc 内部分发想带上新注册的设备,用--refresh-ad-hoc-provisioning-profile。- 排队与取消:
eas build:cancel(可按-p/-e过滤)、eas build:cancel <ID>;eas build:list --status in-queue看队列状态(new|in-queue|in-progress|pending-cancel|errored|finished|canceled)。
–local:出本地包的正确姿势
eas build --local 在自己机器上跑与云端同一套流水线(含 prebuild、fastlane、凭证收集),产物的 ios/、android/ 生成方式和参数与云端逐字节一致。它的定位(docs local-builds.md):
- 调试"云端挂了本地复现不了"的构建失败——官方推荐第一现场;
- 公司政策禁止第三方 CI:整个编译都在自己基础设施上,与 EAS 的通信仅为确认项目存在和下载托管凭证。
限制要知道:一次只能一个平台(all 禁用);node/image/fastlane 等字段被忽略(用的是你本机环境,“复现 CI"与"复现本机"是两码事);不支持缓存;secret 可见性的环境变量不下发(本地自己 export);需要本机装齐 Node、Android SDK/NDK,iOS 另需 fastlane 和 CocoaPods;Windows 官方不支持本地构建(macOS/Linux 才行)。产物默认落在当前目录,可用 EAS_LOCAL_BUILD_ARTIFACTS_DIR 指定,调试时 EAS_LOCAL_BUILD_SKIP_CLEANUP=1 + EAS_LOCAL_BUILD_WORKINGDIR 保留工作目录看 Xcode 日志。
和 npx expo run:ios --configuration Release 的区别:后者是原生工具链视角——直接调本机的 Xcode/Gradle,你用什么姿势配了原生工程就是什么产物;--local 是 EAS 流水线视角,验证的是"云端这套流程在我机器上是否成立”。排查商店包问题用 --local,日常本地 release 测试用 run:* 更轻。
查看构建内容:build:inspect 等
eas build:inspect -p ios -s archive|pre-build|post-build -o ./inspect-dir:把某阶段的项目文件系统快照到本地目录,验证"prebuild 完原生工程到底长什么样"“ipa 里打进去哪些资源"这类问题。eas build:download --build-id <ID> --all-artifacts:连 Xcode 日志、产物归档一起拉下来。eas build:resign:给已有.ipa换签名重打包(比如给内部测试 ipa 加一台新设备,省一次完整构建)。eas build:dev:按 fingerprint 匹配已有的模拟器/仿真器 dev-client 构建并安装运行,找不到再触发新构建(默认选development-simulator档,配合05-Expo-Go与开发构建.md)。
凭证体系:先让 EAS 自动生成
默认 credentialsSource: "remote",首次 eas build 会交互式引导创建全部凭证,之后复用。推荐姿势就是让 EAS 生成,除非有明确理由走 local/existing 路线:
eas credentials # 交互式面板:查看/生成/删除/上传各平台凭证
eas credentials:configure-build面板里能做的事:下载凭证到本地 credentials.json(credentials.json: Upload/Download... 选项)、管理 FCM/APN key、清理过期 iOS 凭证。想让新队友也能构建,把人加进组织即可——iOS 凭证生成后,协作者不需要 Apple 开发者账号权限就能发起构建(凭证在 EAS 侧)。
iOS
三种凭证(都挂在 Apple Developer 账号下,docs app-credentials.md 的要点):
- Distribution Certificate:跟人(一个账号一套),过期不影响已上架 App,重新生成后才能再传新包。
- Provisioning Profile:跟 App,与发布证书绑定,12 个月过期——过期同样不影响线上,下次
eas build -p ios或eas credentials提示重建即可。 - APN Key(推送密钥):运行期用,撤销后推送即断;上限 2 个,换新 key 不会改变用户已有的 Expo push token。
eas credentials 的删除只删 EAS 服务器上的副本,不删 Apple 侧的——想真正清掉要去 Apple Developer 控制台。要换用已有证书(.p12/.csr 路线)见 docs 的 existing-credentials.md;本地凭证路线是 credentialsSource: "local" + 项目根 credentials.json(不是 eas-credentials.json,注意文件名)。
App Store Connect API Key 什么时候必须:CI 非交互提审(eas submit/--auto-submit)时无法交互输凭证——docs submit/ios.md 明确:非交互模式下没有预先分配的 ASC API key 会直接报 App Store Connect API Keys cannot be set up in --non-interactive mode.,且 profile 必须有 ascAppId。构建侧同理:凭证损坏需要修复时,CI 要注入 EXPO_ASC_API_KEY_PATH、EXPO_ASC_KEY_ID、EXPO_ASC_ISSUER_ID,内部分发还会用到 EXPO_APPLE_TEAM_ID、EXPO_APPLE_TEAM_TYPE(docs building-on-ci.md)。所以哪怕构建不用 ASC key,只要 CI 提审,key 就早配好;也可以在 expo.dev 的账号设置或 eas integrations:asc:connect 预先关联。
Android
首次构建自动生成 keystore,存 EAS 服务器。现状(docs app-credentials.md):Google Play 的 App Signing by Google Play 是默认流程——eas build 产出的包用 upload key 签,商店侧用另一把 app signing key 落地重签;upload keystore 丢了可以通过 Play 支持重置,app signing key 丢了应用就死了。所以 EAS 托管的 keystore 按 upload key 理解即可,eas build 不区分两者。
红线只有一条:生产 keystore 绝不进仓库(debug keystore 是唯一例外,它不上商店)。凭证与 Google 解绑类事故的处理链(下载 credentials.json → keytool -export 导 pem → 找 Google 支持换 key)docs 有完整步骤,真出事照抄。
环境变量与 secrets:eas env 家族
旧教程里的 eas secret:create 已从当前 CLI 参考中消失,现行体系是 EAS environment variables(docs eas/environment-variables.md),一条命令覆盖创建与更新:
# 单条:给 production 环境建一个构建/更新期注入的变量(第一个位置参数是环境名)
eas env:set production --name EXPO_PUBLIC_API_URL --value https://api.example.com --visibility plaintext
# 批量:把本地 .env 文件推上去 / 拉下来(默认路径 .env.local)
eas env:push production --path .env.production
eas env:pull production --path .env.local三种 visibility:plaintext(处处可见)、sensitive(仅服务端可见、CLI 打码)、secret(只有 EAS 服务器上的 job 能读,构建日志里做混淆——docs 原文举例:装私有包的 NPM_TOKEN)。scope 分 project(默认)/ account。环境名默认 development|preview|production 三个,自定义环境名是更高档计划的能力。
与构建/热更的接线:build profile 设 "environment": "production" 即套用它;eas update 用 --environment 旗标(SDK 55+ 必传,见 07)。
和 03-应用配置与插件.md 的 EXPO_PUBLIC_ 边界呼应一下,这里有个容易误解的点:eas env 的 secret 控制的是变量在服务侧的可见性,管不住"值会不会被打进客户端 bundle”。凡是最终会被用户设备上跑的 JS 读到的值(EXPO_PUBLIC_*、写进 app config 被内联进二进制的任何值),对抓包拆包的人就是明文——docs 原话:“Secrets do not provide any additional security for values that you end up embedding in your application”。真正的密钥只该留在你的后端,别指望任何客户端侧的加密手段。
config plugins 与云端构建
CNG 项目下 EAS Build 在远端跑 npx expo prebuild(--platform、--non-interactive 由引擎自动加,可用 prebuildCommand 微调),所以:
- 原生定制一律走 config plugins(见
03-应用配置与插件.md),手改ios/不进版本库的内容云端构建拿不到; - lockfile 必须与 package.json 一致——云端
npm ci/frozen 安装,不一致直接红;本地能跑不代表云端能跑,先eas build --local验证; - 想排除某些文件不参与上传,用
.easignore(docs build-reference/easignore.md);私有 npm 包、git submodule、monorepo 各有专页(build-reference 章节)。 - SDK 58 的 iOS scene-based lifecycle 意味着 prebuild 会生成
SceneDelegate.swift等新模板文件,ios/若被提交过旧版本,升级后删掉让云端重新生成(sdk58 changelog 升级指引)。
SDK 58 的构建侧新事实
以下均来自 SDK 58 changelog(2026-10-15,expo.dev/changelog/sdk-58),直接影响构建配置:
- Android release 构建默认开启 R8:模板在
gradle.properties写入android.enableMinifyInReleaseBuilds=true,用proguard-android-optimize.txt预设做 shrink/优化/混淆,包体更小、启动更快。依赖反射的库(部分序列化/事件总线/老三方 SDK)可能因缺 keep 规则在 release 包里崩,debug 一切正常的事故就是这么来的。回退方式:expo-build-properties 插件设android.enableMinifyInReleaseBuilds: false(见03-应用配置与插件.md);更该做的是给具体库补 ProGuard keep 规则而不是全局关掉。 - expo-modules-core 预编译
.so:npm 包直接带四 ABI 的 debug/release 预编译产物,不再每次从源码编libexpo-modules-core.so(过去 Expo Android 构建里最贵的一步)。官方空白项目实测:Apple M3 Pro 上四 ABI debug 全量构建约 80 秒 → 39 秒,核少的 CI 机器收益更大。要回退源码编译:expo-build-properties 设android.buildExpoModulesCoreFromSource。 - fingerprint 默认 preset 改为
balanced:升版本号、改 node_modules 内文件不再改变指纹,rebuild 被误触发的次数明显下降(dev build 复用、EAS Update 兼容判定都吃这条)。三种 preset(strict/balanced/relaxed)及与 runtimeVersion 的关系在07-EAS发布与热更新.md展开,这里只需知道:升级到 58 后你的构建复用率会变高,属于"默认更聪明了"。 - Node 版本门槛变化(22.13+/24.3+),eas.json 里钉了旧
node字段的记得同步。
构建时长预期与常见失败
正常预期(云端、冷启动、无缓存):小型空白项目单端几分钟量级,中大型 App 十几分钟到几十分钟;重复构建因 npm/Maven/CocoaPods 缓存和 ccache/Gradle cache 命中显著变快。以下高频失败按出现频率排:
- lockfile 与 package.json 不一致:云端 frozen install 直接失败。本地
npm install改依赖没带 lock 变更就推上去了,先本地重装验证,或临时EAS_NO_FROZEN_LOCKFILE=1定位(别长期带着)。 pod install相关:CocoaPods 版本与镜像不匹配、私有源缺.netrc(会绕过 CocoaPods 缓存)、缓存里的 Podfile.lock 过期。解法顺序:看 iOS 构建日志的 pod 阶段 →--clear-cache重试 → 固定cocoapods版本字段。- R8/Proguard(SDK 58 新增项):release Android 包运行期崩在反射处,debug 正常——先按上一条加 keep 规则定位库。
- fastlane 报错被吞:加
--verbose-logs或--build-logger-level trace;iOS 完整日志用eas build:download --all-artifacts。 - 内存/OOM:升
resourceClass(免费计划无 large),Android 侧配合 Gradle 内存配置。 - 环境差异:
node字段没钉版本,构建机默认与你本机不同步;以及expo config在云端按 production 模式取环境变量(SDK 58 起 NODE_ENV 由 CLI 强制),本地.env.development的值在云端根本不会生效这类"配置消失"问题,根多在此。
排障总入口:docs.expo.dev 的 build-reference/troubleshooting.md。
CI 集成
先说结论:老文章里的 expo/eas-build-action 已不存在(2026-10-20 用 api.github.com 验证返回 404,只剩第三方同名仿制仓库)。现在官方给的两条路:
路线一:EAS Workflows(Expo 自己的 CI/CD,免维护 runner)。.eas/workflows/build.yml:
name: Build
on:
push:
branches: [main]
jobs:
build_android:
name: Build Android App
type: build
params:
platform: android
build_ios:
name: Build iOS App
type: build
params:
platform: ios路线二:GitHub Actions 裸 eas-cli 步骤(docs building-on-ci.md 现行示例,expo/expo-github-action@v8 已验证为活跃官方仓库,也可不用 action 直接 npx):
# .github/workflows/eas-build.yml
name: EAS Build
on:
workflow_dispatch:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: npm ci
- run: eas build --platform all --non-interactive --no-wait--no-wait 让 CI 触发成功即收工(不计 EAS 构建时间),后续看 dashboard 或 webhook(eas webhook:create --event BUILD)。GitLab/Bitbucket/CircleCI 同理,核心都是 EXPO_TOKEN + npx eas-cli build --non-interactive --no-wait。
另有一个零配置选项:Expo GitHub App(docs build/building-from-github.md)——在 expo.dev 关联仓库后,PR 评论触发构建、自动预览构建,subdirectory 项目要配 “Base directory”,适合不想写 yml 的团队。
实战建议
发版前的配置层自检,按这个顺序过:
eas.json三档 profile 起步(development/preview/production),团队约定 profile 名即语义,extends收敛公共字段;cli.version锁最低 eas-cli 版本,requireCommit建议 production 档开。appVersionSource: "remote"+ productionautoIncrement: true;从老项目迁移先eas build:version:set对齐商店已有版本。- 凭证一律交给 EAS 托管起步,出问题再走 existing/local;CI 提审项目预先配好 ASC API key 与 Google service account key。
- 构建加速三层:内置依赖缓存(默认开)→
EAS_GRADLE_CACHE=1+ ccache → 最后才考虑resourceClass升档;自定义cache.paths只放编译产物类目录,别放 node_modules。 uploadSourceMaps: true,SDK 58 的 Observe 符号化全靠它。- 任何"本地好好的云端红",第一步
eas build --local复现,第二步eas build:inspect -s pre-build对比原生工程,第三步再怀疑玄学。 - 上云前先问一句这件事是否值得:跑 dev build 验证
npx expo run:ios更快,云端构建的价值在"别人也要这个产物"和"环境必须可复现"的时刻。