Mods

了解模块以及如何在创建配置插件时使用它们。


本指南解释了什么是 mod 和 mod 插件、它们的工作原理以及如何在为你的 Expo 项目创建配置插件时有效地使用它们。

🌐 This guide explains what mods and mod plugins are, how they work, and how to use them effectively when creating config plugins for your Expo project.

在本指南中,通过下图,你将了解配置插件层次结构的后两部分:

🌐 Using the diagram below, in this guide, you will learn the last two parts of the config plugin hierarchy:

模组插件

🌐 Mod plugins

Mod 插件提供了一种在预构建过程中修改原生项目文件的方法。它们可以通过 expo/config-plugins 库获得,并封装了顶层 mod(也称为 默认 mods),因为顶层 mod 是特定于平台的,并执行各种任务,这些任务一开始可能很难理解。

🌐 Mod plugins provide a way to modify native project files during the prebuild process. They are made available from expo/config-plugins library and wrap top-level mods (also known as default mods) because top-level mods are platform-specific and perform various tasks that can be difficult to understand at first.

信息 提示: 如果你正在开发需要模组的功能,你应使用 模组插件,而不是直接与顶层模组交互。

可用的 mod 插件

🌐 Available mod plugins

expo/config-plugins 库中可用的以下模块插件:

🌐 The following mod plugins are available in the expo/config-plugins library:

安卓

🌐 Android

Default Android modMod pluginDangerousDescription
mods.android.manifestwithAndroidManifest (Example)-Modify the android/app/src/main/AndroidManifest.xml as JSON (parsed with xml2js)
mods.android.stringswithStringsXml (Example)-Modify the android/app/src/main/res/values/strings.xml as JSON (parsed with xml2js).
mods.android.colorswithAndroidColors (Example)-Modify the android/app/src/main/res/values/colors.xml as JSON (parsed with xml2js).
mods.android.colorsNightwithAndroidColorsNight (Example)-Modify the android/app/src/main/res/values-night/colors.xml as JSON (parsed with xml2js).
mods.android.styleswithAndroidStyles (Example)-Modify the android/app/src/main/res/values/styles.xml as JSON (parsed with xml2js).
mods.android.gradlePropertieswithGradleProperties (Example)-Modify the android/gradle.properties as a Properties.PropertiesItem[].
mods.android.mainActivitywithMainActivity (Example)Modify the android/app/src/main/<package>/MainActivity.java as a string.
mods.android.mainApplicationwithMainApplication (Example)Modify the android/app/src/main/<package>/MainApplication.java as a string.
mods.android.appBuildGradlewithAppBuildGradle (Example)Modify the android/app/build.gradle as a string.
mods.android.projectBuildGradlewithProjectBuildGradle (Example)Modify the android/build.gradle as a string.
mods.android.settingsGradlewithSettingsGradle (Example)Modify the android/settings.gradle as a string.

iOS

