limrun-expo-development について
リモートの iOS シミュレーターと Android エミュレーターで Expo/React Native を開発するための Limrun スキルです。開発クライアントのビルド、Metro 接続、変更後の検証手順を扱います。
主な機能
- 01Expo プロジェクトの準備確認
- 02Debug アプリのリモートビルドとインストール
- 03Metro による JS/TS の反復開発
- 04iOS/Android シミュレーターへの接続
- 05画面の確認とアプリログの調査
活用シーン
- 01リモートシミュレーターでの Expo アプリのデバッグ
- 02ネイティブ依存関係変更後の開発クライアント再ビルド
- 03Metro を使った JS/TS 変更のプレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ limrun-inc/ skills --skill limrun-expo-developmentインストール後、Claude Code で/limrun-expo-developmentを入力して読み込まれたか確認します。
詳細
Limrun での Expo アプリ開発
このスキルは、iOS シミュレーターや Android エミュレーター上での Expo/React Native 固有のセットアップや、開発クライアントを使った反復開発に使用します。コマンドの詳細、デバイス操作、スクリーンショット、録画、クリーンアップについては limrun-ios-simulator と limrun-android-emulator を、ビルドフラグの詳細や Expo を使用しないワークフローについてはビルドスキル(limrun-xcode、limrun-gradle)を使用してください。
すべてのビルドとデバイス操作は Limrun 上で実行する必要があります。ローカルの Xcode、ローカルシミュレーター、ローカルの Android SDK、ローカルエミュレーターは使用しないでください。ローカルの adb は、CLI のトンネルを介してリモートエミュレーターと通信する場合にのみ使用します。
Expo の準備状況
Expo の依存関係やアプリ設定を変更する前に、アプリの Expo SDK バージョンを確認し、対応するバージョンの Expo ドキュメントを使用してください。
Expo アプリであることを確認します。
npx expo config --type introspect --json
以下を確認します。
- iOS の
ios.bundleIdentifierからBUNDLE_IDを、Android のandroid.packageからPACKAGEを取得します。android.packageがない場合、introspect ではプレースホルダー(com.placeholder.appidなど)が報告されますが、ビルド時には別の実際の applicationId が生成されます。$PACKAGEがインストール済みアプリと一致するよう、ビルド前にapp.jsonでandroid.packageを設定してください。 slugからSLUGを取得します。schemeからSCHEMEを取得し、ない場合はexp+${SLUG}を使用します。git branch --show-currentからBRANCHを取得し、ない場合はmainを使用します。- iOS では
ASSET_NAME="${BUNDLE_ID}/${BRANCH}-debug.zip"、Android ではASSET_NAME="${PACKAGE}/${BRANCH}-debug.apk"を使用します。
開発クライアントの準備
Expo の開発ビルドには expo-dev-client が必要です。package.json に含まれていない場合は追加してください。
npx expo install expo-dev-client
expo-dev-client のインストール、ネイティブ依存関係の追加・削除・更新、またはネイティブアプリ設定の変更を行うと、アップロード済みの Debug アセットは古くなります。開発ループを開始する前に、新しい Debug アプリをビルドしてください。ネイティブ依存関係を変更した場合、アップロード済みの Debug アセットは古くなるため、開発ループを始める前に再ビルドしてください。
Debug ビルドアセット
まず、再利用できる Debug 開発クライアントアセットがすでに存在するか確認します。
lim asset list --name-prefix "$BUNDLE_ID/" # iOS
lim asset list --name-prefix "$PACKAGE/" # Android
次の両方を満たす場合に限り、同じ $ASSET_NAME を再利用します。
- アセットが存在すること。
- このセッションでネイティブ依存関係またはネイティブ設定を変更していないこと。
現在のタスクでネイティブ依存関係またはネイティブ設定を変更した場合は、$ASSET_NAME が存在していても再利用しないでください。
アセットを再利用する場合は、デバイスを作成または再利用して、アプリをインストールします。
lim ios create \
--reuse-if-exists \
--install-asset "$ASSET_NAME" \
--label repo=<repo> \
--label agent=<agent>
lim android create \
--reuse-if-exists \
--install-asset "$ASSET_NAME" \
--no-open \
--label repo=<repo> \
--label agent=<agent>
Android の注意事項:デフォルトで create が開くトンネルは維持してください(通常の操作セッションとは異なり、ここでは --no-connect を指定しないでください)。以下の Metro リバーストンネルは、そのトンネルを介して動作します。出力に表示されたインスタンス ID を控え、以降のすべての lim android 呼び出しに --id を指定してください。Metro と Expo はアプリのディレクトリから実行され、インスタンスの解決は Git ワークツリーごとに行われるため、別の場所から実行したコマンドではインスタンスが自動的に見つかりません。
Android での新規ビルド
Debug APK をリモートでビルドし、アセットとしてアップロードします(Expo の prebuild、--expo-app-dir、その他のビルドフラグについては limrun-gradle を参照してください。デフォルトの assembleDebug タスクが適切な開発クライアントビルドです)。
lim gradle build . --upload "$ASSET_NAME"
lim android create --reuse-if-exists --install-asset "$ASSET_NAME" --no-open --label repo=<repo> --label agent=<agent>
起動中のエミュレーターで後からネイティブビルドをやり直す場合は、--upload を指定して再ビルドし、ビルド時に表示されるダウンロード URL から新しい APK をインストールします(インスタンスがサーバー側で APK を取得します)。
lim gradle build . --upload "$ASSET_NAME"
lim android install-app "<Download URL from the build output>" --id <android-instance-id>
iOS での新規ビルド
新規ビルドでは、スタンドアロンの Xcode サンドボックスを作成または再利用してからビルドします。長時間のビルド中にシミュレーターがアイドル状態になり(非アクティブタイムアウトに達し)ないよう、シミュレーターを作成する前にビルドしてください。
lim xcode create --reuse-if-exists --label repo=<repo> --label agent=<agent>
lim xcode build . \
--configuration Debug \
--upload "$ASSET_NAME"
プロジェクトで特定の Xcode が必要な場合は、リポジトリ内で lim xcode version set <major|major.minor> を一度実行してください(Xcode 27 GA では 27、ベータ版では 27.1)。ルールについては limrun-xcode を参照してください。
プロジェクトのレイアウトに応じて、--expo-app-dir、--scheme、または --workspace を使用してください。
次に、その Xcode ターゲットに接続したシミュレーターを作成します。接続時にビルドがインストールされ、すぐに起動します。
lim ios create --attach \
--reuse-if-exists \
--label repo=<repo> \
--label agent=<agent>
ユーザーに表示するブラウザーがない場合は、任意の create に --no-open を追加してください。ストリーム URL を開かず、共有できるよう URL を出力に残します。
再利用したアセットで iOS シミュレーターがすでに実行中で、その後ネイティブの再ビルドが必要になった場合は、2 台目を作成せず、同じシミュレーターに接続してください。
lim xcode attach-simulator <ios-instance-id> --id <xcode-instance-id>
接続後は、lim xcode build が正常に完了するたびに、アプリが接続中のシミュレーターにインストールされて起動します。
iOS で Limrun 経由で Metro を起動する
この手順は iOS 用です。Android では adb reverse を使用します。lim ios コマンドは実行せず、代わりに Android で Metro を起動する に進んでください。
Debug アプリのインストール後、宛先トンネルを 1 つ起動します。Metro は通常のローカルポートをそのまま使用でき、Expo は localhost を通知します。
METRO_PORT=8081
lim ios tunnel \
--selector "localhost:${METRO_PORT}" \
--detach \
--id <ios-instance-id>
TUNNEL_URL="http://localhost:${METRO_PORT}"
echo "TUNNEL_URL=$TUNNEL_URL"
EXPO_PACKAGER_PROXY_URL="$TUNNEL_URL" \
npx expo start --dev-client --port "$METRO_PORT"
EXPO_PACKAGER_PROXY_URL を設定すると、マニフェスト、バンドル URL、ディープリンクで localhost と宣言されたポートが使われます。プロジェクトの dotenv の値より優先されるよう、インラインで設定してください。ユーザーが作業を進めている間は、Metro とデタッチされたトンネルを起動したままにしてください。Metro を管理対象のバックグラウンドプロセスとして実行するか、表示された TUNNEL_URL を 2 つ目のターミナルにコピーしてからアプリを起動してください。
ポート 8081 がすでに使用中の場合は、別のポートを明示的に選び、トンネルセレクター、TUNNEL_URL、Expo の --port に同じ値を使用してください。セレクターセットは不変です。ポートを変更するときは、トンネルを停止し、セレクターリスト全体を指定して再作成してください。
依存関係をインストール済みで、実際にネットワークから隔離された環境である場合に限り、--offline を追加してください。オフラインモードではネットワークチェックと依存関係の検証が無効になるため、通常の Expo 認証への対処として使用しないでください。
iOS 開発クライアントを起動する
dev-client URL を使って Debug アプリを開きます。
ENCODED_URL="$(node -e 'console.log(encodeURIComponent(process.argv[1]))' "$TUNNEL_URL")"
DEV_CLIENT_URL="${SCHEME}://expo-development-client/?url=${ENCODED_URL}"
lim ios open-url --id <ios-instance-id> "$DEV_CLIENT_URL"
起動に失敗し、プライマリスキームが scheme によって設定されていた場合は、exp+${SLUG} でもう一度だけ試してください。新規インスタンスでは、iOS の開発メニューのオンボーディングシートが最初のディープリンクを消費することがあります。案内を進めてから URL をもう一度開いてください。
Expo Go の場合は、--dev-client を --go に置き換えてから、次を開きます。
lim ios open-url \
--id <ios-instance-id> \
"exp://${TUNNEL_URL#http://}"
トンネルのライフサイクル:
lim ios tunnel status --id <ios-instance-id> --json
lim ios tunnel stop --id <ios-instance-id>
1 つのインスタンスで有効にできる宛先トンネルは 1 つです。別のルートセットを開始する前に、現在のトンネルを停止してください。作業を終えたら、Ctrl+C で Metro を停止し、上記のコマンドでデタッチされたトンネルを停止してください。
Metro が停止中にシミュレーターがルートへの接続を試みると、トンネルは有効なままで、ステータスに関連付けられた connection_refused が記録されます。同じプロキシ URL で Metro を再起動し、dev-client URL を開き直してください。シミュレーターやトンネルを再作成する必要はありません。
Android で Metro を起動する
Android では、CLI の ADB トンネル経由で adb reverse を使用します。Metro はデフォルトのポート 8081 のままにし、パッケージャーのホスト名オーバーライドは不要です。エミュレーターからは http://127.0.0.1:8081 で Metro に接続できます。
lim android connect --id <android-instance-id> # background shell; prints "Tunnel started on 127.0.0.1:<port>."
adb -s 127.0.0.1:<port> reverse tcp:8081 tcp:8081
npx expo start --dev-client --port 8081
DEV_CLIENT_URL="${SCHEME}://expo-development-client/?url=http%3A%2F%2F127.0.0.1%3A8081"
lim android open-url "$DEV_CLIENT_URL" --id <android-instance-id>
ADB トンネルは、起動したシェルとともに終了し、再接続のたびにポートが変わります。再接続後は、新しいシリアルを使って adb reverse を再実行してください。トンネルの詳細については limrun-android-emulator を参照してください。
初回起動時は、開発メニューのオンボーディングシートを進め(lim android tap-element --text Continue)、開発メニューを閉じてください。ネイティブのシートの背後でバンドルが読み込まれます。
フォールバック:Expo Tunnel
Limrun エンドポイントを使用できない場合は、Expo のパブリックトンネルを起動します。
npm install --save-dev '@expo/ngrok@^4.1.0'
npx expo start --dev-client --tunnel
Expo が出力する dev-client URI 全体を使用してください。
DEV_CLIENT_URL="<complete URI printed by Expo>"
lim ios open-url --id <ios-instance-id> "$DEV_CLIENT_URL"
lim android open-url "$DEV_CLIENT_URL" --id <android-instance-id>
レガシー iOS 固定ポートのリバーストンネル
lim ios reverse は、予約済みの 57090–57099 の範囲をすでに使用しているワークフローでも引き続き利用できます。
Expo dev-client は複数のパッケージャー URL を導出または通知することがあるため、57090:8081 のようにマッピングが一致していないと、一部の URL がシミュレーター側のリバースエンドポイントではなく、ローカルの Metro ポートを指す場合があります。
lim ios reverse が出力するシミュレーター側のホストを、REACT_NATIVE_PACKAGER_HOSTNAME とエンコード済みの dev-client URL の両方で使用してください。Metro の実行中は、リバースコマンドを別のターミナルまたはバックグラウンドターミナルで実行し続けてください。
lim ios reverse 57090:57090 --id <ios-instance-id>
REACT_NATIVE_PACKAGER_HOSTNAME=<reverse-host> \
npx expo start --dev-client --host lan --port 57090
ENCODED_URL="$(node -e 'console.log(encodeURIComponent(process.argv[1]))' "http://<reverse-host>:57090")"
DEV_CLIENT_URL="${SCHEME}://expo-development-client/?url=${ENCODED_URL}"
lim ios open-url --id <ios-instance-id> "$DEV_CLIENT_URL"
検証
手早く静的検証を行うには、次を優先してください。
npx tsc --noEmit
リポジトリですでに ESLint が設定されている場合に限り、npm run lint または npx expo lint を実行してください。Expo lint は、まだ lint が設定されていないプロジェクトで ESLint 設定を作成し、依存関係を変更することがあります。
iOS では、まず要素ツリーを確認してください。
lim ios element-tree
成功とは、アプリの UI が表示されるか、Expo の開発メニューにトンネルへの接続が表示されることです。新しいインスタンスでは、dev-client の初回起動時にランチャーを覆う開発メニューのオンボーディングシートが表示されることがあります。シートをタップして進め(lim ios tap-element --ax-label Continue)、その後 dev-client URL をもう一度開いてください。最初のディープリンクはシートに消費されるためです。ツリーで接続を確認できない場合は、アプリのログを調べてください。
lim ios app-log "$BUNDLE_ID" --tail 100
Android では要素ツリーではなく、スクリーンショットで確認してください。Expo アプリでは通常、アクセシビリティノードが公開されないため、画面が表示されていてもツリーが空のままになることがあります(limrun-android-emulator を参照)。
lim android screenshot check.png --id <android-instance-id>
アプリが終了した理由(クラッシュ、ANR)を確認するには、監視付きで再起動してください。このコマンドはアプリの実行中は終了せず(バックグラウンドシェルで実行してください)、アプリが終了すると終了理由、スタックトレース、直近のアプリログの末尾を出力します。
lim android launch-app "$PACKAGE" --mode RelaunchIfRunning --id <android-instance-id>
反復開発
接続後は、JS/TS の編集内容が追加のネイティブビルドなしで Metro 経由で反映されます。タスクでネイティブ依存関係、ネイティブ設定、またはビルド設定を変更する場合は、開発ループを再開する前に Debug を再ビルドしてください。
ユーザーには次の内容を伝えてください。
- デバイスストリームを短い Markdown リンクで提示します。例:
[Open simulator stream](<signedStreamUrl>)または[Open emulator stream](<signedStreamUrl>) - アップロードした Debug アセット名
- JS/TS の変更は Metro 経由で反映できること
- ネイティブの変更には新しい Debug ビルドが必要なこと
最終プレビュー
最終的に共有するプレビューや PR のデモには、ユーザーが Metro を実行しておく必要がないよう、Release ビルドを使用してください。
ASSET_NAME="<bundle-id>/<pr-or-session>.zip"
lim xcode build . --configuration Release --upload "$ASSET_NAME"
ASSET_NAME="<package>/<pr-or-session>.apk"
lim gradle build . --task assembleRelease --upload "$ASSET_NAME"
プレビュー URL(APK アセットの場合は platform=android):
https://console.limrun.com/preview?asset=${ASSET_NAME}&platform=ios
注意点
npx expo start --dev-clientにはexpo-dev-clientが必要です。これがないと、Expo は開発ビルドのスキームを判別できません。No script URL providedは通常、アプリが dev-client ビルドではないか、dev-client URL なしで起動されたことを意味します。- ネイティブを新たに再ビルドしてインストールした後に表示される、
Cannot find native moduleのような古い Metro/ランタイムエラーは、古いアプリプロセスが原因の場合があります。再ビルドの失敗と判断する前に、dev-client URL を再起動し、element-treeで確認してください。 - ネイティブ依存関係を追加した後の Debug ビルドが、以前のネイティブ依存関係の構成のまま動作する場合、これは予期しない Limrun の動作です。ビルドを再試行してください。新しいビルド/デバイスターゲットの作成は、トラブルシューティング時の代替手段に限ります。
- Expo トンネルの起動は不安定な場合があります。ワークフローを変更する前に再試行してください。
- ネイティブ依存関係またはネイティブ設定を変更した後は、アップロード済みの Debug アセットを再利用しないでください。
- Android では、インスタンスの解決は git worktree ごとに行われ、ループ内のコマンドは異なるディレクトリから実行されます。このループ内のすべての
lim android呼び出しに--id <android-instance-id>を渡してください。 - スクリーンショットにアプリが表示されている一方で Android の要素ツリーが空なのは、Expo アプリでは正常です。スクリーンショットで確認してください。