创建和使用配置插件
了解如何在 Expo 项目中创建和使用配置插件。
本指南涵盖了如何创建配置插件、如何向配置插件传递参数以及如何将多个配置插件串联在一起的章节。它还介绍了如何使用来自 Expo 库的配置插件。
🌐 This guide covers sections on how to create a config plugin, how to pass parameters to a config plugin, and how to chain multiple config plugins together. It also covers how to use a config plugin from an Expo library.
在本指南中,通过下图,你将了解配置插件层次结构的前两部分:
🌐 Using the diagram below, in this guide, you will learn the first two parts of the config plugin hierarchy:
信息 注意: 以下部分使用动态 应用配置(app.config.js/app.config.ts 而不是 app.json),对于使用简单配置插件来说并非必需。然而,当你想创建/使用接受参数的基于函数的配置插件时,则需要使用动态应用配置。
创建配置插件
🌐 Creating a config plugin
在以下部分中,让我们创建一个本地配置插件,为Android的 AndroidManifest.xml 和iOS的 Info.plist 添加一个任意属性 HelloWorldMessage。
🌐 In the following section, let's create a local config plugin that adds an arbitrary property HelloWorldMessage to the AndroidManifest.xml for Android and Info.plist for iOS.
此示例将创建并修改以下文件。要跟随操作,请在项目根目录下创建一个 plugins 文件夹,并在其中创建 withAndroidPlugin.ts、withIosPlugins.ts 和 withPlugin.ts 文件。
🌐 This example will create and modify the following files. To follow along, create a plugins directory in the root of your project, and inside it, create withAndroidPlugin.ts, withIosPlugins.ts, and withPlugin.ts files.
pluginswithAndroidPlugin.tsContains Android-specific modificationswithIosPlugin.tsContains iOS-specific modificationswithPlugin.tsMain plugin file that combines both Android and iOS pluginsapp.config.tsDynamic app config file that uses the plugin1
创建 Android 插件
🌐 Create Android plugin
在 withAndroidPlugin.ts 中,添加以下代码:
🌐 In withAndroidPlugin.ts, add the following code:
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 库导入 ConfigPlugin 和 withAndroidManifest,向 android/app/src/main/AndroidManifest.xml 文件添加了一个元数据条目 HelloWorldMessage。withAndroidManifest mod 插件是一个异步函数,它接受一个配置对象和一个数据对象,并在返回对象之前修改值。
🌐 The example code above adds a meta-data entry HelloWorldMessage to the android/app/src/main/AndroidManifest.xml file by importing ConfigPlugin and withAndroidManifest from the expo/config-plugins library. The withAndroidManifest mod plugin is an asynchronous function that accepts a config and a data object and modifies the value before returning an object.
2
创建 iOS 插件
🌐 Create iOS plugin
在 withIosPlugin.ts 中,添加以下代码:
🌐 In withIosPlugin.ts, add the following code:
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 库导入 ConfigPlugin 和 withInfoPlist,在 ios/<your-project-name>/Info.plist 文件中添加了 HelloWorldMessage 作为自定义键并附带自定义消息。withInfoPlist mod 插件是一个异步函数,它接受一个配置对象和数据对象,并在返回对象之前修改其值。
3
创建一个组合插件
🌐 Create a combined plugin
现在你可以创建一个组合插件,同时应用两个特定平台的插件。这种方法允许单独维护特定平台的代码,同时提供一个统一的入口点。
🌐 Now you can create a combined plugin that applies both platform-specific plugins. This approach allows the maintenance of platform-specific code separately while providing a single entry point.
在 withPlugin.ts 中,添加以下代码:
🌐 In withPlugin.ts, add the following code:
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 支持并转换为动态应用配置
🌐 Add TypeScript support and convert to dynamic app config
我们建议使用 TypeScript 编写配置插件,因为这样可以为配置对象提供智能感知。然而,你的应用配置最终会由 Node.js 评估,而 Node.js 默认并不识别 TypeScript 代码。因此,你需要在 app.config.ts 文件中为来自 plugins 目录的 TypeScript 文件添加解析器。
🌐 We recommend writing config plugins in TypeScript, since this will provide intellisense for the configuration objects. However, your app config is ultimately evaluated by Node.js, which does not recognize TypeScript code by default. Therefore, you will need to add a parser for the TypeScript files from the plugins directory to app.config.ts file.
通过运行以下命令安装 tsx 库:
🌐 Install tsx library by running the following command:
- npm install --save-dev tsx然后,将静态应用配置 (app.json) 更改为 动态应用配置 (app.config.ts) 文件。你可以通过将 app.json 文件重命名为 app.config.ts 并按如下所示更改文件内容来实现。请确保在 app.config.ts 文件的顶部添加以下导入语句:
🌐 Then, change the static app config (app.json) to the dynamic app config (app.config.ts) file. You can do this by renaming the app.json file to app.config.ts and changing the content of the file as shown below. Ensure to add the following import statement at the top of your app.config.ts file:
import 'tsx/cjs'; module.exports = () => { %%placeholder-start%%... rest of your app config %%placeholder-end%% };
5
从你的动态应用配置中调用配置插件
🌐 Call the config plugin from your dynamic app config
现在,你可以从动态应用配置中调用配置插件。为此,你需要将 withPlugin.ts 文件的路径添加到应用配置中的 plugins 数组中:
🌐 Now, you can call the config plugin from your dynamic app config. To do this, you need to add the path to the withPlugin.ts file to the plugins array in your app config:
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"], ], };
要在本地项目中看到自定义配置的应用,请运行以下命令:
🌐 To see the custom config applied in native projects, run the following command:
- npx expo prebuild --clean --no-install要验证应用的自定义配置插件,请打开 android/app/src/main/AndroidManifest.xml 和 ios/<your-project-name>/Info.plist 文件:
<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>
<plist version="1.0"> <dict> <!-- ... --> <key>HelloWorldMessage</key> <string>Hello world, from Expo plugin!</string> <!-- ... --> </dict> </plist>
向配置插件传递参数
🌐 Passing a parameter to a config plugin
你的配置插件可以接受从应用配置传入的参数。要实现这一点,你需要在配置插件函数中读取该参数,然后在应用配置中将包含该参数的对象与配置插件函数一起传入。
🌐 Your config plugin can accept parameters passed from your app config. To do so, you will need to read the parameter in your config plugin function, and then pass an object containing the parameter along with the config plugin function in your app config.
1
考虑之前的例子,我们来向插件传递一个自定义消息。在 withAndroidPlugin.ts 中添加一个 options 对象,并更新 message 变量以使用 options.message 属性:
🌐 Considering the previous example, let's pass a custom message to the plugin. Add an options object in withAndroidPlugin.ts and update the message variable to use the options.message property:
%%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 属性:
🌐 Similarly, add an options object in withIosPlugin.ts and update the message variable to use the options.message property:
%%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 对象传递给两个插件:
🌐 Update the withPlugin.ts file to pass the options object to both plugins:
%%placeholder-start%%...%%placeholder-end%% const withPlugin: ConfigPlugin<{ message?: string }> = (config, options = {}) => { config = withAndroidPlugin(config, options); return withIosPlugin(config, options); };
4
要动态地向插件传递一个值,你可以在应用配置中向插件传递一个带有 message 属性的对象:
🌐 To pass a value dynamically to the plugin, you can pass an object with the message property to the plugin in your app config:
{ %%placeholder-start%%...%%placeholder-end%% plugins: [ [ "./plugins/withPlugin.ts", { message: "Custom message from app.config.ts" }, ], ], }
链式配置插件
🌐 Chaining config plugins
配置插件可以串联使用以应用多个修改。链中的每个插件按照它出现的顺序运行,一个插件的输出会成为下一个插件的输入。这种顺序执行确保插件之间的依赖得到尊重,并允许你控制对原生代码修改的确切顺序。
🌐 Config plugins can be chained together to apply multiple modifications. Each plugin in the chain runs in the order it appears, with the output of one plugin becoming the input for the next. This sequential execution ensures that dependencies between plugins are respected and allows you to control the precise order of modifications to your native code.
要链接配置插件,你可以将插件数组传递给应用配置中的 plugins 数组属性。在 JSON 应用配置文件格式(app.json)中也支持这种用法。
🌐 To chain config plugins, you can pass an array of plugins to the plugins array property in your app config. This is also supported in JSON app config file format (app.json).
module.exports = ({ config }: { config: ExpoConfig }) => { name: 'my app', plugins: [ [withFoo, 'input 1'], [withBar, 'input 2'], [withDelta, 'input 3'], ], };
plugins 数组在底层使用 withPlugins 方法来连接插件。如果你的插件数组变得很长或配置复杂,你可以直接使用 withPlugins 方法来让配置更易读。withPlugins 会将插件串联在一起并按顺序执行。
🌐 The plugins array uses withPlugins method under the hood to chain the plugins. If your plugins array is getting long or has complex configuration, you can use the withPlugins method directly to make your configuration easier to read. withPlugins will chain the plugins together and execute them in order.
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); };
使用配置插件
🌐 Using a config plugin
Expo 配置插件通常包含在 Node.js 模块中。你可以像安装项目中的其他库一样安装它们。
🌐 Expo config plugins are usually included in Node.js modules. You can install them just like other libraries in your project.
例如,expo-camera 有一个插件,可以向 AndroidManifest.xml 和 Info.plist 添加相机权限。要将其安装到你的项目中,请运行以下命令:
🌐 For example, expo-camera has a plugin that adds camera permissions to the AndroidManifest.xml and Info.plist. To install it in your project, run the following command:
- npx expo install expo-camera在你的应用配置中,你可以将 expo-camera 添加到插件列表中:
🌐 In your app config, you can add expo-camera to the list of plugins:
{ "expo": { "plugins": ["expo-camera"] } }
一些配置插件通过允许你传递选项来自定义其配置,从而提供灵活性。为此,你可以传递一个数组,将 Expo 库名称作为第一个参数,将包含选项的对象作为第二个参数。例如,expo-camera 插件允许你自定义相机权限提示信息:
🌐 Some config plugins offer flexibility by allowing you to pass options to customize their configuration. To do this, you can pass an array with the Expo library name as the first argument, and an object containing the options as the second argument. For example, the expo-camera plugin allows you to customize the camera permission message:
{ "expo": { "plugins": [ [ "expo-camera", { "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera." } ] ] } }
信息 提示:对于每个拥有配置插件的 Expo 库,你可以在该库的 API 参考中找到更多相关信息。例如,
expo-camera库有一个配置插件部分。
运行 npx expo prebuild 时,mods 会被编译,本地文件会发生变化。
🌐 On running the npx expo prebuild, the mods are compiled, and the native files change.
这些更改在你重建本地项目之前不会生效,例如使用 Xcode。如果你在没有本地目录的项目(CNG 项目)中使用配置插件,它们将在 EAS 构建的预构建步骤中应用,或者在本地运行 npx expo prebuild|android|ios 时应用。
🌐 The changes don't take effect until you rebuild the native project, for example, with Xcode. If you're using config plugins in a project without native directories (CNG projects), they will be applied during the prebuild step in EAS Build or when running npx expo prebuild|android|ios locally.