Default iOS modMod pluginDangerousDescription
mods.ios.infoPlistwithInfoPlist (Example)-Modify the ios/<name>/Info.plist as JSON (parsed with @expo/plist).
mods.ios.entitlementswithEntitlementsPlist (Example)-Modify the ios/<name>/<product-name>.entitlements as JSON (parsed with @expo/plist).
mods.ios.expoPlistwithExpoPlist (Example)-Modify the ios/<name>/Expo.plist as JSON (Expo updates config for iOS) (parsed with @expo/plist).
mods.ios.xcodeprojwithXcodeProject (Example)-Modify the ios/<name>.xcodeproj as an XcodeProject object (parsed with xcode).
mods.ios.podfilewithPodfile (Example-Modify the ios/Podfile as a string.
mods.ios.podfilePropertieswithPodfileProperties (Example)-Modify the ios/Podfile.properties.json as JSON.
mods.ios.appDelegatewithAppDelegate (Example)Modify the ios/<name>/AppDelegate.m as a string.

信息 关于默认 Android 和 iOS 模组的说明:
默认模组由模组编译器提供,用于常见的文件操作。危险的修改依赖正则表达式(regex)来修改应用代码,这可能会导致构建失败。正则表达式模组也难以进行版本管理,因此应谨慎使用。始终优先使用应用代码来修改应用代码,也就是 Expo Modules 的原生 API。

Mods

配置插件使用 mods(modifier的缩写)在预构建过程中修改原生项目文件。Mods 是异步函数,允许你更改特定平台的文件,例如 AndroidManifest.xmlInfo.plist,以及其他原生配置文件,而无需手动编辑它们。它们仅在 npx expo prebuild同步 阶段(预构建过程)执行。

🌐 Config plugins use mods (short for modifiers) to modify native project files during the prebuild process. Mods are asynchronous functions that allow you to make changes to platform-specific files such as AndroidManifest.xml and Info.plist, and other native configuration files without having to manually edit them. They execute only during the syncing phase of npx expo prebuild (prebuild process).

它们接受一个配置对象和一个数据对象,然后修改并将它们作为一个整体返回。例如,在原生项目中,mods.android.manifest 会修改 AndroidManifest.xml,而 mods.ios.plist 会修改 Info.plist

🌐 They accept a config and a data object, then modify and return both of them as a single object. For example, in native projects, mods.android.manifest modifies AndroidManifest.xml and mods.ios.plist modifies Info.plist.

你不能在你的配置插件中直接将模块作为顶层函数使用(例如 with.android.manifest)。 当你需要使用某个模块时,你应在配置插件中使用_模块插件_。这些模块插件由 expo/config-plugins 库提供,它们封装了顶层模块函数,并在后台执行各种任务。要查看可用模块列表,请查看 expo/config-plugins 提供的模块插件

默认模组的工作原理及其主要特性

当默认模块解析时,它会被添加到应用配置的 mods 对象中。这个 mods 对象不同于应用配置的其他部分,因为它不会被序列化,这意味着你可以用它在代码生成期间执行操作。尽可能情况下,你应该使用可用的模块插件而不是默认模块,因为它们更易于使用。

🌐 When a default mod resolves, it is added to the mods object of the app config. This mods object is different from the rest of the app config because it doesn't get serialized, which means you can use it to perform actions during code generation. Whenever possible, you should use available mod plugins instead of default mods since they are easier to work with.

以下是默认模组工作原理的高级概述:

🌐 Here is a high-level overview of how default mods work:

  • 配置使用 getPrebuildConfig@expo/prebuild-config 读取
  • Expo 支持的所有核心功能都是通过 withIosExpoPlugins 中的插件添加的。这包括名称、版本、图标、本地化等。
  • 配置被传递给编译器 compileModsAsync
  • 编译器会添加负责读取数据(如 Info.plist)、执行指定的模块(如 mods.ios.infoPlist),然后将结果写入文件系统的基础模块
  • 编译器会遍历所有的模块并异步评估它们,同时提供一些基础属性,例如 projectRoot
    • 每次修改后,错误处理都会断言是否有无效的修改破坏了修改链

以下是默认模组的一些主要特性:

🌐 Here are some key characteristics of default mods:

  • mods 已从清单中省略,并且 无法 通过 Updates.manifest 访问。Mods 存在的唯一目的就是在代码生成过程中修改原生项目文件!

  • mods 可以在 npx expo prebuild 命令期间安全地读取和写入文件。这就是 Expo CLI 修改 Info.plist、权限文件、xcproj 等的方式。

  • mods 是特定于平台的,应该始终添加到特定于平台的对象中:

    app.config.ts
    module.exports = { name: 'my-app', mods: { ios: { /* iOS mods... */ }, android: { /* Android mods... */ }, }, };

在修改内容解决后,每个修改的内容将被写入磁盘。可以添加自定义修改以支持新的原生文件。例如,你可以创建一个修改以支持 GoogleServices-Info.plist,并将其传递给其他修改。

🌐 After mods are resolved, the contents of each mod will be written to disk. Custom mods can be added to support new native files. For example, you can create a mod to support the GoogleServices-Info.plist, and pass it to other mods.

模组插件的工作原理

🌐 How mod plugins work

当一个 mod 插件被执行时,它会被传入一个带有额外属性 modResultsmodRequestconfig 对象。

🌐 When a mod plugin is executed, it gets passed a config object with additional properties: modResults and modRequest.

modResults

modResults 对象包含要修改和返回的数据。它的类型取决于正在使用的 mod。

🌐 The modResults object contains the data to modify and return. Its type depends on the mod that's being used.

modRequest

modRequest 对象包含由模块编译器提供的以下附加属性。

🌐 The modRequest object contains the following additional properties supplied by the mod compiler.

属性类型描述
projectRootstring通用应用的项目根目录。
platformProjectRootstring特定平台的项目根目录。
modNamestring模组的名称。
platformModPlatform模组配置中使用的平台名称。
projectNamestring(仅限 iOS)用于查询项目文件的路径组件。例如,projectRoot/ios/[projectName]/

创建你自己的模组

🌐 Create your own mod

例如,如果你想编写一个用于更新 Xcode 项目“产品名称”的 mod,你需要创建一个使用 withXcodeProject mod 插件的配置插件文件。

🌐 For example, if you want to write a mod to update the Xcode Project's "product name", you'll create a config plugin file that uses the withXcodeProject mod plugin.

my-config-plugin.ts
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; } ); }; // Usage: /// Create a config const config = { name: 'my app', }; /// Use the plugin export default withCustomProductName(config, 'new_name');

插件模块解析

🌐 Plugin module resolution

在实现插件时,有两种基本方法需要考虑:

🌐 When implementing plugins, there are two fundamental approaches to consider:

  1. 在你的应用项目中定义的插件:这些插件存储在项目本地,使其可以轻松地与应用代码一起自定义和维护。它们非常适合特定项目的定制化需求。
  2. 独立包插件:这些插件以独立包的形式存在,并发布到 npm。对于可以在多个项目中共享的可复用插件,这种方式是理想的选择。

这两种方法在修改本地配置时提供相同的功能,但在结构和导入方式上有所不同。下面的章节解释了每种方法的模块解析是如何工作的。

