ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

React Native多端自动化打包发布:EAS+Fastlane工程化实践

React Native多端自动化打包发布:EAS+Fastlane工程化实践 从去年开始我陆陆续续接手过好几个 React Native / Expo 项目几乎每个团队的共同痛点都是同一个打包发布靠人工、靠某一个人的电脑、靠运气。发布日前一天晚上负责打 iOS 包的同学电脑没电、Xcode 版本不对、证书过期、Google Play 账号密码在另一个人手里——这种场景我经历过太多次。后来我把打包发布整体迁到 EAS Fastlane 这套组合上才真正体会到什么叫工程化终局不管是 iOS 还是 Android不管是内测分发还是商店上架一条命令或者一次 Git 推送就能全部搞定。这篇文章就把我这套多端自动化打包发布方案完整拆开讲包括为什么这么选型、EAS 和 Fastlane 各自负责什么、配置怎么写、踩过哪些坑以及后续怎么把 AI 测试和代码审查也接进来。不管你是独立开发者还是中小团队的技术负责人这套方案都有很强的参考价值。1. 把发布日从噩梦变成例行公事1.1 我经历过的手动打包惨状先说说我为什么要折腾这套东西。之前有个项目双端都要发版团队里只有一台 Mac mini 能打 iOS 包。每次发版流程是这样的开发同学合并完代码告诉负责打包的同学可以打了打包同学拉代码、切分支、装依赖、跑 bundle、开 Xcode 调签名、Archive、等上传上传完 TestFlight 或者 App Store Connect 之后还要去后台填审核资料、传截图Android 那边同样来一遍生成 AAB、签名、上传 Google Play、等审核。整个过程耗时两三个小时而且只要中间任何一步报错前功尽弃。最常见的问题就是本地 node_modules 装了半年没更新、某次升级之后 CocoaPods 版本不对、证书链里少了一个中间证书、Google Play 服务账号权限没配好。每次出问题都要花大量时间排查为什么这台电脑上能编那台电脑上不能编。后来我意识到问题的根源不在某一步操作没做好而在于整个流程过度依赖人、依赖环境。只要打包环境是某台特定电脑这个问题就永远存在。所以我把目标定成打包和发布这件事必须环境无关、人人可触发、可重复执行。EAS 和 Fastlane 就是冲着这个目标去的。1.2 先搞清楚 EAS 和 Fastlane 各自该干什么很多刚接触的人会把 EAS 和 Fastlane 搞混觉得它们都是自动化打包工具选了其中一个就行。实际上它们是不同层次的工具配合起来才是完整的解决方案。EASExpo Application Services是 Expo 推出的云端构建服务核心能力是把你的项目源码拿到 Expo 的云服务器上编译Android 产出 AAB/APKiOS 产出 IPA。它解决的是编译环境一致性问题。你在本地不管装了什么稀奇古怪的东西只要 eas.json 配好了云端每次都是干净的环境编译。Fastlane 是一个开源的自动化发布工具链核心能力是把发布过程中的所有操作脚本化。它管的事情更偏向发布动作生成和管理签名证书、给 iOS 版本号自增、上传 IPA 到 TestFlight、上传 AAB 到 Google Play、生成商店截图、提交元数据等等。简单说EAS 管把代码变成安装包Fastlane 管把安装包变成已发布的版本以及让签名这件事不再靠手动。当然两者也有功能重叠的地方比如 EAS Submit 也能上传商店Fastlane 也能在本地打包。但最顺手的分工是EAS 负责云构建Fastlane 负责签名管理和富媒体上传。后面我会展开讲这个分工。1.3 这套方案适合谁先说清楚这套方案不是所有项目都必要。如果你的项目只是个人 Demo或者一个月才发一次版手动打包虽然烦但也能忍。但如果你符合下面任意一条我认为就值得迁移团队超过 2 人iOS 打包依赖特定某台 Mac发版频率超过每周一次甚至每天一次同时维护 iOS、Android 两个平台经常需要打给测试同学的内测包而不是只发正式版证书管理混乱经常出现我的证书过期了那个证书在谁的电脑上这类问题。迁移成本其实没有想象中高尤其是 Expo 项目EAS 基本是开箱即用。哪怕是纯 React Native 裸项目也可以把 EAS 当独立的云构建服务用。Fastlane 那边主要是第一次配置 match 证书体系比较费劲配好之后一劳永逸。下面我从头到尾把每一步讲清楚。2. EAS 云端构建把我这台电脑能编变成云端永远能编2.1 EAS 到底解决了什么EAS Build 的设计思路一句话就能说清楚每次构建都从零开始在云端一个干净的容器里执行安装依赖 → 生成原生工程 → 编译 → 签名全流程。它不像在本地打包那样这次成功可能是因为上次缓存了什么而是保证同样的代码和配置在任何时间、任何人触发产出的包是一致的。对于 Expo 项目来说EAS 有个更狠的优势它连原生工程都不需要你维护。你在本地可能从来不需要打开 Xcode 或者 Android Studio只需要维护好 app.json 和 eas.jsonEAS 会帮你生成对应的原生工程并完成编译。这对不熟悉原生开发的团队来说省掉的功夫是巨大的。EAS Build 还直接集成了签名能力。iOS 证书、Android 签名密钥都可以上传到 EAS 的凭据管理系统构建的时候自动使用。也就是说连证书装在谁的钥匙串里这种问题也被云端接管了。2.2 eas.json 配置拆解EAS 的核心配置在项目根目录的eas.json。我先给一份我目前在生产环境用的配置再逐段解释为什么这么写{ cli: { version: 8.0.0, appVersionSource: remote }, build: { development: { developmentClient: true, distribution: internal, channel: development }, preview: { distribution: internal, channel: preview, android: { buildType: apk } }, production: { distribution: store, channel: production, autoIncrement: true } }, submit: { production: {} } }这里有几个关键点developmentClient: truedevelopment 这个 profile 是给开发调试用的需要配合 Expo Go 的 dev client 使用产出的是开发版应用方便连本地调试服务。distribution: internalpreview 和 development 都走内部分发意味着不需要走商店审核可以直接给测试同学装。buildType: apkpreview 在 Android 上打 APK 而不是 AAB。因为内测分发用 APK 最方便而正式上架 Google Play 需要 AAB。两个 profile 分开打各干各的。autoIncrement: trueproduction 构建时版本号自动递增不用每次手动改。这个非常重要因为 iOS 上传同版本号会直接报错。channel 的概念也顺便说一下。Expo 的更新服务可以按 channel 区分不同环境比如 development 环境连的是开发服务器production 环境走 OTA 更新。构建时指定 channel相当于告诉 EAS这个包是给哪个环境用的后续发布 JS 更新时也能精准投递。2.3 环境变量与敏感信息处理构建过程不是只要代码就能跑很多项目需要 API 地址、第三方 key、甚至一些私有仓库的访问凭证。EAS 提供了多层级的配置方式写在eas.json里的env字段适合所有环境都一样、且不敏感的信息写在项目根目录.env文件会被.gitignore忽略EAS CLI 构建时如果你加了--profile参数它可以把本地的.env传给云端EAS 后台的 Secrets 面板适合存真正的敏感信息比如 API key、上传凭证一旦创建后不会再明文显示。我实际的项目中API 地址这类按环境区分的配置放在app.config.ts里读取通过EXPO_PUBLIC_前缀暴露给客户端export default { name: MyApp, slug: my-app, extra: { apiBaseUrl: process.env.API_BASE_URL, enableAnalytics: process.env.ENABLE_ANALYTICS true } };构建时在 EAS Secrets 里配好API_BASE_URL、ENABLE_ANALYTICS云端构建就会自动读取代码里也拿得到。这样做的好处是本地开发、内测包、正式版用的是同一套代码只是环境变量不同不会再出现测试环境好好的生产环境崩了这种低级问题。2.4 EAS 云端构建免费吗这个问题很多人问我直接说结论EAS Build 有免费额度但比较有限团队项目大概率要付费。截至我写这篇文章的时候EAS 的免费计划每月赠送一定数量的构建时长个人项目偶尔打几个包完全够用但如果每天都触发构建、还要同时打 iOS 和 Android免费额度很容易用完。EAS 的付费档主要有两个维度构建时长和并发数。免费计划并发基本等于 1也就是说一个构建没跑完另一个要排队。团队场景下这个排队很影响效率。我的建议是个人项目先用免费额度真不够再升级团队项目直接上付费档把等待构建的时间成本折算一下你会发现这笔钱花得很值。另外安卓构建走的是 Linux 容器速度快iOS 构建走的是 macOS 容器速度慢一些计费也按照实际时长来。控制预算的一个技巧是内测包不要频繁打 production profile用 preview 就够了既能省时间也省钱。3. Fastlane签名、内测、商店资料这些脏活累活3.1 Fastlane 在整条流水线中的位置如果说 EAS 承包了编译那 Fastlane 承包的就是编译之外几乎所有事。以 iOS 为例一个正式版发布背后至少有这些动作创建/更新 App ID生成和管理 Distribution 证书创建/更新 Provisioning Profile版本号自增Archive 并导出 IPA上传到 TestFlight上传截图和元数据提交审核。如果这些全部手动做哪怕 EAS 已经帮你把包打出来了后面的上传和提交资料仍然要耗费大量时间。Fastlane 的价值就是把这一长串动作缩减成一个命令fastlane beta或者fastlane release。在 EAS Fastlane 的架构里我通常是这样安排的EAS Build 出包EAS Submit 做基础上传Fastlane 负责更复杂的动作——比如带 HTTP 头文件上传、特殊签名的处理、灰度发版的调用、以及本地跑 UI 测试。相当于 Fastlane 是那个兜底的瑞士军刀凡 EAS 没覆盖到、覆盖得不灵活的场景都由它补上。3.2 match 管证书告别证书又过期了iOS 证书管理是所有 iOS 开发者的噩梦Fastlane 官方后来专门做了match来解决这个问题。它的原理是把证书和描述文件加密后存到一个 Git 仓库里所有需要的人拉取这个仓库按需自动安装到本地钥匙串。我第一次配置 match 的时候也觉得麻烦但配完之后发现这才是正道。具体步骤创建一个私有的 Git 仓库比如ios-certificates项目里执行fastlane match init填仓库地址执行fastlane match appstore创建 App Store 分发证书执行fastlane match development创建开发证书。之后任何人拉完代码执行fastlane match development证书和描述文件就自动装到本地了。不再有人问证书在谁电脑上。match 的加密密码也要放进团队的密钥管理里否则新人入职还是没法自己拉证书。一个非常重要的经验在 EAS 上构建 iOS 包时你可以用 EAS 自己管理的证书也可以让 EAS 使用你 Fastlane match 仓库里的证书。如果团队已经有一套 match 体系建议直接用credentialsSource: remote配合appleTeamId配置让 EAS 读取你上传的证书而不是在 EAS 后台再维护一套。两边统一使用同一套证书能避免EAS 打出来的包签名和本地 Fastlane 上传的不一致这种诡异问题。3.3 lane 设计与常见场景Fastlane 的核心概念是 lane可以理解成一个命名好的自动化步骤序列。我项目的fastlane/Fastfile大概是这样的platform :ios do desc 上传到 TestFlight lane :beta do increment_build_number(xcodeproj: ios/MyApp.xcodeproj) build_app(scheme: MyApp, export_method: app-store) upload_to_testflight end desc 提审 App Store lane :release do beta upload_to_app_store(skip_metadata: true, skip_screenshots: true) end end platform :android do desc 上传到 Google Play 内测轨道 lane :beta do gradle(task: bundleRelease) upload_to_play_store(track: internal) end end这里要特别提醒几点increment_build_number必须在打 IPA 之前执行iOS 的 build number 每次上传都要比之前大否则 TestFlight 会直接拒绝。Android 侧如果 EAS 已经帮你把 AAB 打出来了其实 Fastlane 不需要重新跑 gradle直接upload_to_play_store指向那个 AAB 文件即可。反过来如果你用 Fastlane 全流程本地打包那gradle这个 lane 是必须的。skip_metadata: true和skip_screenshots: true在初期配置时强烈建议加上否则 Fastlane 会尝试把本地没有的截图和描述文件推到商店直接报错。Fastlane 还有一个常被忽略的价值它有一套统一的预检机制。比如fastlane beta执行前会检查项目里有没有未提交的改动、证书是否匹配、版本号是否合法。这些检查虽然简单但能避免很多低级失误。4. 双引擎协作Android/iOS/内测/上架一次跑通4.1 整体流程设计说了这么多落地的时候到底怎么串起来我把我现在维护的一个项目完整流程画个文字版开发者把代码推送到 Git 仓库CI我用的是 GitHub Actions也可以用自己的 CI检测到打了v*的 tag触发流水线流水线里调用eas build --platform all --profile productionEAS 在云端分别打 iOS 和 Android 的正式包构建成功后EAS 自动触发提交或者 CI 调用eas submit -p ios和eas submit -p android上传商店内测场景走另一个分支推送代码到develop分支时CI 自动调用eas build --profile preview生成内测包并把下载链接通过 webhook 发到企业微信/钉钉/Slack 群里。这里 EAS 和 Fastlane 的分工是构建、基础上传归 EAS如果还需要更复杂的逻辑——比如上传前先跑一轮自动化测试、或者在 app store connect 里设置审核语言、或者失败时发通知——这部分我用 Fastlane 来写。你也可以把 Fastlane 嵌入到 CI 脚本里构建完成后执行一个fastlane upload_and_notifylane把上传和通知一起做掉。有一个细节值得提EAS Build 的输出可以作为一个产物传给 Fastlane。比如在 GitHub Actions 里eas build完成后拿到的构建 ID 和下载 URL可以让你直接用fastlane的 lane 去下载这个包再上传到其他平台。这就是我说的双引擎协作两边不是竞争而是接力。4.2 版本号统一管理多端自动化发布里版本号管理是最容易翻车的地方。iOS 和 Android 的版本体系看着像其实完全不是一回事iOS 有 CFBundleShortVersionString展示版本号如 2.1.0和 CFBundleVersion构建号同一版本内每次上传必须递增Android 有 versionName展示版本号和 versionCode整数每次上传必须递增。手动管这三四个数字几乎必出问题。我在 EAS 里的做法是开启appVersionSource: remote让 EAS 根据构建时间自动生成递增的构建号同时在配置里用autoIncrement让 EAS 每次基于上一个云端版本号自增。这样本地完全不用改任何配置文件云端就是版本号的事实来源再也不会出现两个人同时打了同一个构建号的尴尬。Fastlane 侧也可以做同样的管理increment_build_number和increment_version_code会自动读取现有版本号并 1。如果你走的是EAS 打正式包Fastlane 打测试包的混合路线建议把两个工具的版本号规则统一用脚本在 CI 里生成一个全局版本号文件EAS 和 Fastlane 都从同一个文件读取双端永远一致。4.3 与 CI 集成触发自动化EAS 本身不做 CI它只负责构建Fastlane 也不做 CI它只负责执行动作。真正把提交代码 → 自动打包 → 自动发布串起来的是 CI 流水线。我用 GitHub Actions 的配置片段例子name: Build Release on: push: tags: - v* jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 20 - run: npm ci - run: npx eas-cli build --platform all --profile production env: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}EXPO_TOKEN是 Expo 后台生成的访问令牌放进 CI 的 secrets 里之后CI 机器就能以你的身份触发构建。构建完成后你可以继续加一步用eas submit上传或者在 EAS 后台开启 auto-submit 功能自动上传。这里我给一个实用建议内测包的触发不要用 tag直接用分支更合理。因为内测包频率高、不需要严格打版本号推一次代码就发一个链接给测试体验会好很多。正式包才用 tag 触发保证每次发版都有明确的代码基线。5. 实战排错我在迁移过程中踩过的坑5.1 EAS 缓存污染导致构建结果不一致EAS 为了加速构建会默认开启缓存包括 npm/yarn 的依赖缓存和 CocoaPods 的缓存。听起来是好事但实际坑了我一次某次我改了package.json里的依赖版本本地跑得好好的EAS 云端构建却一直用旧依赖导致产出的包里有旧 bug。排查了很久最后靠eas build --clear-cache解决了。从此之后我的策略是日常内测包可以依赖缓存加速但每次发正式版之前强制清一次缓存。在 production profile 对应的构建命令里加一个参数或者在 CI 构建 yaml 里显式传入--clear-cache确保正式包永远是从零构建的。慢个一两分钟换来的是确定性值。5.2 Fastlane match 与 EAS 凭据的冲突这个坑我印象很深。最早我们团队用 Fastlane match 管理证书后来接入 EAS我顺手在 EAS 后台又生成了一套新证书。结果某次内测本地的 Fastlane 上传 TestFlight 成功了EAS 构建出的包却一直报签名错误。最终排查发现两套证书体系同时存在EAS 用它自己的证书签名但这个证书不在我们 match 仓库里导致真机安装和验证信息对不上。解决方案就是把凭据来源统一。我在eas.json里指定了{ build: { production: { ios: { credentialsSource: remote } } } }并确保 EAS 后台的 iOS 证书就是从 match 仓库同步过来的那套。具体的操作路径是EAS 登录后在项目凭据页面选择使用已有证书然后填入从 match 仓库导出的p12和 profile 文件。统一之后本地 Fastlane 上传、云端 EAS 构建、App Store Connect 验证三者用的都是同一套证书再也没出过签名问题。5.3 免费额度的误用与配额管理前面讲了 EAS 有免费额度但我见过不少团队把免费额度用光之后直接在 CI 日志里看到一堆build has been queued错误然后一脸懵。这里有个常见误区默认的eas build命令会以当前登录账号的身份创建构建而免费账号的并发是 1构建多了就排队。如果 CI 里同时触发了 iOS 和 Android 两个构建免费账号下 Android 只能等 iOS 完成才开始耗时直接翻倍。我的建议是团队使用付费计划前先在eas.json里给不同 profile 设定好resourceClass。内测包用普通档位就够正式包换成更强的档位保证编译速度。另外设置一个构建触发冷却机制比如同一个分支在 30 分钟内重复推送只打一次包避免无效构建烧配额。5.4 内测链接失效与 OTA 更新的叠加问题还有一个容易被忽视的坑EAS 打出来的内测包如果项目的 Expo 更新服务开着用户首次启动时可能会收到 OTA 更新导致实际运行的 JS 代码和打包时的不一致。测试同学反馈我装的就是最新包啊怎么还是旧功能很多时候都是这个原因。解决方法是内测包在previewprofile 里把channel指到一个专用的 preview 频道并且确保这个频道不发 OTA 更新正式包走 production 频道。这样测试装的是真正的包内代码不会和 OTA 混在一起排查不清。6. 从能打包到工程化终局AI 测试与代码审查6.1 为什么打包自动化只是起点很多人以为打包发布能自动化工程化就到头了。实际上打包自动化解决的只是变更如何安全地到达用户这个链路的一半。另一半是怎么保证这次变更真的没问题。手动测试在小项目里能撑住但一旦发版频率上来了测试就成了瓶颈。更别提有些变更——比如改了一个全局样式、调了一个底层网络库——影响面之大人工回归根本测不全。所以当我把 EAS Fastlane 这套构建发布链跑通之后我立刻开始看下一层的工程化测试自动化和代码审查自动化。这一步做好了整个流水线才算闭环提交代码 → 自动审查 → 自动测试 → 自动构建 → 自动发布全程无人值守。6.2 AI 自动生成测试用例测试用例怎么来传统思路是让开发自己写但大部分业务代码的测试用例都是重复模板比如接口请求成功返回数据接口失败弹出提示。这类用例完全可以交给 AI 生成。现在有一些平台已经能把根据代码变化自动补测试用例做成日常功能比如 Harness 这类工程化平台里就集成了 AI 测试生成能力。我在实际项目中尝试过一个流程在 PR 提交后CI 里跑一个基于 AI 的测试生成器它会读取这次 diff 涉及的函数和组件自动生成对应的单元测试和冒烟用例然后直接提交到分支上。开发者只需要 review 生成的用例是否合理再点击合并。测出来不只是覆盖率数字变好看了而是真的能拦住一些低级回归。给一个我自己的判断标准AI 生成测试用例最适合的是那些纯逻辑、输入输出明确的模块比如工具函数、状态管理、接口数据转换层。UI 层面的测试AI 生成的用处没那么大还是需要写一些端到端的关键路径用例来兜底。想无脑把全部测试都丢给 AI目前不现实但把 60% 的重复用例交给 AI是完全做得到的。6.3 自动代码审查代码 review 是保证代码质量的另一个关键环节。人工 review 的问题是耗时、依赖个人经验、且容易在发版前因为时间紧张而流于形式。AI 代码审查工具可以做到的是在开发者提交 PR 后几秒钟内自动拉取 diff检查明显的反模式、安全隐患、性能问题、未处理的错误分支等等。我在团队里推行的流程是AI 审查先过一遍给出问题列表和修改建议开发者处理完机器查得出的问题之后再交给人工 reviewer 去关注业务逻辑、架构合理性这些机器暂时不擅长的地方。这样人工 review 的负担大幅下降质量反而提升了因为人工可以把精力集中在真正需要人脑判断的问题上。说实话AI 代码审查刚开始推的时候组里有人抵触觉得又多了一个挑刺的。但用了两个月之后大家普遍承认它确实能抓到一些容易漏掉的问题比如空指针、资源未释放、安全组配置错误这些。重要的是把它定位成助手而不是法官建议和结论都需要人来判断。6.4 把这些接进现有流水线如果你已经搭好了 EAS Fastlane 的构建发布链路把 AI 测试和代码审查接进来其实非常顺。以 GitHub Actions 为例基本上就是往现有流水线里加两个 jobreviewjob在 PR 创建和更新时运行调用 AI 审查 API把结果以评论形式贴在 PR 下面testjob在 push 和 PR 时运行先用 Fastlane 执行本地的自动化测试 lane或者跑 jest / detox再让 AI 针对 diff 生成补充用例合并前全部通过才允许进buildjob。这样你就得到了一个完整的工程化闭环。之前打包发布自动化只是让最后一步变得松快加入 AI 测试和审查之后整个变更从提交到上线每一步都有工具兜底人的角色变成了决策者而不是干活的。我对工程化的理解也在不断变化。最初我觉得能用脚本把打包发布跑起来已经很厉害后来发现签名管理、版本号、环境变量这些细节才是决定自动化能不能长期跑下去的关键再后来发现有了稳定的构建和发布管道之后把 AI 测试和代码审查接进来是一件水到渠成的事。现在再回看那段凌晨发版的经历我只想说尽早把规则和工具定好人才不用去当流程里最不稳定的那个环节。
返回列表