模组
编辑页面
了解模组以及在创建配置插件时如何使用它们。
For the complete documentation index, see llms.txt. Use this file to discover all available pages.
本指南解释了什么是 mods 和 mod 插件、它们如何工作,以及在为你的 Expo 项目创建 config plugin 时如何有效地使用它们。
使用下方的图示,在本指南中,你将了解 config plugin 层级结构的最后两部分:
Mod 插件
Mod 插件提供了一种在 prebuild 过程中修改原生项目文件的方法。它们由 expo/config-plugins 库提供,并封装了顶层 mods(也称为 默认 mods),因为顶层 mods 具有平台特定性,并且会执行一些一开始可能难以理解的任务。
提示: 如果你正在开发一个需要 mods 的功能,你应该使用 mod 插件,而不是直接与顶层 mods 交互。
可用的 mod 插件
expo/config-plugins 库中提供了以下 mod 插件:
Android
| 默认 Android mod | Mod 插件 | 危险 | 描述 |
|---|---|---|---|
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 mod | Mod 插件 | 危险 | 描述 |
|---|---|---|---|
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 解析)进行修改。 |
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 mods 的说明:
默认 mods 由 mod 编译器提供,用于常见文件操作。危险修改依赖正则表达式(regex)来修改应用代码,这可能导致构建失败。Regex mods 也难以进行版本控制,因此应谨慎使用。应始终优先使用应用代码来修改应用代码,也就是 Expo Modules 原生 API。
Mods
Config plugins 使用 mods(即 modifiers,修改器的缩写)在 prebuild 过程中修改原生项目文件。Mods 是异步函数,允许你修改 AndroidManifest.xml、Info.plist 以及其他原生配置文件,而无需手动编辑它们。它们只会在 npx expo prebuild(prebuild 过程)的 同步 阶段执行。
它们接收一个 config 和一个 data 对象,然后将两者修改后作为一个对象一起返回。例如,在原生项目中,mods.android.manifest 会修改 AndroidManifest.xml,而 mods.ios.plist 会修改 Info.plist。
你不应在 config plugin 中直接将 mods 作为顶层函数使用(例如 with.android.manifest)。 当你需要使用某个 mod 时,应在 config plugins 中使用 mod 插件。这些 mod 插件由 expo/config-plugins 库提供,它们封装了顶层 mod 函数,并在幕后执行各种任务。要查看可用 mods 的列表,请参阅 expo/config-plugins 提供的 mod 插件。
默认 mods 的工作方式及其关键特性
当默认 mod 解析完成后,它会被添加到 app config 的 mods 对象中。这个 mods 对象不同于 app config 的其他部分,因为它不会被序列化,这意味着你可以用它在代码生成过程中执行操作。尽可能应使用可用的 mod 插件,而不是默认 mods,因为它们更易于使用。
以下是默认 mods 工作方式的高级概览:
- 使用
getPrebuildConfig从@expo/prebuild-config读取配置 - Expo 支持的所有核心功能都会通过
withIosExpoPlugins中的插件添加。这包括名称、版本、图标、语言环境等。 - 配置会传递给编译器
compileModsAsync - 编译器会添加负责读取数据(如 Info.plist)、执行命名 mod(如
mods.ios.infoPlist)、然后将结果写入文件系统的基础 mods - 编译器会遍历所有 mods 并异步求值它们,提供一些基础属性,如
projectRoot- 在每个 mod 之后,错误处理会断言 mod 链是否被无效 mod 破坏
以下是默认 mods 的一些关键特性:
-
mods会从清单中省略,并且不能通过Updates.manifest访问。mods 的存在只是为了在代码生成期间修改原生项目文件! -
mods可在npx expo prebuild命令期间安全地用于读写文件。这就是 Expo CLI 修改 Info.plist、entitlements、xcproj 等文件的方式。 -
mods是平台特定的,并且应始终添加到平台特定对象中:app.config.tsmodule.exports = { name: 'my-app', mods: { ios: { /* iOS 修改器... */ }, android: { /* Android 修改器... */ }, }, };
在 mods 解析完成后,每个 mod 的内容都会写入磁盘。还可以添加自定义 mod 来支持新的原生文件。例如,你可以创建一个 mod 来支持 GoogleServices-Info.plist,并将其传递给其他 mods。
Mod 插件如何工作
当一个 mod 插件执行时,它会接收一个带有额外属性的 config 对象:modResults 和 modRequest。
modResults
modResults 对象包含需要修改并返回的数据。它的类型取决于当前使用的 mod。
modRequest
modRequest 对象包含 mod 编译器提供的以下额外属性。
| 属性 | 类型 | 描述 |
|---|---|---|
projectRoot | string | 通用应用的项目根目录。 |
platformProjectRoot | string | 特定平台的项目根目录。 |
modName | string | mod 的名称。 |
platform | ModPlatform | mods 配置中使用的平台名称。 |
projectName | string | (仅 iOS)用于查询项目文件的路径组件。例如,projectRoot/ios/[projectName]/。 |
创建你自己的 mod
例如,如果你想编写一个 mod 来更新 Xcode 项目的“产品名称”,你将创建一个使用 withXcodeProject mod 插件的配置插件文件。
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; } ); }; // 用法: /// 创建一个 config const config = { name: 'my app', }; /// 使用该插件 export default withCustomProductName(config, 'new_name');
插件模块解析
在实现插件时,有两个基本方法需要考虑:
-
在你的 app 项目中定义的插件:这些插件本地存在于你的项目中,因此很容易与应用代码一起自定义和维护。它们非常适合项目特定的自定义。
-
独立包插件:这些插件作为单独的包存在,并发布到 npm。这种方法非常适合可复用插件,可以在多个项目之间共享。
这两种方法都能提供修改原生配置的相同能力,但在结构和导入方式上有所不同。下面的部分会解释每种方法的模块解析机制。
任何未在下面指定的解析模式都属于非预期行为,且可能会发生破坏性变更。
在你的 app 项目中定义的插件
对于在你的 app 项目中定义的插件,你可以通过几种方式直接在项目中实现插件:
文件导入
你可以通过创建一个 JavaScript/TypeScript 文件,并像使用其他 JS/TS 文件一样在你的配置中使用它,从而快速在项目中创建一个插件。
app.config.tsimport "./my-config-plugin"my-config-plugin.ts 从 config 导入在上面的示例中,config plugin 文件只包含一个最小函数:
module.exports = ({ config }: { config: ExpoConfig }) => {};
动态 app config 内的内联函数
Expo 配置对象也支持按原样将函数传递给 plugins 数组。这对于测试很有用,或者当你想在不创建文件的情况下使用插件时也很有帮助。
const withCustom = (config, props) => config; const config = { plugins: [ [ withCustom, { /* 参数 */ }, ], withCustom, ], };
用函数而不是字符串的一个注意事项是,序列化会把函数替换为函数名。这样可以让清单文件(有点像你应用的 index.html)按预期工作。下面是序列化后的 config 看起来的样子:
{ "plugins": [["withCustom", {}], "withCustom"] }
独立包插件
提示 查看 使用 config plugin 创建模块,获取创建独立包插件的分步指南。
独立包插件可以通过两种方式实现:
1. 专用 config plugin 包
这些是唯一目的就是提供 config plugin 的 npm 包。对于专用 config plugin 包,你可以使用 app.plugin.js 导出你的插件:
app.config.tsimport "expo-splash-screen"node_modulesexpo-splash-screenNode 模块app.plugin.js 自定义插件的入口文件buildindex.js 为了使用 app.plugin.js 而跳过2. 带伴随包的 config plugin
当某个 config plugin 是一个没有 app.plugin.js 的 Node 模块的一部分时,它会使用该包的 main 入口点:
app.config.tsimport "expo-splash-screen"node_modulesexpo-splash-screenNode 模块package.json"main": "./build/index.js"buildindex.js Node 解析到此文件插件解析顺序
当你导入一个插件包时,文件会按以下特定顺序解析:
- 包根目录中的 app.plugin.js
app.config.tsimport "expo-splash-screen"node_modulesexpo-splash-screenNode 模块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-screenNode 模块package.json"main": "./build/index.js"buildindex.js Node 解析到此文件- 直接内部导入(不推荐)
错误 避免直接导入模块内部,因为这会绕过标准解析顺序,并且可能在未来更新中失效。
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
对于 config plugins,更倾向于使用 app.plugin.js 方案,因为它允许与主包代码使用不同的转译设置。这一点尤其重要,因为 Node 环境通常需要与 Android、iOS 或 web JS 环境不同的转译预设(例如使用 module.exports 而不是 import/export)。