连续原生生成 (CNG)

编辑

了解如何使用连续原生生成 (CNG) 和 Prebuild 管理您的原生项目。


单个原生项目本身在维护、扩展和更新方面是复杂的。在跨平台应用中,您有多个原生项目,必须保持它们的维护,并与最新的操作系统发布保持同步,以避免在任何第三方依赖项上落后太多。

随着您的原生项目的增长,来自第三方依赖项的复杂性增加,升级变得复杂,并减缓了开发人员的进展。这会阻碍高级原生功能的添加,导致应用程序的功能下降。在跨平台应用中,这种复杂性在每个平台上都被乘以。

为了应对这一点,我们引入了连续原生生成的概念。与其一次性创建原生项目并在整个代码库生命周期内维护这些原生项目的自定义,短期的原生项目仅在需要时生成,例如在调试或构建时。这些项目是从标准模板以及定义如何自定义模板的配置或自定义代码生成的。结果是一个可以编译成原生应用的原生项目,开发人员可以根据需要进行任何自定义。然而,开发人员仅需维护其自定义的定义,而不是所有原生项目代码。

CNG 在 React Native 应用中的应用

React Native 应用可以通过使用 Prebuild 来自动化升级、安装或卸载库、应用白标自定义、在多个应用之间共享配置、减少 孤立代码 等来使用 CNG

Expo 作为框架 通过结合以下工具启用 CNG:

  1. app config 文件。
  2. 传递给 npx expo prebuild 命令的参数。
  3. 项目中安装的 expo 版本及其对应的 prebuild 模板
  4. 自动链接,用于链接 package.json 中发现的 原生模块
  5. 原生订阅者,用于减少入口点文件(如 MainApplicationAppDelegate)中的原生代码副作用。
  6. EAS 凭据,用于对额外目标和授权进行代码签名。

最终结果是一个工作流程,开发人员可以使用应用配置表达任何原生应用,并通过运行 npx expo prebuild 进行持续生成。

用法

Prebuild 可以通过运行以下命令来使用:

Terminal
npx expo prebuild

这会创建用于运行您的 React 代码的 androidios 目录。如果您手动修改生成的目录,则在下次运行 npx expo prebuild --clean 时您可能会丢失更改。相反,使用 config plugins,这些函数在预构建期间对原生项目执行修改。

我们强烈建议出于 常见问题 部分列出的理由使用 Prebuild,但该系统是 完全可选的,您可以随时停止使用它。

与 EAS Build 的用法

如果您的项目不包含 androidios 目录,EAS Build 将运行 Prebuild 以在编译之前生成这些原生目录。这是使用 npx create-expo-app 创建的任何项目的默认行为。

对于具有 androidios 目录的项目,EAS Build 不会运行 Prebuild,以避免覆盖您对原生目录所做的任何更改。

如果通过 本地编译 (运行 npx expo prebuild,或 npx expo run:androidnpx expo run:ios)来排除您的应用故障,您仍然可以使用 Prebuild 与 EAS Build 在构建过程中生成新的原生目录。创建新项目时,androidios 目录会自动添加到 .gitignore 中,但如果您需要手动添加它们,您可以将它们添加到 .gitignore.easignore 文件中:

.gitignore
1/android
2/ios

与 Expo CLI 运行命令的用法

您可以通过运行以下命令在本地执行原生构建:

Terminal
# 构建您的原生 Android 项目
npx expo run:android

# 构建您的原生 iOS 项目
npx expo run:ios

如果原生目录缺失,npx expo prebuild 将为特定平台运行一次。在后续使用这些 run 命令时,手动运行 npx expo prebuild --clean 以确保原生代码与您的本地配置保持新鲜同步。

平台支持

Prebuild 目前支持 Android 和 iOS。因为网页无需为网页生成原生项目,所以不需要 Web 支持,并且 Web 应用程序在浏览器中运行。使用 --platform 选项为单个平台运行预构建:

Terminal
npx expo prebuild --platform ios

依赖项

Prebuild 首先通过与每个 Expo SDK 版本相对应的模板初始化新的原生项目。这还与特定的 React 和 React Native 版本保持一致。当您的项目的 React 和 React Native 版本与指定在 模板的 package.json 中的预期版本不同时,运行 npx expo prebuild 时会看到警告。

您可以使用 --skip-dependency-update 选项跳过更改 npm 包版本:

Terminal
npx expo prebuild --skip-dependency-update react-native,react

