创建和使用配置插件

编辑页面

了解如何在你的 Expo 项目中创建和使用配置插件。


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

本指南涵盖了如何创建 config plugin、如何向 config plugin 传递参数,以及如何将多个 config plugin 串联在一起的相关章节。它还介绍了如何从 Expo 库中使用 config plugin。

借助下方图示,在本指南中,你将学习 config plugin 层级结构的前两部分:

注意: 以下章节使用动态 app configapp.config.js/app.config.ts,而不是 app.json),但使用简单的 config plugin 并不要求这样做。不过,当你想创建/使用一个接受参数的基于函数的 config plugin 时,就必须使用动态 app config。

创建一个 config plugin

在下面的章节中,让我们创建一个本地 config plugin,它会为 Android 的 AndroidManifest.xml 和 iOS 的 Info.plist 添加一个任意属性 HelloWorldMessage

此示例将创建并修改以下文件。为了跟着操作,请在项目根目录下创建一个 plugins 目录,并在其中创建 withAndroidPlugin.tswithIosPlugins.tswithPlugin.ts 文件。

plugins
withAndroidPlugin.ts包含 Android 特定修改
withIosPlugin.ts包含 iOS 特定修改
withPlugin.ts组合 Android 和 iOS 插件的主插件文件
app.config.ts使用该插件的动态 app config 文件

1

创建 Android 插件

withAndroidPlugin.ts 中,添加以下代码:

