React Native 0.81 引入了 iOS 预编译,将主要使用 React Native 的项目编译时长缩短高达 10 倍。
iOS 编译速度即将迎来飞跃。在 React Native 0.81 中,Expo 和 Meta 正为 iOS 带来预编译的 React Native,能让依赖较少的 app 编译时间缩短高达 10 倍。
一直以来,编译 React Native 及其依赖库都是编译时长的主要来源。几年前,在 Android 上已经通过默认使用预编译库(需要时可选源码编译)解决了这个问题。那么,iOS 呢?
在 React Native 0.81,你也可以选择 iOS 端预编译版的 React Native 及依赖库。 这要归功于 Expo 和 Meta 的合作 —— Riccardo Cipolleschi 与 Christian Falch 从一月起推动该项目,如今已可在 React Native 0.81 / Expo SDK 54 体验。在下文中,Christian 会介绍预编译是如何构建与运作的、你如何在自己的库中采用这种方式,及其未来方向。
预编译 React Native 有哪些好处?
最重要的好处就是所有 React Native 开发者都将感受到编译时间的显著缩短。 你无需每次干净编译时都重新编译 React Native 了,只需每个 release 编译一次。如果想改内部代码,依然可源码编译。这不仅节省了大量编译时间、降低了耗电,还能延长电池续航,甚至让环境更友好——我们测试发现,RNTester 项目干净编译时长从约 120 秒降至 10 秒(M4 Max 机器,具体视硬件而异)。虽然大部分应用因其它依赖库仍需编译,不大可能实现 10 倍提速,但大型项目可见到明显提速,小型项目(React Native 占比更高)提升会更突出。
我们还期望预编译能让与现有原生 app 集成(brownfield)更容易。 以前往大型原生项目引入 react-native 依赖,会让整个长时间编译流程变迁复杂,大型代码库里其它开发者也常常不太欢迎依赖与构建管道的引入。
最后,本项工作也为未来移除 CocoaPods、迁移到 Swift Package Manager (SPM) 做准备。 CocoaPods 正逐步关停,新 podspec 将于 2026 年 12 月 2 日起不再接受,React Native 因此会迁往 SPM。点击了解更多计划。
iOS 预编译 React Native 是如何实现的?
第一步是预编译 React Native 所依赖的第三方库,然后再预编译 React Native 主体。
预编译第三方依赖库
我们首先用 JavaScript 为 React Native 的第三方依赖创建了一个简单的构建系统,由 CI 构建服务器负责实际构建。最终生成的 ReactNativeDependencies.xcframework 是通过自动生成的 Swift 包构建,并编译为 XCFramework,支持 iOS、MacOS、mac-catalyst、tvOS、VisionOS(覆盖所有模拟器)。资源文件如隐私相关 bundle 也会合入,保障应用审核过关。
预编译 React Native 主体
React Native 主体的 XCFramework 预编译管道,主要复用了依赖库预编译的思路,但要处理 50 多个 CocoaPods podspec 文件的结构,稍加不同设置与调整。用 Ruby 写了辅助函数后,现在可用大型 Swift 包分别构建是否预编译的所有必需归档(适用于 iOS、iOS 模拟器、Mac-Catalyst)。
我们还用 Swift 写了一组辅助函数和类,便于配置与构建 Swift 包,实现头文件准确无误。甚至为了避免源码变化,专门做了准备脚本,在安装后的 app 目录构造了部分 Podfile 头文件结构。
预编译依赖库与 React Native 主体的 XCFramework 都是每晚构建并随新版本 React Native 发布。二进制托管在 Maven 仓库,本地安装 React Native 时自动下载。React Native 依然支持源码构建,便于库开发与调试。当前预编译方案属于可选(未来会默认开启),方便想继续源码编译及内联修改的开发者。
制作 ReactNativeDependencies Swift 包的副作用之一,是不再需要在每个引用 Folly 的 podspec 单独配置 Folly。这得益于我们为 Folly 添加了可选配置文件,通过预编译与安装版 Folly 配置自动加载(懂的人自然懂~)。
“前后”对比示例
以下是我们常规自测的编译时间对比,供参考:
Expo Go:启用与未启用预编译 React Native 的对比
RNTester:启用与未启用预编译 React Native 的对比
启用 React Native 的预编译二进制文件对开发者来说是巨大的进步。以后再也不需要每个开发者每天都重复构建相同的代码。展望未来,我们期望能够在 iOS 上预编译我们的 Expo 模块和 React Native 社区库,这将使构建时间成为过去。
预编译 iOS 的 Expo SDK
如果你在这个领域待过一段时间,你可能会问自己——“等等,他们以前不是提供 iOS 的预编译 Expo 模块吗?后来发生了什么?”这不是记忆错误,你是对的。我们曾经这样做过,后来没有。我们预计为 React Native 及其库进行的预编译工作,将使我们能够在不久的将来重新启用 Expo 模块的预编译。我们将开始在使用 CocoaPods 时使用 Swift 包,最终当 CocoaPods 被弃用时,转为仅使用 Swift 包。
我们将始终支持从源码构建,可能使用与 Expo 模块在 Android 上支持的 buildFromSource API 相同的方法。
我可以预编译我的 Expo / React Native 库吗?我应该这样做吗?
如果你是库的作者,可能很快就需要开始考虑这一点,因为 CocoaPods 将被弃用并从 React Native 中移除(参见 discussions-and-proposals#587)。在此之前,我们将为 Expo 和 React Native 库提供一个清晰的迁移路径,其中包括使用 Swift 包进行分发。对于所有的库开发者来说,这将是一个绝佳的机会,可以刷新和简化他们的构建流水线!
你也可以选择继续以源码形式分发你的库,但预计你的用户在看到编译速度的好处后,将开始要求预编译二进制文件。
我何时以及如何能使用这个功能?
在 React Native 0.80 中,你可以选择使用 React Native iOS 依赖项(例如 GLog 和 Folly)的预编译 XCFramework(了解更多)。
在 React Native 0.81 中,你也能够使用 React Native 及其 iOS 依赖项的预编译版本。你可以在 React Native 0.81 上试用这一功能(在撰写本文时是“rc”版本):
使用预编译二进制文件目前是可选的,可以通过在运行 pod-install 时传递正确的环境变量来启用:
RCT_USE_PREBUILT_RNCORE=1 RCT_USE_RN_DEP=1 bundle exec pod-install
我们计划在 SDK 54 中使预编译的库和依赖项成为默认配置(预计在八月末或九月初),这将使用 React Native 0.81。