关于 limrun-expo-development
面向 Expo / React Native 开发的 Limrun 技能,指导在远程 iOS 模拟器和 Android 模拟器上构建、启动及验证应用。内容涵盖开发客户端、Metro 连接和原生变更后的重建流程。
主要功能
- 01Expo 项目就绪检查
- 02远程构建并安装 Debug 应用
- 03通过 Metro 迭代 JS / TS
- 04连接 iOS 与 Android 模拟器
- 05验证界面并检查应用日志
适用场景
- 01在远程模拟器上调试 Expo 应用
- 02原生依赖变更后重建开发客户端
- 03通过 Metro 预览 JS / TS 修改
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ limrun-inc/ skills --skill limrun-expo-development安装后在 Claude Code 中输入/limrun-expo-development确认已加载。
详细介绍
在 Limrun 上开发 Expo 应用
将此技能用于 Expo / React Native 专属设置和开发客户端迭代,适用于 iOS 模拟器和 Android 模拟器。有关命令详情、设备交互、截图、录屏和清理,请使用 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.bundleIdentifier(iOS)获取BUNDLE_ID,从android.package(Android)获取PACKAGE。当缺少android.package时,introspect 会报告占位值(例如com.placeholder.appid),但构建会生成不同的真实 applicationId;请在构建前于app.json中设置android.package,确保$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 进行重新构建,
并通过构建输出的 Download 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 正式版使用 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,供分享使用。
如果已有模拟器因重用资源而在运行,之后又需要进行原生重建,请连接到同一模拟器,而不是创建第二个模拟器:
lim xcode attach-simulator <ios-instance-id> --id <xcode-instance-id>
连接后,每次 lim xcode build 成功,都会在已连接的模拟器上安装并启动应用。
在 iOS 上通过 Limrun 启动 Metro
此流程适用于 iOS。Android 使用 adb reverse;请跳转到在 Android 上启动 Metro,不要运行 lim ios 命令。
Debug 应用安装后,启动一个目标隧道。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 会在清单、bundle URL 和深层链接中保留 localhost 和声明的端口。请将其内联设置,以使其优先于项目 dotenv 值。用户迭代期间,请让 Metro 和分离运行的隧道保持运行。将 Metro 作为受管理的后台进程运行,或者先把输出的 TUNNEL_URL 复制到另一个终端,再启动应用。
如果端口 8081 已被占用,请选择另一个明确的端口,并在隧道选择器、TUNNEL_URL 和 Expo 的 --port 中使用相同的值。选择器集合不可变:端口发生变化时,请停止并使用完整的选择器列表重新创建隧道。
只有在依赖项已安装且环境确实与网络隔离时,才添加 --offline。离线模式会禁用网络检查和依赖项验证,因此不要用它来绕过常规的 Expo 身份验证。
启动 iOS 开发客户端
通过开发客户端 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 来自 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>
一个实例只能接受一个活动目标隧道。开始另一组路由之前,请先停止当前隧道。迭代结束后,使用 Ctrl+C 停止 Metro,并使用上述命令停止分离运行的隧道。
如果模拟器在 Metro 停止时尝试访问某条路由,隧道仍会保持活动状态,状态中会记录相关联的 connection_refused。使用相同的代理 URL 重启 Metro,并重新打开开发客户端 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 隧道会随启动它的 shell 一同结束,且每次重新连接时端口都会变化;每次重新连接后,都要使用新的序列号重新运行 adb reverse。隧道详情请参阅 limrun-android-emulator。
首次启动时,请点按完成开发菜单的引导页面(lim android tap-element --text Continue),然后关闭开发菜单。bundle 会在原生页面后方加载。
回退方案:Expo 隧道
如果无法使用 Limrun 端点,请启动 Expo 的公共隧道:
npm install --save-dev '@expo/ngrok@^4.1.0'
npx expo start --dev-client --tunnel
请使用 Expo 输出的完整开发客户端 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 固定端口反向隧道
对于已经使用预留端口范围 57090–57099 的工作流,仍可使用 lim ios reverse。
Expo dev-client 可以推导或公布多个打包器 URL,因此像 57090:8081 这样的映射不匹配,可能会导致某些 URL 指向本地 Metro 端口,而不是面向模拟器的反向端点。
在 REACT_NATIVE_PACKAGER_HOSTNAME 和编码后的 dev-client URL 中,都使用 lim ios reverse 打印的面向模拟器的主机。运行 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。对于尚未配置 lint 的项目,Expo 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),请在监控模式下重新启动应用;应用运行期间命令会一直阻塞(请在后台 shell 中运行),应用退出时会打印退出原因、堆栈跟踪以及近期应用日志的末尾内容:
lim android launch-app "$PACKAGE" --mode RelaunchIfRunning --id <android-instance-id>
迭代
连接后,JS/TS 编辑应通过 Metro 更新,无需再次进行原生构建。如果任务更改了原生依赖项、原生配置或构建设置,请在重新启动开发循环前重新构建 Debug。
告知用户:
- 设备串流的 Markdown 短链接,例如
[打开模拟器串流](<signedStreamUrl>)或[打开模拟器串流](<signedStreamUrl>) - 已上传的 Debug 资源名称
- 现在可以通过 Metro 迭代 JS/TS 更改
- 原生更改需要新的 Debug 构建
最终预览
如需最终的可分享预览或 PR 演示,请使用 Release 构建,这样用户无需运行 Metro:
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 无法确定开发构建的 scheme。No script URL provided通常表示该应用并非 dev-client 构建,或启动时未提供 dev-client URL。- 全新原生重建/安装后,类似
Cannot find native module的过期 Metro/运行时错误可能来自旧应用进程。重新打开 dev-client URL,并在认定重建失败前通过element-tree验证。 - 如果添加原生依赖项后,Debug 构建仍表现得像旧的原生依赖图,这是异常的 Limrun 行为。请重试构建;创建新的构建/设备目标仅作为故障排查的备用方案。
- Expo 隧道启动可能不稳定。更改工作流前请先重试。
- 原生依赖项或原生配置更改后,请勿重复使用已上传的 Debug 资源。
- 在此循环中,每次调用
lim android都要传入--id <android-instance-id>:实例解析按 git 工作树分别进行,而循环中的命令会从不同目录运行。 - 对 Expo 应用而言,截图显示应用正常但 Android 元素树为空是正常的;请通过截图验证。