Expo Development Build和Prebuild分别是什么?还需要Eject吗?
在Expo项目中,当你需要引入自定义原生代码或第三方原生SDK时,就会碰到一个核心问题:Expo Go无法加载自定义原生模块。这时你有两个现代方案——Development Build和Prebuild(CNG),而曾经常见的Eject已经在SDK 46中被正式废弃。
Expo Go的局限性
Expo Go是一个预打包的沙箱应用,内置了标准Expo SDK的所有模块。它的优势是开箱即用,但也意味着你只能使用SDK包含的原生功能,无法添加任何自定义原生代码。当你需要使用微信支付、极光推送、或者自己编写的原生模块时,Expo Go就不够用了。
Development Build:保留Expo体验的同时扩展原生能力
Development Build是Expo官方推荐的扩展方式。简单理解,它就是为你当前应用量身定制的"Expo Go"——包含了你项目所需的所有原生依赖,同时完整保留了Expo的开发体验。
核心优势
- 保留热更新(OTA)和EAS全家桶能力
- 支持任意第三方原生库和自定义原生模块
- 可通过Expo Modules API用Swift/Kotlin编写原生模块
- 升级SDK时原生层自动跟随,无需手动维护
创建Development Build
bash# 安装EAS CLI npm install -g eas-cli # 登录并配置 eas login eas build:configure # 构建开发版本 eas build --profile development --platform android
在eas.json中配置development profile:
json{ "build": { "development": { "developmentClient": true, "distribution": "internal" } } }
构建完成后,你会在EAS控制台拿到一个安装链接,安装到设备上即可像Expo Go一样通过扫码加载开发服务器。
添加自定义原生模块
使用Expo Modules API创建原生模块,比传统React Native桥接方式简洁得多:
Kotlin(Android):
kotlinpackage expo.modules.custom import expo.modules.kotlin.Promise import expo.modules.kotlin.exception.CodedException import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition class CustomModule : Module() { override fun definition() = ModuleDefinition { Name("CustomModule") AsyncFunction("customMethod") { promise: Promise -> try { promise.resolve("Success") } catch (e: Exception) { promise.reject(CodedException("ERR_CUSTOM", e.message, e)) } } } }
Swift(iOS):
swiftimport ExpoModulesCore public class CustomModule: Module { public func definition() -> ModuleDefinition { Name("CustomModule") AsyncFunction("customMethod") { (promise: Promise) in promise.resolve("Success") } } }
TypeScript调用:
typescriptimport CustomModule from "./src/CustomModule"; CustomModule.customMethod() .then(result => console.log(result)) .catch(error => console.error(error));
相比旧式React Native桥接(Java + Objective-C),Expo Modules API统一用Kotlin和Swift,代码量更少,类型更安全。
Prebuild与CNG:取代Eject的新范式
Eject为什么被废弃
expo eject在SDK 46(2022年8月)中已被移除。Eject的核心问题是它把原生目录变成一次性生成且需要手动维护的代码——一旦eject,你就得自己处理原生依赖升级、版本兼容、构建配置等麻烦事,而且无法回退。
Prebuild + CNG的工作方式
Prebuild用持续原生生成(Continuous Native Generation,CNG)替代了Eject的一次性生成:
bash# 生成原生项目(可反复执行) npx expo prebuild # 清理后重新生成 npx expo prebuild --clean
CNG的关键区别在于:
android/和ios/目录加入.gitignore,不纳入版本控制- 原生代码每次根据
app.json和已安装的npm包自动生成 - 修改原生配置通过Config Plugin而非手动编辑原生文件
- 升级时只需更新npm依赖再重新prebuild,无需手动合并
Config Plugin:声明式修改原生配置
当你需要修改原生项目的Info.plist、AndroidManifest.xml等配置时,不再手动编辑,而是通过Config Plugin:
typescript// app.config.ts中使用config plugin import type { ConfigPlugin } from "expo/config-plugins"; const withCustomConfig: ConfigPlugin = (config) => { // 修改Android配置 config.android = { ...config.android, // 自定义配置 }; return config; }; export default withCustomConfig;
许多常用库已经提供了自己的Config Plugin,安装后自动配置原生层。对于没有官方Plugin的库,你也可以编写本地Plugin。
Development Build vs Prebuild vs Eject对比
| 特性 | Development Build | Prebuild(CNG) | Eject(已废弃) |
|---|---|---|---|
| 保留Expo开发体验 | 是 | 部分 | 否 |
| 支持OTA热更新 | 是 | 需配合EAS | 否 |
| 自定义原生模块 | 支持 | 支持 | 支持 |
| 原生目录维护 | 自动 | 自动(可重复生成) | 手动(一次性) |
| SDK升级难度 | 低 | 低 | 高 |
| 可回退性 | 高 | 高 | 低 |
实际开发中,Development Build和Prebuild并不互斥,而是配合使用:Prebuild负责生成原生项目,Development Build在此基础上构建可调试的开发版本。
什么时候该怎么做
还在用Expo Go且功能够用:继续用Expo Go即可,无需任何改动。
需要第三方原生库但不需要自己写原生代码:安装库后创建Development Build,大多数场景到此就够了。
需要自定义原生模块:用Expo Modules API编写模块,然后通过Development Build构建。原生模块用Swift/Kotlin,不再需要Objective-C。
旧项目已经eject过:参考Expo官方的Adopt Prebuild指南逐步迁移到CNG工作流。核心步骤是确保入口文件使用registerRootComponent,然后执行npx expo prebuild --clean重新生成原生目录,将手动修改迁移为Config Plugin。
团队有深厚原生开发经验且不需要Expo服务:这种情况下可以脱离Expo管理,直接使用React Native CLI。但这不等于eject,而是从一开始就选择bare工作流。
常见问题
Development Build构建太慢怎么办?
首次构建需要编译整个原生项目,确实较慢。后续增量构建会快很多。如果主要在iOS开发,可以考虑在本地用npx expo run:ios代替EAS Build,避免排队等待。
Config Plugin能不能修改任意原生文件?
理论上可以,但不推荐。Config Plugin适合处理配置层面的修改(权限、URL Scheme、字体等)。大规模原生代码改动应该用Expo Modules API写成独立模块。
Prebuild会覆盖我手动改的原生文件吗?
会。这也是CNG的设计意图——原生目录是可丢弃的。如果你有手动修改,必须迁移为Config Plugin,否则下次prebuild就会丢失。
总的来说,2026年的Expo生态中,Eject已成为历史名词。面对原生扩展需求,Development Build + Prebuild(CNG)是唯一的推荐路径,它既保留了Expo的开发效率,又获得了完整的原生能力。