捆绑 Hermes
本页面概述了 Hermes 和 React Native 是如何构建的。
如果您正在寻找有关如何在您的应用中使用 Hermes 的说明,可以在此页面找到相关指南:使用 Hermes
请注意,本页面旨在进行技术深入探讨,面向那些在 Hermes 或 React Native 之上构建扩展的用户。React Native 的普通用户通常无需了解 React Native 与 Hermes 如何交互的深层信息。
什么是“捆绑式 Hermes” (Bundled Hermes)
从 React Native 0.69.0 开始,每个 React Native 版本都将随附一个配套的 Hermes 版本。我们将这种分发模式称为捆绑式 Hermes (Bundled Hermes)。
从 0.69 版本起,您将始终拥有一个与每个 React Native 版本同步构建和测试过的 JS 引擎,供您直接使用。
我们为何转向“捆绑式 Hermes”
从历史上看,React Native 和 Hermes 遵循两个不同的发布流程,并采用不同的版本号。这种具有不同发布周期和不同版本号的做法在开源生态系统中造成了困惑,因为很难确定某个特定的 Hermes 版本是否与特定的 React Native 版本兼容(例如,您需要明确 Hermes 0.11.0 仅与 React Native 0.68.0 兼容,以此类推)。
Hermes 和 React Native 共享 JSI 代码(Hermes 在此,React Native 在此)。如果这两份 JSI 代码不同步,Hermes 的构建版本将无法与 React Native 的构建版本兼容。您可以阅读更多关于此 ABI 不兼容问题的信息。
为了克服这个问题,我们扩展了 React Native 的发布流程,使其能够下载并构建 Hermes,并确保在构建 Hermes 时仅使用一份 JSI 副本。
得益于此,我们可以在发布 React Native 版本的同时发布一个 Hermes 版本,并确保我们构建的 Hermes 引擎与我们正在发布的 React Native 版本完全兼容。我们随发布的 React Native 版本一同提供该版本的 Hermes,因此得名捆绑式 Hermes。
这将如何影响应用开发者
正如引言中所述,如果您是一名应用开发者,此更改不应直接影响到您。
为了保持透明,以下段落描述了我们在底层所做的更改并解释了一些基本原理。
iOS 用户
在 iOS 上,我们迁移了您所使用的 hermes-engine。
在 React Native 0.69 之前,用户需要下载一个 pod(您可以在此处找到 podspec)。
在 React Native 0.69 上,用户将使用定义在 react-native NPM 包中 sdks/hermes-engine/hermes-engine.podspec 文件里的 podspec。该 podspec 依赖于我们上传到 Maven 和 React Native GitHub Release 的预构建 Hermes 压缩包,这是 React Native 发布流程的一部分(例如,查看此版本的资源)。
Android 用户
在 Android 上,我们将以以下方式更新默认模板中的 android/app/build.gradle 文件:
dependencies {
// ...
if (enableHermes) {
+ implementation("com.facebook.react:hermes-engine:+") {
+ exclude group:'com.facebook.fbjni'
+ }
- def hermesPath = "../../node_modules/hermes-engine/android/";
- debugImplementation files(hermesPath + "hermes-debug.aar")
- releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
在 React Native 0.69 之前,用户会从 hermes-engine NPM 包中使用 hermes-debug.aar 和 hermes-release.aar。
在 React Native 0.69 上,用户将使用 react-native NPM 包中 android/com/facebook/react/hermes-engine/ 文件夹内的 Android 多变体制品 (multi-variant artifacts)。另请注意,我们计划在未来的某个 React Native 版本中彻底移除对 hermes-engine 的依赖。
使用新架构的 Android 用户
由于我们原生代码构建设置的特性(即我们使用 NDK 的方式),使用新架构的用户将从源码构建 Hermes。
这统一了新架构用户在 React Native 和 Hermes 上的构建机制(他们将从源码构建两个框架)。这意味着此类 Android 用户在首次构建时可能会遇到构建性能下降的问题。
您可以在此页面找到优化构建时间并降低对构建过程影响的说明:加速构建阶段。
在 Windows 上构建新架构的 Android 用户
在 Windows 机器上使用新架构构建 React Native 应用的用户,需要执行这些额外步骤以确保构建正常工作:
- 确保 环境配置正确,包含 Android SDK 和 node。
- 使用 Chocolatey 安装 cmake
- 安装以下其中一项:
- Visual Studio 2022 构建工具.
- Visual Studio 22 Community Edition - 仅选择 C++ 桌面开发即可。
- 确保 Visual Studio 命令提示符 配置正确。这是必需的,因为在该命令提示符中配置了正确的 C++ 编译器环境变量。
- 在 Visual Studio 命令提示符中运行
npx react-native run-android来运行应用。
用户还能使用其他引擎吗?
是的,用户可以自由启用/禁用 Hermes(在 Android 上使用 enableHermes 变量,在 iOS 上使用 hermes_enabled)。“捆绑式 Hermes”更改只会影响您构建和捆绑 Hermes 的方式。
从 React Native 0.70 开始,enableHermes/hermes_enabled 的默认值为 true。
这将如何影响贡献者和扩展开发者
如果您是 React Native 的贡献者,或者正在 React Native 或 Hermes 之上构建扩展,请继续阅读,我们将在此解释“捆绑式 Hermes”的工作原理。
捆绑式 Hermes 的底层工作原理是什么?
此机制依赖于在 facebook/react-native 仓库内从 facebook/hermes 仓库下载一个包含 Hermes 源码的压缩包。我们在其他原生依赖项(Folly、Glog 等)上也采用了类似的机制,并将 Hermes 与此设置保持一致。
当从 main 分支构建 React Native 时,我们将获取 facebook/hermes 的 main 分支压缩包,并将其作为 React Native 构建过程的一部分进行构建。
当从发布分支(例如 0.69-stable)构建 React Native 时,我们将改为使用 Hermes 仓库中的一个标签 (tag) 来同步这两个仓库之间的代码。所使用的特定标签名称将存储在发布分支中 React Native 的 sdks/.hermesversion 文件内(例如,这是 0.69 发布分支上的文件)。
在某种意义上,您可以将这种方法视为一种 git 子模块。
如果您在 Hermes 之上进行开发,可以参考这些标签来了解构建 React Native 时所使用的 Hermes 版本,因为 React Native 的版本号包含在标签名称中(例如 hermes-2022-05-20-RNv0.69.0-ee8941b8874132b8f83e4486b63ed5c19fc3f111)。
Android 实现细节
为了在 Android 上实现这一点,我们在 React Native 的 /ReactAndroid/hermes-engine 中添加了一个新的构建任务,负责构建 Hermes 并将其打包以供使用(更多背景请见此处)。
您现在可以通过调用以下命令来触发 Hermes 引擎的构建:
# Build a debug version of Hermes
./gradlew :ReactAndroid:hermes-engine:assembleDebug
# Build a release version of Hermes
./gradlew :ReactAndroid:hermes-engine:assembleRelease
在 React Native main 分支中。
您无需在机器上安装额外的工具(如 cmake、ninja 或 python3),因为我们已将构建配置为使用 NDK 版本的这些工具。
在 Gradle 使用端,我们也发布了一个小的改进:我们将 releaseImplementation 和 debugImplementation 变更为 implementation。这是可能的,因为更新后的 hermes-engine Android 制品具有变体感知能力 (variant aware),能够将引擎的调试版本与应用的调试版本正确匹配。这里不需要任何自定义配置(即使您使用 staging 或其他构建类型/风味)。
但是,这使得模板中必须加入以下行:
exclude group:'com.facebook.fbjni'
这是必要的,因为 React Native 使用非 prefab 方法(即解压 .aar 并提取 .so 文件)来消费 fbjni。而 Hermes-engine 和其他库则使用 prefab 来消费 fbjni。我们正在考虑在未来解决这个问题,届时 Hermes 的导入将仅需一行代码。
iOS 实现细节
iOS 的实现依赖于一系列脚本,这些脚本位于以下位置:
/scripts/hermes。这些脚本包含下载 Hermes 压缩包、解压并配置 iOS 构建的逻辑。如果您将hermes_enabled字段设置为true,它们会在pod install时被调用。/sdks/hermes-engine。这些脚本包含了实际构建 Hermes 的逻辑。它们是从facebook/hermes仓库复制并适配的,以便在 React Native 中正常工作。具体而言,utils文件夹中的脚本负责为所有 Mac 平台构建 Hermes。
Hermes 作为 CircleCI 上 build_hermes_macos 任务的一部分进行构建。该任务将生成一个制品压缩包,当使用已发布的 React Native 版本时,该压缩包会被 hermes-engine podspec 下载(这是为 React Native 0.69 在 build_hermes_macos 中创建的制品示例)。
预构建的 Hermes
如果当前使用的 React Native 版本没有预构建的制品(例如,您可能正在使用 main 分支的 React Native),那么 Hermes 将需要从源码构建。首先,在 pod install 期间会为 macOS 构建 Hermes 编译器 hermesc,然后 Hermes 本身会作为 Xcode 构建流水线的一部分,使用 build-hermes-xcode.sh 脚本进行构建。
从源码构建 Hermes
使用 main 分支的 React Native 时,Hermes 总是从源码构建。如果您使用的是稳定的 React Native 版本,可以通过在使用 CocoaPods 时将 CI 环境变量设置为 true 来强制从源码构建 Hermes:CI=true pod install。
调试符号
默认情况下,Hermes 的预构建制品不包含调试符号 (dSYMs)。我们计划在未来为每个版本分发这些调试符号。在此之前,如果您需要 Hermes 的调试符号,则需要从源码构建 Hermes。构建目录中每个 Hermes 框架旁边都会创建一个 hermes.framework.dSYM。
恐怕这次更改会影响到我
我们想强调的是,这本质上是一个关于 Hermes 在哪里 构建以及代码 如何 在两个仓库之间同步的组织性变更。该更改对我们的用户而言应该是完全透明的。
历史上,我们习惯于为特定的 React Native 版本发布一个 Hermes 版本(例如 v0.11.0 for RN0.68.x)。
有了“捆绑式 Hermes”,您可以改为依赖一个标签,该标签代表了某个特定 React Native 版本发布时所使用的 Hermes 版本。