包管理器

依赖项 更改时,Prebuild 将使用当前项目中使用的包管理器重新安装库(这是从锁文件中推断的)。您可以通过提供 --npm--yarn--pnpm 中的一个来强制使用特定的包管理器。

通过传递 --no-install 命令,可以跳过所有安装,这对于快速测试生成非常有用。

清理

--clean 选项会在生成之前删除任何现有的原生目录。在没有 --clean 选项的情况下重新运行 npx expo prebuild 将在现有文件上叠加更改,这样更快,但在某些情况下可能不会产生相同的结果。

例如,一些配置插件不是幂等的。当一个项目利用多个“危险修改器”向应用程序的代码添加正则表达式更改时,可能会导致意外行为。这就是为什么使用 --clean 选项是使用预构建命令最安全的方法,并且在大多数情况下通常推荐。

使用 --clean 选项

当使用 --clean 选项时,如果您的 git 代码库中有任何未提交的更改,您将会收到警告,因为此选项将删除并重新创建您所有的原生项目文件。在 CI 中遇到时,这个提示是可选的,并将被跳过。您可以通过启用环境变量 EXPO_NO_GIT_STATUS=1 来禁用此检查。

在某些情况下,开发人员可能希望经常在工作流程之间切换。例如,您可能想在 Android Studio 和 Xcode 中原生构建自定义功能,然后将该功能移入本地配置插件。

模板

您可以通过 config plugins 自定义生成原生目录的方式。许多配置插件已经存在,以进行很多修改,社区库通常会自己提供插件。您可以 查看一些流行插件的列表 以获取更多信息。

Prebuild 从模板文件开始,随后通过配置插件进行修改。模板文件基于 Expo SDK 版本,并来自 npm 包 expo-template-bare-minimum。您可以通过向 npx expo prebuild 命令传递 --template /path/to/template.tgz 来更改使用的模板。这通常并不推荐,因为 @expo/prebuild-config 中的基本修改器对模板文件做了某些不记录的假设,因此维护您的自定义模板可能会很棘手。

注意: 在所有包都从私有注册表下载且阻止对 npm 公共注册表访问的网络环境中,必须将本地可用的模板传递给预构建命令。了解有关使用默认模板的本地版本的更多信息

副作用

npx expo prebuild 在生成 androidios 目录之外会执行几个副作用。正在进行工作以消除这些副作用 — 理想情况下,运行 npx expo prebuild 将生成 Android 和 iOS 项目,并保持项目的其余部分不变。

除了生成原生目录外,prebuild 还会进行以下修改:

  • 修改 package.json 中的 scripts 字段,替代 expo start --androidexpo start --ios,使用 expo run:androidexpo run:ios
  • 修改 package.json 中的 dependencies 字段

scripts 字段的便利更改是唯一改变开发人员在预构建前后工作的副作用。所有其他更改可以保留并提交到 git,以最小化运行预构建时的差异。

可选性

