教程:创建带有配置插件的模块
编辑页面
使用 Expo Modules API 创建原生模块和配置插件的教程。
For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.
Config plugins 让你可以自定义在 Continuous Native Generation (CNG) 项目中使用 npx expo prebuild 生成的原生 Android 和 iOS 项目。你可以用它们向原生配置文件添加属性、将资源复制到原生项目,或者应用更高级的配置,例如添加一个 app extension target。
作为应用开发者,config plugins 可以帮助你应用默认 app config 中未暴露的自定义设置。作为库作者,它们可以让你为使用你库的开发者自动配置原生项目。
本教程将说明如何从零开始创建一个新的 config plugin,以及如何从 Expo 模块中读取插件注入到 AndroidManifest.xml 和 Info.plist 的自定义值。
1
2
设置工作区
在这个示例中,你不需要 create-expo-module 包含的 view 模块。使用以下命令清理默认模块:
- 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找到以下文件并用提供的最小样板替换它们:
- android/src/main/java/expo/modules/nativeconfiguration/ExpoNativeConfigurationModule.kt
- ios/ExpoNativeConfigurationModule.swift
- src/ExpoNativeConfigurationModule.ts
- src/index.ts
- example/App.tsx
- package.json
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; } // 此调用从 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> ); }
{ %%placeholder-start%%... %%placeholder-end%% "dependencies": { "expo-native-configuration": "file:.." %%placeholder-start%%... %%placeholder-end%% } }
3
运行示例项目
在项目根目录中,运行 TypeScript 编译器以监听变化并重新构建模块的 JavaScript:
# 在项目根目录中运行此命令以启动 TypeScript 编译器- npm run build在另一个终端窗口中,编译并运行示例应用:
# 导航到示例项目- cd example# 重新安装依赖- rm -rf node_modules && npm install# 在 Android 上运行示例应用- npx expo run:android# 在 iOS 上运行示例应用- npx expo run:ios你应该会看到一个包含文本 "API key: api-key" 的屏幕。
4
创建新的 config plugin
Plugins 是同步函数,接收一个 ExpoConfig 并返回一个修改后的 ExpoConfig。按照惯例,这些函数以 with 开头。将你的插件命名为 withMyApiKey,或者使用其他名称,只要符合这一惯例即可。
下面是一个基础 config plugin 函数示例:
const withMyApiKey = config => { return config; };
你也可以使用 mods,它们是用于修改原生项目中文件(例如源代码或配置文件 plist、xml)的异步函数。mods 对象与应用配置的其他部分不同,因为它在初次读取后不会序列化。这使你可以在 代码生成期间 执行操作。
编写 config plugin 时,请遵循以下注意事项:
- 插件必须是同步的,并且其返回值必须可序列化,除了添加的任何
mods之外。 - 当
expo/config的getConfig方法读取配置时,会调用plugins。相反,mods只会在npx expo prebuild的“同步”阶段被调用。
虽然不是必需的,但建议使用
expo-module-scripts来简化插件开发。它为 TypeScript 和 Jest 提供了推荐的默认配置。更多信息请参阅 config plugins 指南。
先使用这个最小样板创建你的插件。创建一个用于用 TypeScript 编写插件的 plugin 目录,并在项目根目录添加一个 app.plugin.js 文件,它将作为插件入口点。
创建 plugin/tsconfig.json 文件
{ "extends": "expo-module-scripts/tsconfig.plugin", "compilerOptions": { "outDir": "build", "rootDir": "src" }, "include": ["./src"], "exclude": ["**/__mocks__/*", "**/__tests__/*"] }
为你的插件创建 plugin/src/index.ts 文件
import { ConfigPlugin } from 'expo/config-plugins'; const withMyApiKey: ConfigPlugin = config => { console.log('my custom plugin'); return config; }; export default withMyApiKey;
在根目录创建 app.plugin.js 文件
// 此文件为你的插件配置入口文件。 module.exports = require('./plugin/build');
在项目根目录运行 npm run build plugin,以 watch 模式启动 TypeScript 编译器。接下来,通过在 example/app.json 文件中添加以下一行来配置示例项目使用你的插件:
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "plugins": ["../app.plugin.js"] } }
当你在 example 目录中运行 npx expo prebuild 命令时,终端会通过 console 语句输出 "my custom plugin"。
- cd example- npx expo prebuild --clean要将自定义 API key 注入到 AndroidManifest.xml 和 Info.plist 中,请使用 expo/config-plugins 提供的 helper mods。它们可以轻松修改原生文件。在这个示例中,使用 withAndroidManifest 和 withInfoPlist。
顾名思义,withAndroidManifest 允许你读取并修改 AndroidManifest.xml 文件。使用 AndroidConfig helper 向主应用添加一个 metadata 项,如下所示:
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 属性,你可以添加自定义值,如下代码片段所示:
const withMyApiKey: ConfigPlugin<{ apiKey: string }> = (config, { apiKey }) => { config = withInfoPlist(config, config => { config.modResults['MY_CUSTOM_API_KEY'] = apiKey; return config; }); return config; };
你可以将所有内容合并到一个函数中,从而创建一个自定义插件:
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 key 作为配置选项传递给插件。如下所示修改 example/app.json 中的 plugins 字段:
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "plugins": [["../app.plugin.js", { "apiKey": "custom_secret_api" }]] } }
完成此更改后,通过在 example 目录中运行 npx expo prebuild --clean 来测试插件是否正常工作。该命令会执行你的插件并更新原生文件,将 "MY_CUSTOM_API_KEY" 注入到 AndroidManifest.xml 和 Info.plist 中。你可以检查 example/android/app/src/main/AndroidManifest.xml 和 example/ios/exponativeconfigurationexample/Info.plist 的内容来验证这一点。
5
从模块中读取原生值
现在,通过使用平台特定方法访问其内容,让你的原生模块读取添加到 AndroidManifest.xml 和 Info.plist 中的字段。
在 Android 上,可以使用 packageManager 类从 AndroidManifest.xml 文件中访问 metadata 信息。要读取 "MY_CUSTOM_API_KEY" 的值,请更新 android/src/main/java/expo/modules/nativeconfiguration/ExpoNativeConfigurationModule.kt 文件:
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 文件:
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
下一步
恭喜你,你已经创建了一个与 Android 和 iOS 的 Expo 模块交互的配置插件!
如果你想挑战一下自己,让这个插件更灵活,这个练习对你开放。修改插件,使其允许传入任意一组配置键和值,并添加从模块中读取任意键的功能。
使用 Kotlin 和 Swift 创建原生模块的参考文档。
了解如何为 macOS 和 tvOS 平台添加支持。