Mods

编辑页面

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


For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.

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 插件

Mod 插件提供了一种在 prebuild 过程中修改原生项目文件的方法。它们由 expo/config-plugins 库提供,并封装了顶层 mods(也称为 默认 mods),因为顶层 mods 具有平台特定性,并且会执行一些一开始可能难以理解的任务。

提示: 如果你正在开发一个需要 mods 的功能,你应该使用 mod 插件,而不是直接与顶层 mods 交互。

可用的 mod 插件

expo/config-plugins 库中提供了以下 mod 插件:

Android

默认 Android modMod 插件危险描述
mods.android.manifestwithAndroidManifest (示例)-android/app/src/main/AndroidManifest.xml 作为 JSON(使用 xml2js 解析)进行修改
mods.android.stringswithStringsXml (示例)-android/app/src/main/res/values/strings.xml 作为 JSON(使用 xml2js 解析)进行修改。
mods.android.colorswithAndroidColors (示例)-android/app/src/main/res/values/colors.xml 作为 JSON(使用 xml2js 解析)进行修改。
mods.android.colorsNightwithAndroidColorsNight (示例)-android/app/src/main/res/values-night/colors.xml 作为 JSON(使用 xml2js 解析)进行修改。
mods.android.styleswithAndroidStyles (示例)-android/app/src/main/res/values/styles.xml 作为 JSON(使用 xml2js 解析)进行修改。
mods.android.gradlePropertieswithGradleProperties (示例)-android/gradle.properties 修改为 Properties.PropertiesItem[]
mods.android.mainActivitywithMainActivity (示例)android/app/src/main/<package>/MainActivity.java 作为字符串进行修改。
mods.android.mainApplicationwithMainApplication (示例)android/app/src/main/<package>/MainApplication.java 作为字符串进行修改。
mods.android.appBuildGradlewithAppBuildGradle (示例)android/app/build.gradle 作为字符串进行修改。
mods.android.projectBuildGradlewithProjectBuildGradle (示例)android/build.gradle 作为字符串进行修改。
mods.android.settingsGradlewithSettingsGradle (示例)android/settings.gradle 作为字符串进行修改。

iOS

默认 iOS modMod 插件危险描述
mods.ios.infoPlistwithInfoPlist (示例)-ios/<name>/Info.plist 作为 JSON(使用 @expo/plist 解析)进行修改。
mods.ios.entitlementswithEntitlementsPlist (示例)-ios/<name>/<product-name>.entitlements 作为 JSON(使用 @expo/plist 解析)进行修改。
mods.ios.expoPlistwithExpoPlist (示例)-ios/<name>/Expo.plist 作为 JSON(Expo iOS 更新配置)(使用 @expo/plist 解析)进行修改。
mods.ios.xcodeprojwithXcodeProject (示例)-ios/<name>.xcodeproj 作为 XcodeProject 对象(使用 xcode 解析)进行修改。
mods.ios.podfilewithPodfile (示例-ios/Podfile 作为字符串进行修改。
mods.ios.podfilePropertieswithPodfileProperties (示例)-ios/Podfile.properties.json 作为 JSON 进行修改。
mods.ios.appDelegatewithAppDelegate (示例)ios/<name>/AppDelegate.m 作为字符串进行修改。
关于默认 Android 和 iOS mods 的说明:
默认 mods 由 mod 编译器提供,用于常见文件操作。危险修改依赖正则表达式(regex)来修改应用代码,这可能导致构建失败。Regex mods 也难以进行版本控制,因此应谨慎使用。应始终优先使用应用代码来修改应用代码,也就是 Expo Modules 原生 API。

Mods

Config plugins 使用 mods(即 modifiers,修改器的缩写)在 prebuild 过程中修改原生项目文件。Mods 是异步函数,允许你修改 AndroidManifest.xmlInfo.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.ts
    module.exports = { name: 'my-app', mods: { ios: { /* iOS mods... */ }, android: { /* Android mods... */ }, }, };

在 mods 解析完成后,每个 mod 的内容都会写入磁盘。还可以添加自定义 mod 来支持新的原生文件。例如,你可以创建一个 mod 来支持 GoogleServices-Info.plist,并将其传递给其他 mods。

Mod 插件如何工作

当一个 mod 插件执行时,它会接收一个带有额外属性的 config 对象:modResultsmodRequest

modResults

modResults 对象包含需要修改并返回的数据。它的类型取决于当前使用的 mod。

modRequest

modRequest 对象包含 mod 编译器提供的以下额外属性。

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

创建你自己的 mod

例如,如果你想编写一个 mod 来更新 Xcode Project 的“产品名称”,你将创建一个使用 withXcodeProject mod 插件的 config 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; } ); }; // 用法: /// 创建一个 config const config = { name: 'my app', }; /// 使用该插件 export default withCustomProductName(config, 'new_name');

插件模块解析

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

  1. 在你的 app 项目中定义的插件:这些插件本地存在于你的项目中,因此很容易与应用代码一起自定义和维护。它们非常适合项目特定的自定义。

  2. 独立包插件:这些插件作为单独的包存在,并发布到 npm。这种方法非常适合可复用插件,可以在多个项目之间共享。

这两种方法都能提供修改原生配置的相同能力,但在结构和导入方式上有所不同。下面的部分会解释每种方法的模块解析机制。

任何未在下面指定的解析模式都属于非预期行为,且可能会发生破坏性变更。

在你的 app 项目中定义的插件

对于在你的 app 项目中定义的插件,你可以通过几种方式直接在项目中实现插件:

文件导入

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

app.config.tsimport "./my-config-plugin"
my-config-plugin.ts 从 config 导入

在上面的示例中,config plugin 文件只包含一个最小函数:

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

动态 app config 内的内联函数

Expo config 对象也支持按原样将函数传递给 plugins 数组。这对于测试很有用,或者当你想在不创建文件的情况下使用插件时也很有帮助。

app.config.ts
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_modules
expo-splash-screenNode module
  app.plugin.js 自定义插件的入口文件
  build
   index.js 为了使用 app.plugin.js 而跳过

2. 带伴随包的 config plugin

当某个 config plugin 是一个没有 app.plugin.js 的 Node module 的一部分时,它会使用该包的 main 入口点:

app.config.tsimport "expo-splash-screen"
node_modules
expo-splash-screenNode module
  package.json"main": "./build/index.js"
  build
   index.js Node 解析到此文件

插件解析顺序

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

  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 自定义插件的入口文件
  build
   index.js 为了使用 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 解析到此文件
  1. 直接内部导入(不推荐)
避免直接导入模块内部,因为这会绕过标准解析顺序,并且可能在未来更新中失效。
app.config.tsimport "expo-splash-screen/build/index.js"
node_modules
expo-splash-screen
  package.json"main": "./build/index.js"
  app.plugin.js 因直接导入而被忽略
  build
   index.js expo-splash-screen/build/index.js

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

对于 config plugins,更倾向于使用 app.plugin.js 方案,因为它允许与主包代码使用不同的转译设置。这一点尤其重要,因为 Node 环境通常需要与 Android、iOS 或 web JS 环境不同的转译预设(例如使用 module.exports 而不是 import/export)。