模块
编辑
了解模块以及在创建配置插件时如何使用它们。
本指南解释了什么是模块和模块插件,它们的工作原理,以及在为您的 Expo 项目创建配置插件时如何有效地使用它们。
使用下面的图表,在本指南中,您将学习配置插件层次结构的最后两个部分:
模块插件
模块插件提供了一种在预构建过程中修改本机项目文件的方法。它们来自 expo/config-plugins 库,并封装了顶级模块(也称为 默认 模块),因为顶级模块是平台特定的并执行一些难以理解的各种任务。
提示: 如果您正在开发需要模块的功能,则应使用 模块插件 而不是直接与顶级模块交互。
可用模块插件
以下模块插件可在 expo/config-plugins 库中使用:
Android
| 默认 Android 模块 | 模块插件 | 危险性 | 描述 |
|---|---|---|---|
mods.android.manifest | withAndroidManifest (示例) | - | 将 android/app/src/main/AndroidManifest.xml 作为 JSON 修改(使用 xml2js 解析) |
mods.android.strings | withStringsXml (示例) | - | 将 android/app/src/main/res/values/strings.xml 作为 JSON 修改(使用 xml2js 解析)。 |
mods.android.colors | withAndroidColors (示例) | - | 将 android/app/src/main/res/values/colors.xml 作为 JSON 修改(使用 xml2js 解析)。 |
mods.android.colorsNight | withAndroidColorsNight (示例) | - | 将 android/app/src/main/res/values-night/colors.xml 作为 JSON 修改(使用 xml2js 解析)。 |
mods.android.styles | withAndroidStyles (示例) | - | 将 android/app/src/main/res/values/styles.xml 作为 JSON 修改(使用 xml2js 解析)。 |
mods.android.gradleProperties | withGradleProperties (示例) | - | 将 android/gradle.properties 作为 Properties.PropertiesItem[] 修改。 |
mods.android.mainActivity | withMainActivity (示例) | 将 android/app/src/main/<package>/MainActivity.java 作为字符串修改。 | |
mods.android.mainApplication | withMainApplication (示例) | 将 android/app/src/main/<package>/MainApplication.java 作为字符串修改。 | |
mods.android.appBuildGradle | withAppBuildGradle (示例) | 将 android/app/build.gradle 作为字符串修改。 | |
mods.android.projectBuildGradle | withProjectBuildGradle (示例) | 将 android/build.gradle 作为字符串修改。 | |
mods.android.settingsGradle | withSettingsGradle (示例) | 将 android/settings.gradle 作为字符串修改。 |
iOS
| 默认 iOS 模块 | 模块插件 | 危险性 | 描述 |
|---|---|---|---|
mods.ios.infoPlist | withInfoPlist (示例) | - | 将 ios/<name>/Info.plist 作为 JSON 修改(使用 @expo/plist 解析)。 |
mods.ios.entitlements | withEntitlementsPlist (示例) | - | 将 ios/<name>/<product-name>.entitlements 作为 JSON 修改(使用 @expo/plist 解析)。 |
mods.ios.expoPlist | withExpoPlist (示例) | - | 将 ios/<name>/Expo.plist 作为 JSON 修改(Expo 更新配置用于 iOS)(使用 @expo/plist 解析)。 |
mods.ios.xcodeproj | withXcodeProject (示例) | - | 将 ios/<name>.xcodeproj 作为 XcodeProject 对象修改(使用 [xcode](https://www.npmjs.com/package/xcode)解析)。 |
mods.ios.podfile | withPodfile (示例 | - | 将 ios/Podfile 作为字符串修改。 |
mods.ios.podfileProperties | withPodfileProperties (示例) | - | 将 ios/Podfile.properties.json 作为 JSON 修改。 |
mods.ios.appDelegate | withAppDelegate (示例) | 将 ios/<name>/AppDelegate.m 作为字符串修改。 |
信息 关于默认 Android 和 iOS 模块的说明:
默认模块由模块编译器提供,以便于常见的文件操作。危险的修改依赖于正则表达式(regex)来修改应用代码,这可能导致构建失败。正则模块的版本控制也很困难,因此应谨慎使用。始终倾向于使用应用代码来修改应用代码,即使用 Expo Modules 原生 API。
模块
配置插件使用 模块(modifiers 的缩写)在预构建过程中修改本机项目文件。模块是异步函数,允许您在不必手动编辑它们的情况下,对平台特定文件(如 AndroidManifest.xml 和 Info.plist 以及其他本机配置文件)进行更改。它们仅在 npx expo prebuild(预构建过程) 的 同步 阶段执行。
它们接受一个配置和一个数据对象,然后修改并将两者作为一个单一对象返回。例如,在本机项目中,mods.android.manifest 修改 AndroidManifest.xml,而 mods.ios.plist 修改 Info.plist。
您并不直接在配置插件中使用模块作为顶级函数(例如 with.android.manifest)。 当您需要使用模块时,您在配置插件中使用 模块插件。这些模块插件由 expo/config-plugins 库提供,并封装了顶级模块函数,幕后执行各种任务。要查看可用模块的列表,请查看 由 expo/config-plugins 提供的模块插件。
默认模块的工作原理及其主要特征
当默认模块解析时,它被添加到应用配置的 mods 对象中。这个 mods 对象与应用配置的其余部分不同,因为它不会被序列化,这意味着您可以在代码生成 期间 执行操作。尽可能使用可用的模块插件,而不是默认模块,因为它们更容易使用。
以下是默认模块工作原理的高层概述:
- 使用
getPrebuildConfig从@expo/prebuild-config读取配置 - 所有 Expo 支持的核心功能通过
withIosExpoPlugins中的插件添加。这包括名称、版本、图标、区域设置等。 - 配置传递给编译器
compileModsAsync - 编译器添加负责读取数据(如 Info.plist)的基本模块,执行命名模块(如
mods.ios.infoPlist),然后将结果写入文件系统 - 编译器遍历所有模块并异步评估它们,提供一些基本属性,例如
projectRoot- 每个模块后,错误处理会检查模块链是否由于无效模块而被破坏
以下是默认模块的一些主要特征:
-
mods被省略在清单中,无法 通过Updates.manifest访问。模块存在的唯一目的是在代码生成期间修改本机项目文件! -
mods可在npx expo prebuild命令期间安全地读取和写入文件。这就是 Expo CLI 如何修改 Info.plist、权限、xcproj 等等。 -
mods是平台特定的,应始终添加到平台特定对象中:app.config.tsmodule.exports = { name: 'my-app', mods: { ios: { /* iOS 模块... */ }, android: { /* Android 模块... */ }, }, };
在模块解析后,每个模块的内容将被写入磁盘。可以添加自定义模块以支持新的本机文件。例如,您可以创建一个模块来支持 GoogleServices-Info.plist,并将其传递给其他模块。
模块插件的工作原理
当执行模块插件时,会传递一个带有附加属性的 config 对象:modResults 和 modRequest。
modResults
modResults 对象包含要修改和返回的数据。其类型取决于正在使用的模块。
modRequest
modRequest 对象包含模块编译器提供的以下附加属性。
| 属性 | 类型 | 描述 |
|---|---|---|
projectRoot | string | 通用应用的项目根目录。 |
platformProjectRoot | string | 特定平台的项目根目录。 |
modName | string | 模块的名称。 |
platform | ModPlatform | 在模块配置中使用的平台名称。 |
projectName | string | (仅限 iOS)用于查询项目文件的路径组件。例如,projectRoot/ios/[projectName]/。 |
创建您自己的模块
例如,如果您想编写一个模块来更新 Xcode 项目的“产品名称”,您将创建一个配置插件文件,使用 withXcodeProject 模块插件。
import { ConfigPlugin, withXcodeProject, IOSConfig } from 'expo/config-plugins'; const withCustomProductName: ConfigPlugin<string> = (config, customName) => { return withXcodeProject( config, async ( config ) => { config.modResults = IOSConfig.Name.setProductName({ name: customName }, config.modResults); return config; } ); }; // 用法: /// 创建一个配置 const config = { name: 'my app', }; /// 使用插件 export default withCustomProductName(config, 'new_name');
插件模块解析
在实现插件时,有两种基本方法需要考虑:
-
在您应用项目中定义的插件:这些插件本地存在于您的项目中,使其容易与应用代码一起自定义和维护。它们非常适合于项目特定的自定义。
-
独立包插件:这些插件作为单独的包存在并发布到 npm。此方法适合于可以跨多个项目共享的可重用插件。
这两种方法都提供了相同的功能来修改您的本机配置,但在结构和导入方式上有所不同。下面的部分解释了每种方法的模块解析是如何工作的。
任何未在下面指定的解析模式都是意外行为,并可能面临破坏性更改。
在您应用项目中定义的插件
对于在您应用项目中定义的插件,您可以通过多种方式直接实现插件:
文件导入
您可以通过创建 JavaScript/TypeScript 文件快速在您的项目中创建插件,并在您的配置中像使用任何其他 JS/TS 文件一样使用它。
app.config.tsimport "./my-config-plugin"my-config-plugin.ts 从配置导入在上述示例中,配置插件文件包含一个最基本的函数:
module.exports = ({ config }: { config: ExpoConfig }) => {};
动态应用配置中的内联函数
Expo 配置对象还支持将函数原样传递给 plugins 数组。这对于测试很有用,或者如果您想在不创建文件的情况下使用插件。
const withCustom = (config, props) => config; const config = { plugins: [ [ withCustom, { /* props */ }, ], withCustom, ], };
使用函数而不是字符串的一个注意事项是,序列化将用函数的名称替换函数。这使得 清单(有点像您应用的 index.html)正常工作。序列化后的配置看起来如下所示:
{ "plugins": [["withCustom", {}], "withCustom"] }
独立包插件
信息 请参阅 创建带有配置插件的模块 以获取逐步指南,了解如何创建独立包插件。
独立包插件可以通过两种方式实现:
1. 专用配置插件包
这些是 npm 包,其唯一目的是提供配置插件。对于专用配置插件包,您可以使用 app.plugin.js 导出您的插件:
app.config.tsimport "expo-splash-screen"node_modulesexpo-splash-screen节点模块app.plugin.js 自定义插件的入口文件buildindex.js 为了 app.plugin.js 被跳过2. 带有伴生包的配置插件
当配置插件是没有 app.plugin.js 的 Node 模块的一部分时,它会使用该包的 main 入口点:
app.config.tsimport "expo-splash-screen"node_modulesexpo-splash-screen节点模块package.json"main": "./build/index.js"buildindex.js 节点解析到此文件插件解析顺序
当您导入插件包时,文件以以下特定顺序解析:
- 包根目录中的 app.plugin.js
app.config.tsimport "expo-splash-screen"node_modulesexpo-splash-screen节点模块package.json"main": "./build/index.js"app.plugin.js 自定义插件的入口文件buildindex.js 为了 app.plugin.js 被跳过- 包的主入口(来自 package.json)
app.config.tsimport "expo-splash-screen"node_modulesexpo-splash-screen节点模块package.json"main": "./build/index.js"buildindex.js 节点解析到此文件- 直接内部导入(不推荐)
避免直接导入模块内部,因为这会绕过标准解析顺序,可能在未来更新中造成问题。
app.config.tsimport "expo-splash-screen/build/index.js"node_modulesexpo-splash-screenpackage.json"main": "./build/index.js"app.plugin.js 由于直接导入已被忽略buildindex.js expo-splash-screen/build/index.js为什么使用 app.plugin.js 来处理插件
app.plugin.js 方法被首选用于配置插件,因为它允许与主包代码不同的转译设置。这一点特别重要,因为 Node 环境通常需要与 Android、iOS 或 Web JS 环境不同的转译预设(例如,使用 module.exports 而不是 import/export)。