Prebuild 是可选的,并与所有 Expo 工具和服务无缝集成。对于那些手动管理原生项目的现有 React Native 项目,请不要使用 npx expo prebuild,因为这可能会覆盖任何手动自定义。开发人员可以继续对他们的原生项目进行(直接修改)(/more/glossary-of-terms/#bare-workflow),同时采用其他 Expo 工具和工作流程。后来,他们可以将他们的手动自定义迁移到应用配置和/或配置插件中,然后采用 CNG。

Expo 提供的一切,包括 EAS、Expo CLI 和 Expo SDK 中的库,都是为了完全支持裸 React Native 项目,因为这是使用 npx expo prebuild 的项目支持的最低要求。唯一的例外是 Expo Go 应用,该应用只能加载任意的 React Native 项目,前提是它们包含 Expo Go 运行时中缺失的原生代码的 JavaScript 回退。

常见问题

CNG

CNG 如何帮助项目升级?

未使用 连续原生生成 的 React Native 开发人员报告称,将他们的应用程序升级到最新版本的 React Native 是库的主要弱点,正如 React Native 调查 (2022) 所示。

使用 CNG 时,升级过程仅涉及升级 npm 依赖项、应用配置,并重新运行 npx expo prebuild --clean

React Native 库作者如何采用 CNG?

React Native 库作者可以通过几种方式采用 CNG。这取决于他们库的复杂性。以下是几种情况:

  • 没有原生代码或配置副作用:如 react-native-blurhash 这样的库没有原生代码或配置副作用,可以无缝集成到 npx expo prebuild 中。它们可以依赖 Node 模块解析,无需任何额外配置。

  • 有原生代码且安装后无需额外设置:具有原生代码的库通常可以通过 Expo 自动链接 自动安装和链接,该链接在构建原生应用之前运行。

  • 额外的配置副作用和设置:需要额外配置副作用的库可以通过为其库创建 Expo 配置插件 来采用 CNG。这种方法使库作者能够自动添加权限消息等值到 Info.plist,或在 Xcode 项目中注入目标。

  • 依赖于原生运行时钩子的库:依赖于特定原生运行时钩子的库,例如通过 AppDelegateMainActivityMainApplication 等拦截初始启动 URL,可以利用 生命周期监听器 在 Expo 模块 API 中。这些生命周期监听器允许通过 Expo 自动链接应用这些运行时钩子,而不是通过修改这些标准原生项目文件,从而消除对配置插件的需求。

许多复杂的库和服务已经通过 Expo Prebuild 支持 CNG,例如 MapBoxOneSignalStripeReact Native Firebase

库作者采用 CNG 并不是使用 npx expo prebuild 的先决条件。如果库作者尚未采用 CNG,开发人员仍然可以通过创建本地 配置插件 来修改原生生成管道,从而使用 npx expo prebuild。这种灵活性使得 CNG 对 React Native 社区内所有开发人员都可及且有利。

CNG 限于 React Native 项目吗?

不,CNG 是一个多功能模式,可以应用于任何原生项目。虽然 Expo Prebuild 是一个专门为 React Native 项目实现 CNG 的工具,但这个概念本身并不限于此框架。

社区如何使用 CNG?

以下是一些将困难的原生功能转换为简单配置文件的社区示例,这使开发人员能够构建更强大的应用,而不妨碍迭代速度:

  • iOS Safari 扩展:在这里,为 iOS 创建 Safari 扩展的过程,这是一项著名的难以实现的功能,简化为几行 JSON。

  • iMessage 贴纸应用:这个 Expo 配置插件可以从一个 JSON 对象生成整个 iMessage 贴纸应用。

  • 跨平台端到端测试:配置原生应用以支持 Detox 的 E2E 测试只需一行。

  • 整个 Firebase 套件:在这里可以看到整个原生 Firebase 套件,从多个 IDE 的多步骤原生配置过程减少到基础的 JSON 配置。

  • 跨平台主屏幕小部件:这个 Expo 配置插件可以为 Android 和 iOS 生成主屏幕小部件。

  • 通知扩展和代码签名:这个 Expo 配置插件在 iOS 上生成通知扩展目标,并增强 EAS 凭据服务以保持零配置代码签名正常工作。

  • Apple App Clips:这个 Expo 配置插件将生成 Apple App Clip 的过程从多步骤的过程(涵盖多个目标)缩减为一行 ["react-native-app-clip", { "name": "My App Clip" }]

在任何时候,这些功能都可以轻松添加和移除,而没有任何副作用。CNG 使开发人员能够快速实验复杂功能并对其进行迭代,而无需担心项目的长期维护成本或潜在的孤立代码。

CNG 是否可以用于 Android 和 iOS 以外的操作系统?

绝对可以!CNG 是一个可以应用于任何操作系统的抽象概念。尽管 Expo Prebuild 正式为 Android 和 iOS 实现了 CNG,但它也为开发人员提供了抽象平台支持,以创建对此其他平台的实现。

使用 Expo 是 CNG 的要求吗?

根本不是。CNG 是一个开放模式,任何社区都可以采用。我们将这一模式抽象定义,以帮助其他社区了解如何将 CNG 采纳到他们自己的项目中。

CNG 与静态站点生成(SSG)等 Web 开发模式相比如何?

CNG 与 SSG 共享相似之处,因为它是从一组输入生成项目。然而,CNG 与 SSG 的输出不同。它生成原生运行时代码,而不是静态网站代码。这意味着原生项目是按需生成的,一旦原生项目编译成原生应用,生成的源代码和配置就会被丢弃。

是否可以在现有的棕地项目中使用 CNG?

CNG 旨在持续管理原生项目的整个状态。因此,它不适用于现有棕地项目。但是,您可以使用 CNG 生成一个新的原生项目,然后将其集成到现有的棕地项目中。

Prebuild

Expo Prebuild 简化了 CNG 处理。以下是 Prebuild 解决的 React Native 开发周期中的一些问题:

Prebuild 如何帮助实现合理的项目升级?

构建原生代码需要熟悉平台的工具,这使得学习曲线陡峭。在跨平台开发中,由于多平台的存在,这种挑战加剧。如果必须在特定平台的原生代码中实现许多功能,那么跨平台工具就没有帮助。

在引导原生应用时,有一些您可能不理解的初始代码和配置。尽管您不负责维护它们,但最终您需要了解这些代码,以安全地升级您的应用。这个挑战往往使开发人员错误地升级或者开始一个新应用,复制现有源代码。

通过 Prebuild,升级过程更接近于升级纯 JavaScript 应用。提升 package.json 中的版本,重新生成原生项目,您应该准备好继续开发。

Prebuild 如何简化跨平台配置?

跨平台配置,如应用图标、名称、启动画面等,必须在原生代码中手动实施。这些实现通常在每个平台上相差甚远。

通过 Prebuild,跨平台配置在配置插件层面处理,开发人员只需设置一个值,如 "icon": "./icon.png",即可处理所有图标生成。

我该如何管理 Prebuild 的依赖副作用?

许多复杂的原生包在安装和 自动链接 之外需要额外的设置。例如,摄像头库需要将权限设置添加到 AndroidManifest.xml 中(用于 Android)和 Info.plist 中(用于 iOS)。这种额外的设置可以视为包的配置副作用。将所需副作用代码粘贴到您的项目的原生文件中可能导致难以解决的原生编译错误,而且这也是您现在擅自拥有并维护的代码。

通过 Prebuild,库作者比任何人都更了解如何配置他们的库,可以创建一个可测试和版本化的脚本,称为 配置插件,以自动添加其库所需的配置副作用。这意味着库的副作用可以更具有表现力、强大和稳定。对于原生代码副作用,我们还提供了默认 prebuild 模板 中标准的 Android 生命周期监听器AppDelegate 订阅者

Prebuild 如何帮助解决孤立代码问题?

当您卸载一个包时,您必须确保完全删除使该包工作所需的所有副作用。如果您遗漏了任何内容,则会导致孤立代码,您无法追踪到任何特定包,代码会堆积,让您的项目更难理解和维护。

通过 Prebuild,唯一的副作用是项目的 Expo 配置中的 配置插件 (app.json),当相应的节点模块已被卸载时,它将抛出错误,这意味着许多孤立配置会大大减少。

何时 Prebuild 可能不适合某个项目

以下是 Expo Prebuild 可能适合某个特定项目的一些原因:

平台兼容性

Prebuild 仅可用于 Expo SDK 支持的原生平台。目前仅支持 Android 和 iOS。除了 Web,该平台不需要 npx expo prebuild,因为它使用浏览器而不是自定义的原生运行时。

直接更改比模块化和自动化更快

所有原生更改必须与原生模块一起添加(使用 React Native 的内置原生模块 API 或 Expo 模块 API)和配置插件。这意味着如果您想快速向项目中添加原生文件以进行实验,那么您可能会更好地运行预构建,然后手动添加文件,然后逐步找到与 monorepo 的系统集成。我们计划通过向 Expo 自动链接 添加功能,以便在构建之前找到原生项目文件并链接它们,加速此过程。

如果您想修改配置,例如 gradle.properties 文件,您需要编写一个插件(示例)。这可以通过帮助插件库轻松实现自动化,但如果您频繁需要执行这项操作,速度会稍慢。

社区对配置插件的支持

并非所有包都已支持 Expo Prebuild。如果您发现某个库在安装后需要额外设置但还没有配置插件,我们建议您打开一个 Pull Request 或问题,以便维护者了解此功能请求。

许多包,例如 react-native-blurhash,不需要超过由 自动链接 处理的任何其他原生配置,因此不需要配置插件。

其他包,例如 react-native-ble-plx,确实需要额外设置,因此需要与 npx expo prebuild 一起使用配置插件(在这种情况下,有一个外部插件 @config-plugins/react-native-ble-plx)。

另外,我们还拥有一个 非树配置插件 的库,提供尚未采用该系统的流行包的插件。可以将其视为 TypeScript 的 DefinitelyTyped。我们更倾向于包本身提供它们的配置插件,但如果它们尚未采用该系统,社区仍然可以使用该库中列出的包。