创建和使用配置插件

编辑

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


本指南涵盖如何创建配置插件、如何向配置插件传递参数,以及如何将多个配置插件串联在一起的部分。还包括如何从 Expo 库中使用配置插件。

通过下方图表,在本指南中你将学习配置插件层级结构的前两部分:

注意: 以下各节使用动态 应用配置app.config.js/app.config.ts 而非 app.json),这并非使用简单配置插件所必需。然而,当你想创建/使用一个接受参数的基于函数的配置插件时,需要使用动态应用配置。

创建一个配置插件

在下节中,我们让我们创建一个本地配置插件,为 Android 的 AndroidManifest.xml 和 iOS 的 Info.plist 添加任意属性 HelloWorldMessage

此示例将创建并修改以下文件。若要跟随,请在项目根目录创建一个 plugins 目录,并在其中创建 withAndroidPlugin.tswithIosPlugins.ts,以及 withPlugin.ts 文件。

plugins
withAndroidPlugin.tsContains Android-specific modifications
withIosPlugin.tsContains iOS-specific modifications
withPlugin.tsMain plugin file that combines both Android and iOS plugins
app.config.tsDynamic app config file that uses the plugin

1

创建 Android 插件

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

withAndroidPlugin.ts
import { ConfigPlugin, withAndroidManifest } from 'expo/config-plugins'; const withAndroidPlugin: ConfigPlugin = config => { // Define a custom message const message = 'Hello world, from Expo plugin!'; return withAndroidManifest(config, config => { const mainApplication = config?.modResults?.manifest?.application?.[0]; if (mainApplication) { // Ensure meta-data array exists if (!mainApplication['meta-data']) { mainApplication['meta-data'] = []; } // Add the custom message as a meta-data entry 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 文件添加一个 meta-data 条目 HelloWorldMessagewithAndroidManifest mod 插件是一个异步函数,接受一个 config 和一个 data 对象,并在返回对象之前修改值。

2

Create iOS plugin

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

withIosPlugin.ts
import { ConfigPlugin, withInfoPlist } from 'expo/config-plugins'; const withIosPlugin: ConfigPlugin = config => { // Define the custom message const message = 'Hello world, from Expo plugin!'; return withInfoPlist(config, config => { // Add the custom message to the Info.plist file config.modResults.HelloWorldMessage = message; return config; }); }; export default withIosPlugin;

上面的示例代码通过从 expo/config-plugins 库导入 ConfigPluginwithInfoPlist,在 ios/<your-project-name>/Info.plist 文件中将 HelloWorldMessage 作为自定义 key、并附加自定义消息。withInfoPlist 模块插件是一个异步函数,接收一个 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 => { // Apply Android modifications first config = withAndroidPlugin(config); // Then apply iOS modifications and return return withIosPlugin(config); }; export default withPlugin;

4

添加 TypeScript 支持并转换为动态应用配置

我们建议使用 TypeScript 编写配置插件,因为这将为配置对象提供 IntelliSense。但是,您的应用配置最终由 Node.js 评估,而 Node.js 默认不识别 TypeScript 代码。因此,您需要将来自 plugins 目录的 TypeScript 文件的解析器添加到 app.config.ts 文件中。

通过以下命令安装 tsx 库:

Terminal
npm install --save-dev tsx

接着,将静态应用配置 (app.json) 更改为 动态应用配置 (app.config.ts) 文件。您可以将 app.json 文件重命名为 app.config.ts,并将文件内容修改如下所示。请确保在 app.config.ts 文件顶部添加以下 import 语句:

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

5

从你的动态应用配置调用 config 插件

现在,你可以从你的动态应用配置中调用 config 插件。要做到这一点,你需要将 withPlugin.ts 文件的路径添加到你的应用配置中的 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

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

AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- ... rest of the configuration--> <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 插件可以接受来自应用配置的参数。为此,你需要在你的配置插件函数中读取该参数,然后将包含该参数的对象与配置插件函数一起在你的应用配置中传递。

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

要将值动态传递给插件,您可以在应用配置中向插件传递一个具有 message 属性的对象:

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

链式配置插件

可以将配置插件链式连接,以应用多项修改。链中的每个插件按出现的顺序运行,一个插件的输出成为下一个插件的输入。这种顺序执行确保插件之间的依赖关系被遵循,并让你能够精确控制对原生代码修改的顺序。

要链接配置插件,您可以将插件数组传递给应用配置中的 plugins 数组属性。这在 JSON 应用配置文件格式(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'; // Create a base config object const baseConfig = { name: 'my app', %%placeholder-start%%... rest of the config %%placeholder-end%% }; // ❌ Hard to read withDelta(withFoo(withBar(config, 'input 1'), 'input 2'), 'input 3'); // ✅ Easy to read withPlugins(config, [ [withFoo, 'input 1'], [withBar, 'input 2'], // When no input is required, you can just pass the method withDelta, ]); // Export the base config with plugins applied module.exports = ({ config }: { config: ExpoConfig }) => { return withPlugins(baseConfig, plugins); };

使用配置插件

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

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

Terminal
npx expo install expo-camera

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

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

一些配置插件通过允许传递选项来自定义它们的配置来提供灵活性。为此,您可以传递一个数组,将 Expo 库名作为第一个参数,将包含选项的对象作为第二个参数。例如,expo-camera 插件允许您自定义相机权限提示消息:

app.json
{ "expo": { "plugins": [ [ "expo-camera", { "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera." } ] ] } }
提示: 对于每个具有配置插件的 Expo 库,您可以在该库的 API 参考中找到更多信息。例如,expo-camera 库有一个配置插件部分

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

在重新构建原生项目之前,变更不会生效,例如在 Xcode 中。 如果您在没有原生目录的项目中使用配置插件(CNG 项目),它们将会在 EAS Build 的 prebuild 步骤中应用,或在本地运行 npx expo prebuild|android|ios 时应用。