withAndroidPlugin.ts
import { ConfigPlugin, withAndroidManifest } from 'expo/config-plugins'; const withAndroidPlugin: ConfigPlugin = config => { // 定义一个自定义消息 const message = 'Hello world, from Expo plugin!'; return withAndroidManifest(config, config => { const mainApplication = config?.modResults?.manifest?.application?.[0]; if (mainApplication) { // 确保 meta-data 数组存在 if (!mainApplication['meta-data']) { mainApplication['meta-data'] = []; } // 将自定义消息作为 meta-data 条目添加 mainApplication['meta-data'].push({ $: { 'android:name': 'HelloWorldMessage', 'android:value': message, }, }); } return config; }); }; export default withAndroidPlugin;

上面的示例代码通过从 expo/config-plugins 库中导入 ConfigPluginwithAndroidManifest,向 android/app/src/main/AndroidManifest.xml 文件添加了一个 HelloWorldMessage 的 meta-data 条目。withAndroidManifest mod plugin 是一个异步函数,它接受一个 config 和一个 data 对象,并在返回对象之前修改该值。

2

创建 iOS 插件

withIosPlugin.ts 中,添加以下代码:

withIosPlugin.ts
import { ConfigPlugin, withInfoPlist } from 'expo/config-plugins'; const withIosPlugin: ConfigPlugin = config => { // 定义自定义消息 const message = 'Hello world, from Expo plugin!'; return withInfoPlist(config, config => { // 将自定义消息添加到 Info.plist 文件中 config.modResults.HelloWorldMessage = message; return config; }); }; export default withIosPlugin;

上面的示例代码通过从 expo/config-plugins 库中导入 ConfigPluginwithInfoPlist,向 ios/<your-project-name>/Info.plist 文件中添加了一个名为 HelloWorldMessage 的自定义键及其自定义消息。withInfoPlist mod plugin 是一个异步函数,它接受一个 config 和一个 data 对象,并在返回对象之前修改该值。

3

创建一个组合插件

现在你可以创建一个组合插件,同时应用这两个平台特定插件。这种方式可以将平台特定代码分开维护,同时提供一个统一入口。

withPlugin.ts 中,添加以下代码:

withPlugin.ts
import { ConfigPlugin } from 'expo/config-plugins'; import withAndroidPlugin from './withAndroidPlugin'; import withIosPlugin from './withIosPlugin'; const withPlugin: ConfigPlugin = config => { // 先应用 Android 修改 config = withAndroidPlugin(config); // 再应用 iOS 修改并返回 return withIosPlugin(config); }; export default withPlugin;

4

添加 TypeScript 支持并转换为动态 app config

我们建议使用 TypeScript 编写 config plugin,因为这样会为配置对象提供智能提示。不过,你的 app config 最终是由 Node.js 评估的,而 Node.js 默认并不识别 TypeScript 代码。因此,你需要为 plugins 目录中的 TypeScript 文件到 app.config.ts 文件添加一个解析器。

通过运行以下命令安装 tsx 库:

Terminal
npm install --save-dev tsx

然后,将静态 app config (app.json) 改为 动态 app config (app.config.ts) 文件。你可以通过将 app.json 文件重命名为 app.config.ts 并按如下所示修改文件内容来实现。请确保在 app.config.ts 文件顶部添加以下导入语句:

app.config.ts
import 'tsx/cjs'; module.exports = () => { %%placeholder-start%%... rest of your app config %%placeholder-end%% };

5

从动态 app config 中调用 config plugin

现在,你可以从动态 app config 中调用 config plugin。为此,你需要将 withPlugin.ts 文件的路径添加到 app config 的 plugins 数组中:

app.config.ts
import "tsx/cjs"; import { ExpoConfig } from "expo/config"; module.exports = ({ config }: { config: ExpoConfig }) => { %%placeholder-start%%... rest of your app config %%placeholder-end%% plugins: [ ["./plugins/withPlugin.ts"], ], };

要在原生项目中查看已应用的自定义配置,请运行以下命令:

Terminal
npx expo prebuild --clean --no-install

要验证自定义 config plugins 是否已应用,请打开 android/app/src/main/AndroidManifest.xmlios/<your-project-name>/Info.plist 文件:

AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- ... 其余配置--> <application ...> <meta-data android:name="HelloWorldMessage" android:value="Hello world, from Expo plugin!"/> <!-- ... --> </application> </manifest>
Info.plist
<plist version="1.0"> <dict> <!-- ... --> <key>HelloWorldMessage</key> <string>Hello world, from Expo plugin!</string> <!-- ... --> </dict> </plist>

向 config plugin 传递参数

你的 config plugin 可以接受从 app config 传入的参数。为此,你需要在 config plugin 函数中读取该参数,然后在 app config 中将包含该参数的对象与 config plugin 函数一起传入。

1

基于前面的示例,让我们向插件传递一条自定义消息。在 withAndroidPlugin.ts 中添加一个 options 对象,并更新 message 变量以使用 options.message 属性:

withAndroidPlugin.ts
%%placeholder-start%%...%%placeholder-end%% type AndroidProps = { message?: string; }; const withAndroidPlugin: ConfigPlugin<AndroidProps> = ( config, options = {} ) => { const message = options.message || 'Hello world, from Expo plugin!'; return withAndroidManifest(config, config => { %%placeholder-start%%... rest of the example remains unchanged %%placeholder-end%% }); }; export default withAndroidPlugin;

2

同样地,在 withIosPlugin.ts 中添加一个 options 对象,并更新 message 变量以使用 options.message 属性:

withIosPlugin.ts
%%placeholder-start%%...%%placeholder-end%% type IosProps = { message?: string; }; const withIosPlugin: ConfigPlugin<IosProps> = (config, options = {}) => { const message = options.message || 'Hello world, from Expo plugin!'; %%placeholder-start%%... rest of the example remains unchanged%%placeholder-end%% }; export default withIosPlugin;

3

更新 withPlugin.ts 文件,将 options 对象传递给这两个插件:

withPlugin.ts
%%placeholder-start%%...%%placeholder-end%% const withPlugin: ConfigPlugin<{ message?: string }> = (config, options = {}) => { config = withAndroidPlugin(config, options); return withIosPlugin(config, options); };

4

要动态地向插件传递值,你可以在 app config 中向插件传入一个带有 message 属性的对象:

app.config.ts
{ %%placeholder-start%%...%%placeholder-end%% plugins: [ [ "./plugins/withPlugin.ts", { message: "Custom message from app.config.ts" }, ], ], }

串联 config plugins

config plugins 可以串联起来,以应用多个修改。链中的每个插件都会按其出现顺序运行,前一个插件的输出会成为下一个插件的输入。这种顺序执行确保插件之间的依赖关系得到尊重,并允许你精确控制对原生代码修改的顺序。

要串联 config plugins,你可以在 app config 的 plugins 数组属性中传入一个插件数组。JSON 格式的 app config 文件(app.json)同样支持这一点。

app.config.ts
module.exports = ({ config }: { config: ExpoConfig }) => { name: 'my app', plugins: [ [withFoo, 'input 1'], [withBar, 'input 2'], [withDelta, 'input 3'], ], };

plugins 数组在底层使用 withPlugins 方法来串联这些插件。如果你的 plugins 数组很长,或者配置较复杂,你可以直接使用 withPlugins 方法,使配置更易于阅读。withPlugins 会将插件串联起来并按顺序执行。

app.config.ts
import { withPlugins } from 'expo/config-plugins'; // 创建一个基础配置对象 const baseConfig = { name: 'my app', %%placeholder-start%%... rest of the config %%placeholder-end%% }; // ❌ 不易阅读 withDelta(withFoo(withBar(config, 'input 1'), 'input 2'), 'input 3'); // ✅ 更易阅读 withPlugins(config, [ [withFoo, 'input 1'], [withBar, 'input 2'], // 当不需要输入时,你只需传入该方法 withDelta, ]); // 导出应用了插件的基础配置 module.exports = ({ config }: { config: ExpoConfig }) => { return withPlugins(baseConfig, plugins); };

使用配置插件

Expo 配置插件通常包含在 Node.js 模块中。你可以像安装其他库一样在项目中安装它们。

例如,expo-camera 有一个插件,会将相机权限添加到 AndroidManifest.xmlInfo.plist。要在项目中安装它,请运行以下命令:

Terminal
npx expo install expo-camera

在你的 应用配置 中,你可以将 expo-camera 添加到插件列表:

app.json
{ "expo": { "plugins": ["expo-camera"] } }

某些配置插件提供了灵活性,允许你传入选项来自定义其配置。为此,你可以传入一个数组,数组的第一个参数是 Expo 库名称,第二个参数是包含这些选项的对象。例如,expo-camera 插件允许你自定义相机权限消息:

app.json
{ "expo": { "plugins": [ [ "expo-camera", { "cameraPermission": "允许 $(PRODUCT_NAME) 访问你的相机。" } ] ] } }
提示:对于每一个带有配置插件的 Expo 库,你都可以在该库的 API 参考中找到更多相关信息。例如,expo-camera 库有一个配置插件部分

在运行 npx expo prebuild 时,mods 会被编译,原生文件也会发生变化。

这些更改不会生效,直到你重新构建原生项目,例如使用 Xcode。如果你在一个没有原生目录的项目(CNG 项目)中使用配置插件,它们会在 EAS Build 的 prebuild 步骤中应用,或者在本地运行 npx expo prebuild|android|ios 时应用。