Expo 不用 EAS,在 Windows 本地构建 iOS 安装包
这篇从新建一个 Expo 工程开始,在 Windows 本机生成 iOS 工程,装到 iPhone 上跑开发版,最后打出能上传 App Store Connect 的 ipa。你需要 Apple 开发者账号、一台 iPhone 和数据线,并且已经安装 KXBuild。第一次走完大约一小时,大部分时间花在下载依赖上。
本地构建和 EAS Build 怎么选
EAS Build 在 Expo 的云端 macOS 上构建,不用自己维护构建环境,证书也可以交给 EAS 管理。代价是免费账户每月的构建次数有限,高峰期要排队,每次改完原生配置都要等一轮云端构建。eas build --local 可以在本机构建,但 iOS 端仍然要求 macOS 和 Xcode。
在 Windows 上用 KXBuild 本地构建,不排队、不占云端次数,还能对原生代码下断点。需要你自己准备证书和描述文件,ios 目录也在本机生成。两种方式可以并存,团队里有人用 EAS 出正式包、有人在 Windows 上本地调试,互不影响。
新建 Expo 工程
已有工程在 VS Code 中「文件 → 打开文件夹」打开工程根目录即可,跳到下一节。
没有现成工程的,用 Expo 官方命令新建。工程的父目录用短路径,node_modules 层级很深,放得太深在 Windows 上容易遇到路径过长的错误。
1cd D:\work
2npx create-expo-app@latest expohello
新建完成后在 VS Code 中打开 D:\work\expohello。
也可以直接用 KXBuild 的模板新建,以下两种做法任选其一。
- VS Code 插件。命令面板运行
KXBuild: 新建项目,模板选「Expo 应用」。 - 命令行。执行
kxbuild project create -t expo_app -n expohello -o D:\work。
模板的说明见项目模板。
在 app.json 里填好 iOS 配置
Expo 工程的 ios 目录是按 app.json 生成的,所以在生成之前先把 iOS 相关的字段填好,能省掉一次重新生成。下面只列出和 iOS 相关的字段,其他字段保持原样。
1{
2 "expo": {
3 "name": "expohello",
4 "version": "1.0.0",
5 "ios": {
6 "bundleIdentifier": "com.example.expohello",
7 "buildNumber": "1",
8 "appleTeamId": "ABCDE12345",
9 "infoPlist": {
10 "NSCameraUsageDescription": "用于扫描二维码",
11 "NSMicrophoneUsageDescription": "用于录制视频中的声音"
12 }
13 }
14 }
15}
各字段的作用如下。
| 字段 | 作用 |
|---|---|
version |
商店里显示的版本号,对应 CFBundleShortVersionString |
ios.buildNumber |
构建号,对应 CFBundleVersion,每次上传 App Store Connect 都要比上一次大 |
ios.bundleIdentifier |
Bundle ID,要和开发者后台注册的 App ID、描述文件一致 |
ios.appleTeamId |
开发者账号的 Team ID,在 developer.apple.com 的 Membership 页能看到 |
ios.infoPlist |
直接写进 Info.plist 的键,权限说明文字放在这里 |
用到相机、麦克风、相册、定位的 App 必须写对应的权限说明,缺了会在调用时直接闪退,审核也会被拒。有些 Expo 库(例如 expo-camera)支持在 plugins 里配置权限文字,按该库文档写即可,效果相同。
签名需要的 .p12 证书和 .mobileprovision 描述文件放进证书目录,开发阶段用 Apple Development 证书和包含这部 iPhone UDID 的 Development 描述文件,获取和放置方法见证书与签名。
体检并安装原生依赖
第一次打开工程必须先体检。 它会检查 Expo 环境,并生成、准备好 iOS 端的工程,完成后工程下多出 ios 目录。以下两种做法任选其一。
- VS Code 插件。在 KXBuild 视图标题栏「···」中运行「体检并修复开发环境」。
- 命令行。执行下面的命令。
1kxbuild doctor D:\work\expohello
体检之后进入 ios 目录安装原生依赖。第一次会下载较多内容,时间取决于网络。
1cd D:\work\expohello\ios
2pod install
完成后 ios 目录下会有 expohello.xcworkspace,之后构建都用它。CocoaPods 的更多用法见 CocoaPods。
在 iPhone 上运行开发版
先用数据线连上 iPhone,解锁后点「信任此电脑」,并在「设置 → 隐私与安全性 → 开发者模式」中打开开发者模式,手机会重启一次。驱动和手机设置见设备准备。
开发版的 JS 从电脑上的 Metro 加载,所以先另开一个终端,在工程根目录启动 Metro。
1cd D:\work\expohello
2npx expo start
然后在 KXBuild 视图中选中这台 iPhone,按 F5。KXBuild 会依次构建、签名、安装、启动 App 并附加调试器。第一次调试某个 iOS 版本时会自动下载对应的符号库,有几个 GB,只需一次。
App 启动后,改 JS 或 TypeScript 代码保存即可自动刷新,不用重新构建。两种代码各用各的调试器。
- 原生代码(ios 目录下的 Swift、Objective-C)在 VS Code 中下断点,F5 启动后直接生效,用法见运行与调试。
- JS 代码在运行
npx expo start的终端按j打开 Expo 的调试器,在里面下断点。
App 一打开就红屏,通常是手机连不上电脑上的 Metro。确认手机和电脑在同一个局域网,Windows 防火墙放行 Node.js 或 Metro 的 8081 端口。
改了原生代码要先停止调试再按 F5,调试工具栏上的「重启」不会重新编译。
修改 app.json 之后重新生成 iOS 工程
改了 Bundle ID、版本号、权限说明、图标,或者新装了带原生代码的库,都要重新生成 ios 目录,否则改动不会进入 App。体检只在 ios 目录不存在时生成它,所以要先删掉旧的 ios 目录再体检。
1cd D:\work\expohello
2Remove-Item -Recurse -Force .\ios
3kxbuild doctor D:\work\expohello
4cd ios
5pod install
插件用户删掉 ios 目录后在 KXBuild 视图运行「体检并修复开发环境」,再到 ios 目录执行 pod install,效果相同。
重新生成会覆盖 ios 目录下的所有手动修改。需要长期保留的原生配置写进 app.json 或 config plugin,这是 Expo 推荐的做法。如果你的工程已经把 ios 目录纳入版本管理、直接改原生文件,就不要删目录,改用 属性列表编辑器和工程设置编辑器直接修改。
打出 Release ipa
上架前先确认两件事。ios.buildNumber 比上一次上传的大,改过就按上一节重新生成。证书目录里有 Apple Distribution 证书和 App Store 类型的描述文件,用 Development 描述文件签的包上传时会被拒。
Release 构建会把 JS 打包进 App,运行时不再依赖 Metro。以下两种做法任选其一。
- VS Code 插件。在 KXBuild 视图的「构建配置」节点切到 Release,命令面板运行
KXBuild: 构建。 - 命令行。在工程根目录执行下面的命令。
1kxbuild build .\ios\expohello.xcworkspace -c Release
工程的 Release 配置没有指定发布证书时,命令行加 -D CODE_SIGN_IDENTITY="Apple Distribution" 临时覆盖。
构建完成后得到 ios\build\expohello.ipa,结构和 Xcode 导出的一样。在 Windows 上用 Appuploader 上传到 App Store Connect,处理完成后出现在 TestFlight 页,先发给测试员验证再提交审核,详见发布与上架。
常见问题
体检没有生成 ios 目录。 带 -v 再运行一次 kxbuild doctor D:\work\expohello -v,输出里会写明失败的步骤,多数是依赖没装完或网络中断。
编译报找不到 Pods 里的头文件或模块。 确认已经在 ios 目录执行过 pod install,并且构建的是 .xcworkspace 而不是 .xcodeproj。
报文件名过长或找不到文件。 把工程移到 D:\work 这类短路径下,删掉 ios 目录重新生成。
安装后启动就退出,提示 didn’t start in time。 App 很可能启动即崩溃,常见原因是缺少权限说明或原生库没有重新生成。查看设备日志和崩溃报告的方法见运行与调试和排错与 FAQ。