Expo SDK 核心包演进全解析:从 expo/fetch 到原生 Web 运行时(packages/expo CHANGELOG 深度解读)
packages/expo 是 Expo 开源仓库中承载 "The Expo SDK"(package.json 的自述)的元包——它聚合 expo-modules-core、@expo/cli、@expo/metro 等能力,并为 App 提供入口、运行时 polyfill 与原生宿主层。其 CHANGELOG.md 记录了从 SDK 45 到 57 近三年的完整演进:原生 Fetch API 的落地与成熟、Web 标准运行时(Winter Runtime)的持续补齐、iOS/Android 新架构宿主的迁移、DOM Components 的诞生,以及多次破坏性变更。读完本文,你将掌握这套版本档案的阅读方法、各能力引入的确切版本与底层实现,并能在升级 SDK 时准确评估影响面。
一、读懂这份版本档案:CHANGELOG 的结构与分类体系
1.1 变更记录的组织方式
这份 CHANGELOG 采用"倒序时间线 + 语义化分类"结构:顶部是 Unpublished(未发布变更,即当前 main 分支上已合入、尚未随版本号发布的改动),其下按版本号从新到旧排列。每个版本条目内部按统一图标体系分类:
| 分类 | 含义 |
|---|---|
| 🛠 Breaking changes | 破坏性变更,升级前必须评估 |
| 🎉 New features | 新功能 |
| 🐛 Bug fixes | 缺陷修复 |
| 💡 Others | 内部重构、依赖升级、API 再导出等 |
| ⚠️ Notices | 兼容性公告(如新增 React Native 版本支持) |
| 📚 3rd party library updates | 三方库版本更新 |
大量补丁版本标注为 _This version does not introduce any user-facing changes._(无面向用户变更),表明这类版本仅包含构建或依赖层面的微调——例如 57.0.9、57.0.6、57.0.1 等。同时可以观察到发布节奏:SDK 57 时代以 57.0.x 补丁形式持续发布,大版本(55/56/57)则先经历 preview.0 ~ preview.N 预发布阶段再正式发版。
1.2 "Unpublished" 段:当前开发状态的窗口
Unpublished 段是评估仓库当前状态的第一手信息,它直接反映 main 分支上正在积累的变更。以本文写作时的仓库状态为例,Unpublished 段包含:
- Breaking:Node.js 最低版本提升到
^22.13.0(对应 package.json 中engines.node字段^22.13.0 || ^24.3.0 || ^26.0.0 || >=27.0.0); - Bug fixes:一批围绕
expo/fetch的流式传输竞态修复、Hermes JSI 重载崩溃修复、UIKit Scene 生命周期下的 deep link 修复、import.meta.url在 web 端的空值修复、DOM 组件在 WebView 加载期间的 prop 更新丢失修复等; - Others:
URL/URLSearchParams升级到 IDNA/TR-46 规范实现、TextDecoder性能重写、@expo/metro升级至 56.1.0 等。
对开发者而言,跟踪 Unpublished 段可以提前感知下一版 SDK 的动向。
二、版本基线演进:平台目标与 React Native 支持矩阵
2.1 原生平台最低版本的一路抬升
CHANGELOG 清晰记录了部署目标的收紧轨迹:
- SDK 47:iOS 部署目标提升至 13.0,弃用 iOS 12;
- SDK 50:iOS 13.4;同时补上 Android 对 SDK 21/22 的放弃(SDK 50 alpha);
- SDK 52:iOS/tvOS 部署目标提升至 15.1;
- SDK 56(56.0.0-preview.0):最低版本抬升为 iOS/tvOS 16.4、macOS 13.4——这是一次影响面较大的基线变更,意味着使用较旧系统设备的用户将无法运行 SDK 56+ 构建的应用。
2.2 React Native 版本支持节奏
每次新架构兼容都通过 ⚠️ Notices 或 🎉 New features 显式宣告,形成一条清晰的兼容线:
RN 0.69(SDK 46)→ 0.70(SDK 47)→ 0.71/0.72/0.73(SDK 48~50)→ 0.74/0.75/0.76(SDK 51~52)→ 0.77/0.78(SDK 53)→ 0.80(SDK 54)→ 0.82/0.83(SDK 55)→ 0.84(SDK 56)→ 0.87+(Unpublished,移除旧架构 bridge 的 RCTRootViewFactoryConfiguration 装配)。
SDK 51 起正式放弃 RN 0.73 及更低版本(51.0.0-beta.0);SDK 53 将 React Native 升级至 0.78,并默认启用新架构(newArchEnabled 未指定时视为 true)。当前仓库 devDependencies 中的 react-native 为 0.87.1(package.json),与 Unpublished 段"针对 RN 0.87+ 修复 iOS 构建"的变更互相印证。
2.3 工具链基线
除平台与 RN 之外,仓库还持续跟进工具链:SDK 56 将项目模板升级到 TypeScript v6;SDK 54 默认启用 React Native Strict TypeScript API typings;SDK 53 的 expo/tsconfig.base 切换到 moduleResolution: "bundler";Node.js 最低版本在 Unpublished 段提升到 22.13.0。
三、expo/fetch:原生 Fetch API 的完整落地与成熟
3.1 从零到一:fetch 的引入与转正
expo/fetch 是这份 CHANGELOG 中出现频率最高的主题,其演进本身就是一部"从实验特性到默认运行时"的完整案例:
- SDK 52(52.0.0-preview.0):引入 Fetch API 支持(原始实现合并自多个 PR),同时加入
file://本地文件请求支持; - SDK 52.0.19:为
expo/fetch加入 Blob 支持,并让FormData支持 Blob 字段; - SDK 56(56.0.0-preview.0):
expo/fetch成为默认 fetch——这是一个 Breaking change,意味着原生端fetch()不再走 react-native 自带的 whatwg-fetch,而是直接命中 Expo 的原生实现。源码层面,这一实现的入口位于 src/winter/fetch/fetch.ts:它把输入归一化(区分字符串 URL、URL 对象与 Request 对象)、处理credentials: 'same-origin'(等价映射为include)、归一化 HTTP method、在signal.aborted时抛出AbortError,最终交给ExpoFetchModule.NativeRequest发起原生请求。
3.2 流式传输与取消语义的持续打磨
fetch 的流式能力经历了大量修补,这从侧面说明"真流式"在移动端的复杂度:
- 52.0.8 起"懒启动"流式传输(
Started expo/fetch streaming lazily); - 52.0.19 修复
AbortController流式请求不工作、流式请求不完成的问题; - 52.0.36 修复 Android 的
cancelStreaming问题; - 56.0.8 修复
bodyUsed在 Response 被 clone 两次后在兄弟对象间泄漏的问题; - Unpublished 段密集修复:URLSession 回调与
startStreaming()之间的竞态(可能导致 200 响应空 body、丢块或流未关闭)、AbortSignal中止时在途 read 应 reject 为AbortError而非永久挂起、请求失败后Response.text()/.arrayBuffer()永不 settle 等。
源码中可见对应的健壮性设计:fetch.ts 在启动前检查 signal 是否已中止,并为中止订阅了清理回调——先中止 body 流再取消原生请求,避免迟到的原生事件触及已废弃的 controller。
3.3 响应体语义与压缩解码
- SDK 56(56.0.0):Android 端新增
brotli、gzip、zstd解压支持(CompressionInterceptor.kt在 56.0.5 被进一步精修)——即使调用方自定义了Accept-Encoding头,也会解压(56.0.8 修复); - 56.0.0-preview.12:实现
Response.clone(),并按规范在 body 被二次读取时抛出TypeError; - 56.0.0-preview.0:为
Response添加Symbol.toStringTag,使其能被识别为标准 Response 对象; - 57.0.0-preview.0 时代以来的修复:
Response.blob()在全局 Blob 为 react-native 实现时抛错(57.0.5 修复);Android 上无 body 的POST/PUT/PATCH会误发单个0x00字节(56.0.10 修复)。
3.4 输入兼容性
expo/fetch 对输入类型的兼容也在持续收紧:56.0.0-preview.13 修复其不尊重自身 NativeRequest 作为 RequestInit 输入的问题;56.0.0 修复 whatwg-fetch 的 Request#body 未正确透传的问题。这保证了从 react-native 默认 fetch 迁移到 expo/fetch 时,既有代码的行为尽量一致。
四、Winter Runtime:原生端 Web 标准运行时的系统性补齐
CHANGELOG 中大量"Add ... to native platforms"类条目,指向 expo 包内置的 Winter Runtime(源码集中在 src/winter),目标是让原生端的全局对象向浏览器看齐:
4.1 URL 与 URLSearchParams
- SDK 50 alpha:加入标准
URLAPI(随后为规避SharedArrayBuffer问题迁移到whatwg-url-without-unicode),之后一度回退再恢复; - SDK 55 preview:用
whatwg-url-minimum替换整个URL/URLSearchParams实现; - SDK 56 preview.7:升级到
whatwg-url-minimum@0.1.2,修复URL/URLSearchParams缺失Symbol.toStringTag的问题; - Unpublished 段:升级到 IDNA/TR-46 规范实现并提升性能,"与浏览器的差距已很小"。
配套能力包括 SDK 52 的 URL.canParse、URL.createObjectURL 对 Blob 的编码支持(SDK 50 alpha)。
4.2 异步 API 与错误类型
- SDK 56(56.0.0):新增
AbortSignal.timeout、AbortSignal.any与DOMException(源码见 src/winter/AbortSignal.ts、src/winter/DOMException.ts,均配有 native 平台测试用例); - SDK 54 preview.7:新增
structuredClone内建(源码位于src/winter,测试见structuredClone.test.ios.ts); - SDK 54 preview.0:新增
TextDecoderStream与TextEncoderStream(对应 src/winter/TextDecoderStream.ts); - SDK 52 preview.0:为原生端补充最小
TextDecoder;Unpublished 段对其做了性能重写(Rewrite the TextDecoder implementation to increase decoding performance); - SDK 52 preview.0:polyfill
Symbol.asyncIterator,并为全局FormData补充set/delete/get/has/forEach/entries/keys/values/[Symbol.iterator]原型方法。
4.3 模块级与导入相关
import.meta.url:Unpublished 段修复其开启transform.inlineRequires后为null的问题(此前 57.0.7 已修复同步执行后读取为 null 的问题);globalThis.__ExpoImportMetaRegistry(SDK 53 preview.1)与 src/winter/ImportMetaRegistry.ts 对应;- Unpublished 段新增
expo/internal/bundle-origin(getBundleOrigin)与getBundleUrl内部工具,并从 bundle URL 推导getDevServer; - 全局类型链在 SDK 55 preview.9 被修正为
expo -> expo-modules-core/types -> ./build/global,不再依赖types="node",并修复缺失@types/node时的module类型声明。
这些能力共同支撑了"同一份 JS 代码在 iOS/Android/Web 行为一致"的目标,也是 SDK 52 起 useEvent/useEventListener hooks(面向 EventEmitter 对象)得以提供的基础之一。
五、iOS 原生层演进:Factory 化与 UIKit Scene 生命周期
5.1 从 AppDelegate 到 ReactNativeFactory
SDK 53~56 期间,iOS 宿主层经历了一次结构性重组:
- SDK 53 preview:将
AppDelegate集成从expo-modules-core迁入expo包; - SDK 55 preview.0(Breaking):移除
EXAppDelegateWrapper类(弃用提示在 54.0.3 已加入);移除bindReactNativeFactory函数;不再要求继承ExpoAppDelegate即可使用ExpoReactNativeFactory; - SDK 56 preview.0:
ExpoReactNativeFactory切换到新的AppContext.setRuntimeAPI; - Unpublished 段:新增
ExpoBundleConfiguration以从归一化 bundle URL 推导RCTBundleConfiguration,并让 dev server 端口在运行时从RCTMetroPortInfo.plist 键解析——使未安装 expo-dev-client 的 bare 项目能连上自己的 Metro 实例而非默认 8081。
5.2 UIKit Scene 生命周期(iOS 27 SDK 兼容)
Unpublished 段引入了一组面向 iOS 27 SDK 的变更:应用改用 UIKit scene-based 生命周期启动;ExpoAppDelegate 在扩展(extensions/widgets)中被标记为不可用(ExpoAppSceneDelegate);修复 scene 生命周期下冷启动 URL 时 Linking.getInitialURL() 返回 null、deep link 被丢弃的问题。
5.3 稳定性修复与 Swift 6
- Hermes JSI 重载崩溃:Unpublished 段修复了重载时两个重叠的
RCTHostruntime 回调共享EXReactNativeFactory的 app context ivar、导致一个回调在另一个 runtime 上装饰对象的问题; - 调度器生命周期:57.0.5 将 React runtime scheduler 通过 weak handle 传给
ExpoModulesCore,避免重载期间向已销毁的 scheduler 派发任务; - Swift 6:SDK 55 preview.0 全面采纳 Swift 6;
- macOS:SDK 54 preview.13 为 macOS 增加 ExpoAppDelegate 与 AppDelegate subscribers 支持(55 preview 起移除 react-native-macos 0.79 的 workarounds)。
六、Android 原生层演进:Host 包装类退场与 New Architecture 对齐
Android 侧的主线与 iOS 对称,核心是移除旧架构时期的包装类、向 React Native 新架构的宿主 API 对齐:
- SDK 55 preview.0(Breaking):删除
ReactNativeHostWrapper类;55.0.0 移除遗留 autolinking 集成;SDK 53 起改用 Expo Modules Gradle Plugin; - ReactActivityDelegateWrapper:从生命周期原子性(53.0.19)、Android 10 的
onWindowFocusChangedNPE(53.0.18)、低内存重建 Activity 时onActivityResult的传播(46.0.0-alpha.0)、onPause的AssertionError(52.0.14)到 54.0.21 移除 edge-to-edge 逻辑——这一包装类被反复修补,最终在新架构下逐步退场; - Edge-to-edge 兼容:54.0.19 修复
enableMinifyInReleaseBuilds为 true 时 edge-to-edge 失效的问题;54.0.0-preview.0 修复 RN 的enableEdgeToEdgeGradle 属性被忽略的问题; - bridgeless 模式:SDK 51 起为
ReactNativeHostHandler增加 bridgeless 支持、onReactInstanceException()等钩子,供 expo-updates 处理异常与错误恢复(onDidCreateDevSupportManager); - R8 混淆兼容:51.0.0-beta.0 修复 R8 开启时
getReactHost抛NoSuchMethodException的问题。
七、DOM Components 与 expo/dom:WebView 内运行 Web 组件
7.1 诞生与默认化
- SDK 52(52.0.0-preview.0):引入 DOM Components 初始版本与
expo/dom模块,同时提供useDOMImperativeHandle、matchContentsprop、useExpoDOMWebView等能力; - SDK 56(56.0.0-preview.0,Breaking):
@expo/dom-webview成为 DOM 组件的默认 WebView 实现(配套修复其initialProps不稳定与重载问题); - Unpublished 段:修复
expo/dom与expo/dom/internal子路径导出的平台解析。
7.2 能力补全
DOM Components 在 SDK 52 周期内快速成型:52.0.0-preview.5 加入 router 支持;52.0.0-preview.19 加入 EAS Updates 支持;52.0.37 修复 EAS Update 下 DOM Components 的支持;52.0.30 增加 debug 期零高度 DOM 组件的告警样式;process.env.EXPO_DOM_HOST_OS(SDK 55 preview.0)用于在 DOM 组件内检测其原始宿主平台;$$EXPO_INITIAL_PROPS(54.0.5)用于判断是否运行在 DOM 组件内;Unpublished 段修复 DOM 组件在 WebView 加载期间丢弃 prop 更新的问题。
八、异步模块(async-require)与 Metro 集成
- SDK 54(54.0.0-preview.0):默认启用 async requires(
Enable async requires by default),配合expo/internal/async-require-module(54.0.21)作为@expo/metro-config的asyncRequireModulePath; - SDK 55 preview.0:
async-require增加unstable_createWorkerhelper;56.0.10 让 async require 返回带同步 bailout 的 thenable; - Unpublished 段:修复 async imports 返回的不是完整 promise 形状的 thenable 问题;修复 Metro 断开后 async chunk 加载时误调
window.location的问题; - Metro 版本:自 SDK 54 起切换到
@expo/metro封装包,后续持续升级(54.1.0 → 56.0.0 → 56.1.0,对应 metro 0.83.x → 0.84.x)。
九、破坏性变更汇总与升级注意点
将 CHANGELOG 中所有 🛠 Breaking changes 归纳如下,升级前建议逐条核对:
| 版本 | 破坏性变更 |
|---|---|
| Unpublished | Node.js 最低版本提升至 ^22.13.0 |
| 56.0.0-preview.10 | 从 expo 依赖中移除 @expo/vector-icons(仅影响依赖它被间接带入的项目) |
| 56.0.0-preview.0 | iOS/tvOS 最低 16.4、macOS 13.4;expo/fetch 成为默认 fetch;DOM 组件默认用 @expo/dom-webview |
| 55.0.0-preview.0 | 移除 iOS EXAppDelegateWrapper;移除 Android ReactNativeHostWrapper |
| 54.0.0-preview.5 | 默认启用 RN Strict TypeScript API typings(除非使用内部 API,否则非破坏) |
| 53.0.0-preview.0 | 升级 RN 至 0.78;expo/types 导出变更;移除 DevToolsPluginClient 的 protected eventEmitter 与 fbemitter |
| 51.0.0-beta.0 | 放弃 RN 0.73 及更低版本 |
| 50.0.0-alpha.2 | 放弃 Android SDK 21/22;移除遗留 hashAssetFiles |
| 50.0.0-alpha.1 | 移除 Logs 模块导出与 logUrl 端点 |
| 47.0.0-alpha.1 | 移除 expo-error-recovery 与 exp.errorRecovery 根组件 props;iOS 13.0 起弃用 iOS 12 |
| 49.0.0-alpha.1 | 移除 EXPO_USE_LOCAL_CLI,本地 CLI 统一走 expo 包 |
配套源码佐证:当前包的 exports 映射 以 expo-source 条件指向 src/(对应 SDK 52 起"以 src 目录作为 Metro 目标"的变更),Expo.ts 则集中展示了核心导出面——registerRootComponent、expo-modules-core 各类重导出、useEvent/useEventListener 等 hooks,以及 reloadAppAsync(SDK 51 preview.11 引入)。
十、如何将 CHANGELOG 用于日常研发
- 升级评估:先扫 Unpublished 与目标版本的 Breaking changes 与 Notices,再核对 RN 支持版本与平台最低版本,最后检查自己使用的 API 是否在移除列表(如
EXAppDelegateWrapper、@expo/vector-icons间接依赖)。 - 能力溯源:想确认某个 Web API 在当前 SDK 是否可用,直接检索 CHANGELOG 中对应的 "Add ... to native platforms" 条目与 src/winter 目录,源码与测试(如
AbortSignal.test.native.ts、DOMException.test.native.ts、structuredClone.test.ios.ts、TextDecoderStream.test.ios.ts)即可给出确定性答案。 - 行为变更追踪:
expo/fetch这类高频模块,其行为(如流式取消、压缩解码、Response.clone)的每一次调整都能在 CHANGELOG 中找到对应条目,配合 fetch.ts 的实现阅读,可快速定位升级后行为差异的原因。
这份 CHANGELOG 既是历史档案,也是当前仓库开发状态的实时窗口——持续关注其 Unpublished 段,就能始终领先版本发布一步。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.2 K634
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown300
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java101
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java60
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript60
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python280