🌐 Both approaches provide the same capabilities for modifying your native configuration, but differ in how they're structured and imported. The sections below explain how module resolution works for each approach.

任何未在下文指定的解析模式都属于意外行为,可能会发生破坏性更改。

在你的应用项目中定义的插件

🌐 Plugins defined within your app's project

在你的应用项目中定义插件后,你可以通过多种方式直接在项目中实现插件:

🌐 With plugins defined within your app's project, you can implement plugins directly in your project in several ways:

文件导入

🌐 File import

你可以通过创建一个 JavaScript/TypeScript 文件快速在你的项目中创建插件,并像使用其他 JS/TS 文件一样在配置中使用它。

🌐 You can quickly create a plugin in your project by creating a JavaScript/TypeScript file and use it in your config like any other JS/TS file.

app.config.tsimport "./my-config-plugin"
my-config-plugin.ts Imported from config

在上面的示例中,配置插件文件包含一个最基本的函数:

🌐 In the above example, the config plugin file contains a bare minimum function:

my-config-plugin.ts
module.exports = ({ config }: { config: ExpoConfig }) => {};

动态应用配置中的内联函数

🌐 Inline function inside of dynamic app config

Expo 配置对象也支持将函数原样传递到 plugins 数组中。这对于测试很有用,或者如果你想使用插件而不创建文件时也很方便。

🌐 Expo config objects also support passing functions as-is to the plugins array. This is useful for testing, or if you want to use a plugin without creating a file.

app.config.ts
const withCustom = (config, props) => config; const config = { plugins: [ [ withCustom, { /* props */ }, ], withCustom, ], };

使用函数而不是字符串的一个注意事项是,序列化会将函数替换为函数的名称。这可以保持 清单(有点像你应用的 index.html)按预期工作。下面是序列化配置的示例:

🌐 One caveat to using functions instead of strings is that serialization will replace the function with the function's name. This keeps manifests (kind of like the index.html for your app) working as expected. Here is what the serialized config would look like:

{ "plugins": [["withCustom", {}], "withCustom"] }

独立包插件

🌐 Standalone package plugins

信息 请参阅 使用配置插件创建模块 获取有关如何创建独立包插件的分步指南。

独立软件包插件可以通过两种方式实现:

🌐 Standalone package plugins can be implemented in two ways:

1. 专用配置插件包

🌐 1. Dedicated config plugin packages

这些是 npm 包,其唯一目的是提供配置插件。对于专用的配置插件包,你可以使用 app.plugin.js 导出你的插件:

🌐 These are npm packages whose sole purpose is to provide a config plugin. For a dedicated config plugin package, you can export your plugin using app.plugin.js:

app.config.tsimport "expo-splash-screen"
node_modules
expo-splash-screenNode module
  app.plugin.js Entry file for custom plugins
  build
   index.js Skipped in favor of app.plugin.js

2. 使用配套软件包配置插件

🌐 2. Config plugins with companion packages

当配置插件是 Node 模块的一部分且没有 app.plugin.js 时,它使用该包的 main 入口点:

🌐 When a config plugin is part of a Node module without an app.plugin.js, it uses the package's main entry point:

app.config.tsimport "expo-splash-screen"
node_modules
expo-splash-screenNode module
  package.json"main": "./build/index.js"
  build
   index.js Node resolve to this file

插件解析顺序

🌐 Plugin resolution order

当你导入插件包时,文件将按以下特定顺序解析:

🌐 When you import a plugin package, files are resolved in this specific order:

  1. 包根目录中的 app.plugin.js
app.config.tsimport "expo-splash-screen"
node_modules
expo-splash-screenNode module
  package.json"main": "./build/index.js"
  app.plugin.js Entry file for custom plugins
  build
   index.js Skipped in favor of app.plugin.js
  1. 包的主入口(来自 package.json)
app.config.tsimport "expo-splash-screen"
node_modules
expo-splash-screenNode module
  package.json"main": "./build/index.js"
  build
   index.js Node resolve to this file
  1. 直接内部导入(不推荐)

错误 避免直接导入模块的内部内容,因为这会绕过标准解析顺序,并可能在将来更新中导致破坏。

app.config.tsimport "expo-splash-screen/build/index.js"
node_modules
expo-splash-screen
  package.json"main": "./build/index.js"
  app.plugin.js Ignored due to direct import
  build
   index.js expo-splash-screen/build/index.js

为什么使用 app.plugin.js 插件

🌐 Why use app.plugin.js for plugins

app.plugin.js 方法更适合用于配置插件,因为它允许与主包代码不同的转译设置。这一点尤为重要,因为 Node 环境通常需要与 Android、iOS 或网页 JS 环境不同的转译预设(例如,使用 module.exports 而不是 import/export)。

🌐 The app.plugin.js approach is preferred for config plugins as it allows different transpilation settings from the main package code. This is particularly important because Node environments often require different transpilation presets compared to Android, iOS, or web JS environments (for example, module.exports instead of import/export).