模块

编辑

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


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

使用下面的图表,在本指南中,您将学习配置插件层次结构的最后两个部分:

模块插件

模块插件提供了一种在预构建过程中修改本机项目文件的方法。它们来自 expo/config-plugins 库,并封装了顶级模块(也称为 默认 模块),因为顶级模块是平台特定的并执行一些难以理解的各种任务。

提示: 如果您正在开发需要模块的功能,则应使用 模块插件 而不是直接与顶级模块交互。

可用模块插件

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

Android

默认 Android 模块模块插件危险性描述
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 模块模块插件危险性描述
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](https://www.npmjs.com/package/xcode)解析)。
mods.ios.podfilewithPodfile (示例-ios/Podfile 作为字符串修改。
mods.ios.podfilePropertieswithPodfileProperties (示例)-ios/Podfile.properties.json 作为 JSON 修改。
mods.ios.appDelegatewithAppDelegate (示例)ios/<name>/AppDelegate.m 作为字符串修改。

信息 关于默认 Android 和 iOS 模块的说明:
默认模块由模块编译器提供,以便于常见的文件操作。危险的修改依赖于正则表达式(regex)来修改应用代码,这可能导致构建失败。正则模块的版本控制也很困难,因此应谨慎使用。始终倾向于使用应用代码来修改应用代码,即使用 Expo Modules 原生 API。

模块

配置插件使用 模块(modifiers 的缩写)在预构建过程中修改本机项目文件。模块是异步函数,允许您在不必手动编辑它们的情况下,对平台特定文件(如 AndroidManifest.xmlInfo.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.ts
    module.exports = { name: 'my-app', mods: { ios: { /* iOS 模块... */ }, android: { /* Android 模块... */ }, }, };

在模块解析后,每个模块的内容将被写入磁盘。可以添加自定义模块以支持新的本机文件。例如,您可以创建一个模块来支持 GoogleServices-Info.plist,并将其传递给其他模块。

模块插件的工作原理

当执行模块插件时,会传递一个带有附加属性的 config 对象:modResultsmodRequest

modResults

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

modRequest

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

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

创建您自己的模块

例如,如果您想编写一个模块来更新 Xcode 项目的“产品名称”,您将创建一个配置插件文件,使用 withXcodeProject 模块插件。

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; } ); }; // 用法: /// 创建一个配置 const config = { name: 'my app', }; /// 使用插件 export default withCustomProductName(config, 'new_name');

插件模块解析

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

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

  2. 独立包插件:这些插件作为单独的包存在并发布到 npm。此方法适合于可以跨多个项目共享的可重用插件。

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

任何未在下面指定的解析模式都是意外行为,并可能面临破坏性更改。

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

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

文件导入

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

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

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

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

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

Expo 配置对象还支持将函数原样传递给 plugins 数组。这对于测试很有用,或者如果您想在不创建文件的情况下使用插件。

app.config.ts
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_modules
expo-splash-screen节点模块
  app.plugin.js 自定义插件的入口文件
  build
   index.js 为了 app.plugin.js 被跳过

2. 带有伴生包的配置插件

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

app.config.tsimport "expo-splash-screen"
node_modules
expo-splash-screen节点模块
  package.json"main": "./build/index.js"
  build
   index.js 节点解析到此文件

插件解析顺序

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

  1. 包根目录中的 app.plugin.js
app.config.tsimport "expo-splash-screen"
node_modules
expo-splash-screen节点模块
  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-screen节点模块
  package.json"main": "./build/index.js"
  build
   index.js 节点解析到此文件
  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 来处理插件

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