Product • Development • React Native • 2025年11月13日 • 読了10分
Keith Kurak Engineering
EAS WorkflowsがGitHub Actionsをどのように補完し、React Nativeチームがより高速にビルドし、よりクリーンなCI構成を維持できるようにするかを説明します。
概要
EAS Workflowsについて開発者と話すと、他のCI/CDソリューションが必ず話題にのぼります。既に多くの強力なCI/CDサービスが存在し、多くの開発チームはそれらに投資済みであるため、重要なプロセスを変更するのは大きな決断です。開発者が知りたいのは主に2点です。
- EAS Workflowsは他のソリューションと何が違うのか?
- 既存のCIランナーにEAS Workflowsをどう統合できるのか?
GitHub Actionsは自然に比較対象になります。EAS Workflows自体はGitHubアプリ統合を持ち、GitHub Actionsと同じGitHubイベント(例:プルリクエストの更新、ブランチのマージ)からワークフローを実行できます。既にGitHub Actionsを試している、あるいは本番で多数のactionsを稼働させている場合、EAS Workflowsの導入を検討している可能性が高いでしょう。
EAS WorkflowsはReact Nativeアプリ向けに設計されたCI/CDサービスで、EAS Build、Update、Submitと相性の良い組み込みジョブを最小限の設定で提供します。多くのモバイル中心のプロセスでは他のCIソリューションの代替として使えますが、同時に併用することも可能です。現在のCI/CDをWorkflowsと組み合わせることで、署名処理を簡素化し、ビルドを高速化し、重要なジョブだけを実行することで、最終的により高速で柔軟、かつ保守しやすいパイプラインを作成できます。
ここではEAS Workflowsが提供するものを理解し、GitHub Actionsと比較/対比しつつ統合方法を見ていきます。まずReact Nativeアプリ用の簡単なGitHub Actionsから始め、その等価物がEAS Workflowsでどうなるかを見て、次に両者を混在させながらWorkflowsを最も有用な箇所で使い続ける方法を示します。
シンプルなビルドワークフロー
PRをmainにマージするたびにAndroidとiOSのプレビュービルドを作成してQAが変更をテストできるようにしたいとします。これはGitHubイベントに基づいてトリガーする必要があり、既にGitHub Actionsを使えるので、まずは単純なGitHub Actionを書くのが合理的です。
name : Build iOS and Android on merge to main
on :
push :
branches :
- main
jobs :
build-android :
name : Android Release Build
runs-on : ubuntu - latest
env :
ANDROID_KEYSTORE_BASE64 : $ { { secrets.ANDROID_KEYSTORE_BASE64 } }
ANDROID_KEYSTORE_PASSWORD : $ { { secrets.ANDROID_KEYSTORE_PASSWORD } }
ANDROID_KEY_ALIAS : $ { { secrets.ANDROID_KEY_ALIAS } }
ANDROID_KEY_PASSWORD : $ { { secrets.ANDROID_KEY_PASSWORD } }
steps :
- name : Checkout repo
uses : actions/checkout@v4
- name : Setup Bun
uses : oven - sh/setup - bun@v1
with :
bun-version : latest
- name : Decode Android keystore
run : |
echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > android/release.keystore
- name : Write signing config gradle.properties
run : |
cat >> android/gradle.properties <<EOF
MYAPP_UPLOAD_STORE_FILE=release.keystore
MYAPP_UPLOAD_KEY_ALIAS=${ANDROID_KEY_ALIAS}
MYAPP_UPLOAD_STORE_PASSWORD=${ANDROID_KEYSTORE_PASSWORD}
MYAPP_UPLOAD_KEY_PASSWORD=${ANDROID_KEY_PASSWORD}
EOF
- name : Set up JDK
uses : actions/setup - java@v4
with :
distribution : temurin
java-version : "17"
- name : Set up Android SDK
uses : android - actions/setup - android@v3
- name : Assemble Release APK/AAB
working-directory : android
run : |
android/gradlew :app:bundleRelease --stacktrace
- name : Upload Android artifact
uses : actions/upload - artifact@v4
with :
name : android - release - aab
path : android/app/build/outputs/bundle/release/ *.aab
build-ios :
name : iOS Release Build
runs-on : macos - latest
needs : build-android
env :
MATCH_PASSWORD : $ { { secrets.MATCH_PASSWORD } }
APPLE_ID : $ { { secrets.APPLE_ID } }
APP_STORE_CONNECT_API_KEY_ID : $ { { secrets.APP_STORE_CONNECT_API_KEY_ID } }
APP_STORE_CONNECT_API_ISSUER_ID : $ { { secrets.APP_STORE_CONNECT_API_ISSUER_ID } }
APP_STORE_CONNECT_API_KEY_BASE64 : $ { { secrets.APP_STORE_CONNECT_API_KEY_BASE64 } }
steps :
- name : Checkout repo
uses : actions/checkout@v4
- name : Setup Bun
uses : oven - sh/setup - bun@v1
with :
bun-version : latest
- name : setup - cocoapods
uses : maxim - lobanov/setup - cocoapods@v1
with :
podfile-path : myApp/Podfile.lock
- name : Fetch signing assets with fastlane match
working-directory : ios
env :
MATCH_PASSWORD : $ { { secrets.MATCH_PASSWORD } }
APPLE_ID : $ { { secrets.APPLE_ID } }
run : |
bundle exec fastlane match appstore --readonly
- name : Build iOS archive / ipa
working-directory : ios
env :
APP_STORE_CONNECT_API_KEY_ID : $ { { secrets.APP_STORE_CONNECT_API_KEY_ID } }
APP_STORE_CONNECT_API_ISSUER_ID : $ { { secrets.APP_STORE_CONNECT_API_ISSUER_ID } }
run : |
bundle exec fastlane ios build
- name : Upload iOS artifact
uses : actions/upload - artifact@v4
with :
name : ios - release - ipa
path : ios/build/ *.ipa
このやり方自体は複雑ではないように見えますが、すぐに複雑さが増します。驚くことではありませんが、多くの複雑さは署名認証情報まわりに集中します。Androidキーストア用に複数の環境変数を設定する必要があり、Apple署名をFastlane Matchで簡略化したとしても、Match用の別リポジトリを用意する必要があります。
少しEAS Buildを加える
長い間、開発チームはGitHub ActionsからEAS Buildを呼び出してきました(Expoチーム自身もその例があります)。GradleやFastlaneのステップをEAS Buildの呼び出しに置き換えてみましょう:
name : EAS Build on merge to main
on :
push :
branches : [ main ]
jobs :
trigger-eas-builds :
runs-on : ubuntu - latest
env :
EXPO_TOKEN : $ { { secrets.EXPO_TOKEN } }
steps :
- uses : actions/checkout@v4
- name : Setup Node & Bun
uses : actions/setup - node@v4
with :
node-version : "20"
- run : |
bun install
- name : Kick off Android and iOS builds
run : |
npx eas build \
--platform all \
--profile preview \
--non-interactive \
--no-wait
この方がはるかに短くなります。EAS BuildはExpoプロジェクトに典型的なprebuild、Gradle、Fastlaneなどのコマンドをすでに適切に設定済みです。しかし、複雑さの大きな削減はビルド認証情報の管理にあります。複数の環境変数やFastlane Match設定を、EAS Buildを呼び出すための単一のEXPO_TOKENに置き換えました。ビルド認証情報はすでにEASに保存されており、eas credentialsでアップロードするか、手動で初回ビルドを呼び出すと自動生成できます。
上記のシンプルなワークフローはEAS Buildを起動して終了します(ビルドが完了するのを待ちません)。これはCI分数の観点で最も効率的な方法です。ただし、ビルド完了を待ってからカスタムスクリプトで結果を処理することもできます:
npx eas build --platform android --profile preview --json > build.json
BUILD_URL=$(jq -r '. [0].artifacts.buildUrl' build.json)
echo "Android build URL : $BUILD_URL"
この方法はGitHub ActionsのCI分数を消費しますが、設定と保守の少なさ、EAS Buildがfast M4 Proワーカー等でReact Nativeビルド速度を最適化している点を考えると、しばしば有益なトレードオフになります。
EAS Workflowsへの完全移行
この時点で、我々のGitHub Actionが行っているのはmainブランチへのpushでビルドをトリガーすることだけです。リポジトリをExpoのGitHubアプリに接続すれば、同じトリガーをEAS Workflowに追加でき、GitHub Actionsを単なる中継として使う必要がなくなります:
name : Build preview on main push
on :
push :
branches : [ main ]
jobs :
build_ios :
type : build
params :
platform : ios
profile : preview
build_android :
type : build
params :
platform : android
profile : preview
これにより、Botユーザーを作成してEXPO_TOKENを渡す必要や、別サービス上で追加のCI分数を消費する必要がなくなります。iOSビルド自体はEAS上で行われるため、GitHub ActionsでMacランナーを使う必要もありません。
Expo最適化された強力なワークフロー
ビルドを行うためのEAS Workflow YAMLは短くシンプルですが、GitHub Actionsからビルドを呼び出すこと自体が大幅に複雑だったわけではありません。EAS Workflowsが本当に得意とするのは、Expoアプリ固有の特性に基づいてジョブを条件付きで実行する必要がある場合です。
基本的なbuildやupdateジョブに加えて、EAS Workflowsには以下のような組み込みジョブがあります:
- ネイティブ構成を一意に識別するハッシュを生成する(fingerprint)
- 既存のビルドに新しいJavaScriptを適用する(repack)
- シミュレータ上でMaestroのE2Eテストを実行する(maestro)
数行のYAMLだけで強力なワークフローを構築し、テストやデプロイを高速化できます。
BuildかUpdateか
多くのReact Nativeアプリと同様に、プルリクエストの大半はネイティブコードではなくJavaScriptの変更であることが多いです。現在のワークフローがmainへのすべてのコミットでフルビルドを行っているなら、native fingerprintを使ってネイティブコードが変更された場合にのみビルドするように改善できます:
name : Build or update preview version
on :
push :
branches : [ main ]
jobs :
fingerprint :
environment : preview
type : fingerprint
android_get_build :
needs : [ fingerprint ]
type : get - build
params :
fingerprint_hash : $ { { needs.fingerprint.outputs.android_fingerprint_hash } }
platform : android
profile : preview
android_update :
needs : [ android_get_build ]
if : $ { { needs.android_get_build.outputs.build_id } }
type : update
params :
channel : preview
platform : android
android_build :
needs : [ android_get_build ]
if : $ { { !needs.android_get_build.outputs.build_id } }
type : build
params :
platform : android
profile : preview
iOSについても同様に get build -> update / build のステップを繰り返します
EASは直近のビルドとfingerprint(各ビルドで計算される)を既に持っているため、このデータはEAS Workflow内で利用可能です。あとは望む形でそれらをつなぐだけです。
fingerprintとrepackによる高速E2Eテスト
同じ原則(ネイティブfingerprintに基づいた条件付きビルド)をE2Eテストの高速化にも使えます。E2Eテストはフルビルドに加えてテスト実行が必要なため、以前は実行場所やタイミングを制限していたかもしれません。本来はユニットテストやlintと同様に、PRごとに実行したいはずです。
fingerprintを使えば、既にマッチするビルドがある場合にフルビルドの代わりにRepackを実行してMaestroのE2Eテスト用のビルド段階を高速化できます。Repackは"ビルド時のアップデート"のように考えられます。repackジョブは既存のビルドを取り、JavaScriptを再バンドルし、古いビルド内のJavaScriptバンドルを新しいものに置き換え、再署名します。これにより、テスト実行用の新しいビルドを約1分で...