5月28日 05:39

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):

kotlin
package 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):

swift
import ExpoModulesCore public class CustomModule: Module { public func definition() -> ModuleDefinition { Name("CustomModule") AsyncFunction("customMethod") { (promise: Promise) in promise.resolve("Success") } } }

TypeScript调用:

typescript
import 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 BuildPrebuild(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的开发效率,又获得了完整的原生能力。

标签:Expo