有关使用 Expo 模块 API 创建带有配置插件的原生模块的教程。
配置插件可让你自定义使用 npx expo prebuild
生成的原生 Android 和 iOS 项目。你可以使用它们将属性添加到原生配置文件、将资源复制到原生项目或应用高级配置,例如添加 应用扩展目标。
¥Config plugins let you customize native Android and iOS projects generated with npx expo prebuild
. You can use them to add properties to native config files, copy assets to native projects, or apply advanced configurations, such as adding an app extension target.
作为应用开发者,配置插件可帮助你应用默认 应用配置 中未公开的自定义项。作为库作者,它们使你能够为使用库的开发者自动配置原生项目。
¥As an app developer, config plugins help you apply customizations not exposed in the default app config. As a library author, they enable you to configure native projects automatically for developers using your library.
本教程介绍如何从头开始创建新的配置插件,并从 Expo 模块读取你的插件注入 AndroidManifest.xml 和 Info.plist 的自定义值。
¥This tutorial explains how to create a new config plugin from scratch and read custom values that your plugin injects into AndroidManifest.xml and Info.plist from an Expo module.
1
¥Initialize a module
首先使用 create-expo-module
初始化一个新的 Expo 模块项目。这将为 Android、iOS 和 TypeScript 设置脚手架,并包含一个示例项目以在应用中测试模块。运行以下命令开始:
¥Start by initializing a new Expo module project with create-expo-module
. This sets up scaffolding for Android, iOS, and TypeScript and includes an example project to test the module within an app. Run the following command to get started:
-
npx create-expo-module expo-native-configuration
本指南使用 expo-native-configuration
/ExpoNativeConfiguration
作为项目名称。但是,你可以选择任何你喜欢的名称。
¥This guide uses the name expo-native-configuration
/ExpoNativeConfiguration
for the project. However, you can choose any name you prefer.
2
¥Set up workspace
在此示例中,你不需要 create-expo-module
包含的视图模块。使用以下命令清理默认模块:
¥In this example, you don't need the view module included by create-expo-module
. Clean up the default module with the following command:
-
cd expo-native-configuration
-
rm android/src/main/java/expo/modules/nativeconfiguration/ExpoNativeConfigurationView.kt
-
rm ios/ExpoNativeConfigurationView.swift
-
rm src/ExpoNativeConfigurationView.tsx src/ExpoNativeConfiguration.types.ts
-
rm src/ExpoNativeConfigurationView.web.tsx src/ExpoNativeConfigurationModule.web.ts
找到以下文件并将其替换为提供的最小样板文件:
¥Locate the following files and replace them with the provided minimal boilerplate:
android/src/main/java/expo/modules/nativeconfiguration/ExpoNativeConfigurationModule.kt
ios/ExpoNativeConfigurationModule.swift
src/ExpoNativeConfigurationModule.ts
src/index.ts
example/App.tsx
package expo.modules.nativeconfiguration
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoNativeConfigurationModule : Module() {
override fun definition() = ModuleDefinition {
Name("ExpoNativeConfiguration")
Function("getApiKey") {
return@Function "api-key"
}
}
}
import ExpoModulesCore
public class ExpoNativeConfigurationModule: Module {
public func definition() -> ModuleDefinition {
Name("ExpoNativeConfiguration")
Function("getApiKey") { () -> String in
"api-key"
}
}
}
import { NativeModule, requireNativeModule } from 'expo';
declare class ExpoNativeConfigurationModule extends NativeModule {
getApiKey(): string;
}
// This call loads the native module object from the JSI.
export default requireNativeModule<ExpoNativeConfigurationModule>('ExpoNativeConfiguration');
import ExpoNativeConfigurationModule from './ExpoNativeConfigurationModule';
export function getApiKey(): string {
return ExpoNativeConfigurationModule.getApiKey();
}
import * as ExpoNativeConfiguration from 'expo-native-configuration';
import { Text, View } from 'react-native';
export default function App() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>API key: {ExpoNativeConfiguration.getApiKey()}</Text>
</View>
);
}
3
¥Run the example project
在项目的根目录中,运行 TypeScript 编译器来监视更改并重建模块的 JavaScript:
¥In the root of your project, run the TypeScript compiler to watch for changes and rebuild the module's JavaScript:
# Run this in the root of the project to start the TypeScript compiler
-
npm run build
在另一个终端窗口中,编译并运行示例应用:
¥In another terminal window, compile and run the example app:
# Navigate to the example project
-
cd example
# Run the example app on iOS
-
npx expo run:ios
# Run the example app on Android
-
npx expo run:android
你应该会看到一个屏幕,上面写着“API 密钥:api-key”。
¥You should see a screen with the text "API key: api-key".
4
¥Create a new config plugin
插件是同步函数,接受 ExpoConfig
并返回修改后的 ExpoConfig
。按照惯例,这些函数以单词 with
为前缀。将你的插件命名为 withMyApiKey
或使用其他名称,只要它遵循此约定即可。
¥Plugins are synchronous functions that accept an ExpoConfig
and return a modified ExpoConfig
. By convention, these functions are prefixed with the word with
. Name your plugin withMyApiKey
or use a different name, as long as it follows this convention.
以下是基本配置插件功能的示例:
¥Here is an example of a basic config plugin function:
const withMyApiKey = config => {
return config;
};
你还可以使用 mods
,它们是修改原生项目中的文件(例如源代码或配置文件(plist、xml))的异步函数。mods
对象与应用配置的其余部分不同,因为它在初始读取后不会序列化。这允许你在代码生成期间执行操作。
¥You can also use mods
, which are async functions that modify files in native projects, such as source code or configuration files (plist, xml). The mods
object is different from the rest of the app config because it doesn't serialize after the initial reading. This allows you to perform actions during code generation.
编写配置插件时,请遵循以下注意事项:
¥When writing config plugins, follow these considerations:
插件必须是同步的,并且它们的返回值必须是可序列化的,除了添加的任何 mods
。
¥Plugins must be synchronous, and their return value must be serializable, except for any mods
that are added.
每当 expo/config
中的 getConfig
方法读取配置时,就会调用 plugins
。相比之下,mods
仅在 npx expo prebuild
的 "syncing" 阶段调用。
¥plugins
are invoked whenever the getConfig
method from expo/config
reads the configuration. In contrast, mods
are invoked only during the "syncing" phase of npx expo prebuild
.
虽然是可选的,但使用
expo-module-scripts
可以简化插件开发。它为 TypeScript 和 Jest 提供了推荐的默认配置。有关详细信息,请参阅 配置插件指南。¥Although optional, use
expo-module-scripts
to simplify plugin development. It provides a recommended default configuration for TypeScript and Jest. For more information, see the config plugins guide.
开始使用这个最小样板创建你的插件。创建一个插件目录,用于在 TypeScript 中编写插件,并在项目根目录中添加一个 app.plugin.js 文件,这将是插件的入口点。
¥Start creating your plugin with this minimal boilerplate. Create a plugin directory for writing the plugin in TypeScript and add an app.plugin.js file in the project root, which will be the plugin's entry point.
¥Create a plugin/tsconfig.json file
{
"extends": "expo-module-scripts/tsconfig.plugin",
"compilerOptions": {
"outDir": "build",
"rootDir": "src"
},
"include": ["./src"],
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
}
¥Create a plugin/src/index.ts file for your plugin
import { ConfigPlugin } from 'expo/config-plugins';
const withMyApiKey: ConfigPlugin = config => {
console.log('my custom plugin');
return config;
};
export default withMyApiKey;
¥Create an app.plugin.js file in the root directory
// This file configures the entry file for your plugin.
module.exports = require('./plugin/build');
在项目的根目录下,运行 npm run build plugin
以在监视模式下启动 TypeScript 编译器。接下来,通过将以下行添加到 example/app.json 文件来配置你的示例项目以使用你的插件:
¥At the root of your project, run npm run build plugin
to start the TypeScript compiler in watch mode. Next, configure your example project to use your plugin by adding the following line to the example/app.json file:
{
"expo": {
...
"plugins": ["../app.plugin.js"]
}
}
当你在示例目录中运行 npx expo prebuild
命令时,终端会通过控制台语句记录 "我的自定义插件"。
¥When you run the npx expo prebuild
command inside your example directory, the terminal logs "my custom plugin" through a console statement.
-
cd example
-
npx expo prebuild --clean
要将你的自定义 API 密钥注入 AndroidManifest.xml 和 Info.plist,请使用助手 mods
由 expo/config-plugins
提供。这些使修改原生文件变得容易。对于此示例,请使用 withAndroidManifest
和 withInfoPlist
。
¥To inject your custom API keys into AndroidManifest.xml and Info.plist, use helper mods
provided by expo/config-plugins
. These make it easy to modify native files. For this example, use withAndroidManifest
and withInfoPlist
.
顾名思义,withAndroidManifest
允许你读取和修改 AndroidManifest.xml 文件。使用 AndroidConfig
助手将元数据项添加到主应用,如下所示:
¥As the name suggests, withAndroidManifest
allows you to read and modify the AndroidManifest.xml file. Use AndroidConfig
helpers to add a metadata item to the main application, as shown below:
const withMyApiKey: ConfigPlugin<{ apiKey: string }> = (config, { apiKey }) => {
config = withAndroidManifest(config, config => {
const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
AndroidConfig.Manifest.addMetaDataItemToMainApplication(
mainApplication,
'MY_CUSTOM_API_KEY',
apiKey
);
return config;
});
return config;
};
同样,你可以使用 withInfoPlist
修改 Info.plist 值。使用 modResults
属性,你可以添加自定义值,如下面的代码片段所示:
¥Similarly, you can use withInfoPlist
to modify the Info.plist values. Using the modResults
property, you can add custom values as shown in the code snippet below:
const withMyApiKey: ConfigPlugin<{ apiKey: string }> = (config, { apiKey }) => {
config = withInfoPlist(config, config => {
config.modResults['MY_CUSTOM_API_KEY'] = apiKey;
return config;
});
return config;
};
你可以通过将所有内容合并到一个函数中来创建自定义插件:
¥You can create a custom plugin by merging everything into a single function:
import {
withInfoPlist,
withAndroidManifest,
AndroidConfig,
ConfigPlugin,
} from 'expo/config-plugins';
const withMyApiKey: ConfigPlugin<{ apiKey: string }> = (config, { apiKey }) => {
config = withInfoPlist(config, config => {
config.modResults['MY_CUSTOM_API_KEY'] = apiKey;
return config;
});
config = withAndroidManifest(config, config => {
const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
AndroidConfig.Manifest.addMetaDataItemToMainApplication(
mainApplication,
'MY_CUSTOM_API_KEY',
apiKey
);
return config;
});
return config;
};
export default withMyApiKey;
插件准备就绪后,更新示例应用以将你的 API 密钥作为配置选项传递给插件。修改 example/app.json 中的 plugins
字段,如下所示:
¥With the plugin ready to use, update the example app to pass your API key to the plugin as a configuration option. Modify the plugins
field in example/app.json as shown below:
{
"expo": {
...
"plugins": [["../app.plugin.js", { "apiKey": "custom_secret_api" }]]
}
}
进行此更改后,通过在示例目录中运行 npx expo prebuild --clean
来测试插件是否正常工作。此命令执行你的插件并更新原生文件,将 "MY_CUSTOM_API_KEY"
注入 AndroidManifest.xml 和 Info.plist。你可以通过检查 example/android/app/src/main/AndroidManifest.xml 的内容来验证这一点。
¥After making this change, test that the plugin works correctly by running npx expo prebuild --clean
inside the example directory. This command executes your plugin and updates native files, injecting "MY_CUSTOM_API_KEY"
into AndroidManifest.xml and Info.plist. You can verify this by checking the contents of example/android/app/src/main/AndroidManifest.xml.
5
¥Read native values from the module
现在,使用特定于平台的方法访问其内容,让你的原生模块读取添加到 AndroidManifest.xml 和 Info.plist 的字段。
¥Now, make your native module read the fields added to AndroidManifest.xml and Info.plist by using platform-specific methods to access their contents.
在 Android 上,使用 packageManager
类从 AndroidManifest.xml 文件访问元数据信息。要读取 "MY_CUSTOM_API_KEY"
值,请更新 android/src/main/java/expo/modules/nativeconfiguration/ExpoNativeConfigurationModule.kt 文件:
¥On Android, access metadata information from the AndroidManifest.xml file using the packageManager
class. To read the "MY_CUSTOM_API_KEY"
value, update the android/src/main/java/expo/modules/nativeconfiguration/ExpoNativeConfigurationModule.kt file:
package expo.modules.nativeconfiguration
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import android.content.pm.PackageManager
class ExpoNativeConfigurationModule() : Module() {
override fun definition() = ModuleDefinition {
Name("ExpoNativeConfiguration")
Function("getApiKey") {
val applicationInfo = appContext?.reactContext?.packageManager?.getApplicationInfo(appContext?.reactContext?.packageName.toString(), PackageManager.GET_META_DATA)
return@Function applicationInfo?.metaData?.getString("MY_CUSTOM_API_KEY")
}
}
}
在 iOS 上,你可以使用 Bundle.main.object(forInfoDictionaryKey: "")
方法读取 Info.plist 属性的内容。要访问之前添加的 "MY_CUSTOM_API_KEY"
值,请更新 ios/ExpoNativeConfigurationModule.swift 文件,如下所示:
¥On iOS, you can read the content of an Info.plist property using the Bundle.main.object(forInfoDictionaryKey: "")
method. To access the "MY_CUSTOM_API_KEY"
value added earlier, update the ios/ExpoNativeConfigurationModule.swift file as shown:
import ExpoModulesCore
public class ExpoNativeConfigurationModule: Module {
public func definition() -> ModuleDefinition {
Name("ExpoNativeConfiguration")
Function("getApiKey") {
return Bundle.main.object(forInfoDictionaryKey: "MY_CUSTOM_API_KEY") as? String
}
}
}
6
¥Run your module
使用你的原生模块读取添加到原生文件的字段,你现在可以运行示例应用并使用 ExamplePlugin.getApiKey()
函数访问你的自定义 API 密钥。
¥With your native modules reading the fields added to the native files, you can now run the example app and access your custom API key using the ExamplePlugin.getApiKey()
function.
-
cd example
# execute our plugin and update native files
-
npx expo prebuild
# Run the example app on Android
-
npx expo run:android
# Run the example app on iOS
-
npx expo run:ios
¥Next steps
恭喜,你已创建与 Android 和 iOS 的 Expo 模块交互的配置插件!
¥Congratulations, you have created a config plugin that interacts with an Expo module for Android and iOS!
如果你想挑战自己并使插件更加通用,那么这个练习适合你。修改插件以允许传入任意一组配置键和值,并添加从模块读取任意键的功能。
¥If you want to challenge yourself and make the plugin more versatile, this exercise is open for you. Modify the plugin to allow any arbitrary set of config keys and values to be passed in, and add functionality to read arbitrary keys from the module.
使用 Kotlin 和 Swift 创建原生模块的参考。
了解如何添加对 macOS 和 tvOS 平台的支持。