模组

编辑页面

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


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 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 修改器... */ }, android: { /* Android 修改器... */ }, }, };

在 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 项目的“产品名称”,你将创建一个使用 withXcodeProject mod 插件的配置插件文件。

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 文件一样在你的配置中使用它,从而快速在项目中创建一个插件。

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 配置对象也支持按原样将函数传递给 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 模块
  app.plugin.js 自定义插件的入口文件
  build
   index.js 为了使用 app.plugin.js 而跳过

2. 带伴随包的 config plugin

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

app.config.tsimport "expo-splash-screen"
node_modules
expo-splash-screenNode 模块
  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 模块
  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 模块
  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)。