教程:创建具有配置插件的模块

编辑

一篇关于使用 Expo Modules API 创建本地模块和配置插件的教程。


配置插件 让您可以自定义通过 npx expo prebuild 生成的本地 Android 和 iOS 项目,适用于 持续本地生成 (CNG) 项目。您可以利用它们向本地配置文件添加属性,复制资产到本地项目,或应用高级配置,例如添加 应用扩展目标

作为应用开发者,配置插件帮助您应用在默认的 应用配置 中未公开的自定义设置。作为库作者,它们使您能够为使用您库的开发者自动配置本地项目。

本教程解释了如何从零开始创建一个新的配置插件,并读取您插件注入到 AndroidManifest.xmlInfo.plist 中的自定义值。

1

初始化模块

开始时,用 create-expo-module 初始化一个新的 Expo 模块项目。这将设置 Android、iOS 和 TypeScript 的脚手架,并包含一个示例项目以在应用中测试模块。运行以下命令开始:

Terminal
npx create-expo-module expo-native-configuration

本指南使用名称 expo-native-configuration/ExpoNativeConfiguration 作为模块项目的名称,您可以选择任何您喜欢的名称。

2

设置工作区

在本示例中,您不需要 create-expo-module 包含的视图模块。使用以下命令清理默认模块:

Terminal
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
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 class ExpoNativeConfigurationModule : Module() { override fun definition() = ModuleDefinition { Name("ExpoNativeConfiguration") Function("getApiKey") { return@Function "api-key" } } }
ios/ExpoNativeConfigurationModule.swift
import ExpoModulesCore public class ExpoNativeConfigurationModule: Module { public func definition() -> ModuleDefinition { Name("ExpoNativeConfiguration") Function("getApiKey") { () -> String in "api-key" } } }
src/ExpoNativeConfigurationModule.ts
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');
src/index.ts
import ExpoNativeConfigurationModule from './ExpoNativeConfigurationModule'; export function getApiKey(): string { return ExpoNativeConfigurationModule.getApiKey(); }
example/App.tsx
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> ); }
package.json
{ %%placeholder-start%%... %%placeholder-end%% "dependencies": { "expo-native-configuration": "file:.." %%placeholder-start%%... %%placeholder-end%% } }

3

运行示例项目

在您的项目根目录中,运行 TypeScript 编译器以观察更改并重新构建模块的 JavaScript:

Terminal
# 在项目根目录中运行此命令以启动 TypeScript 编译器
npm run build

在另一个终端窗口中,编译并运行示例应用程序:

Terminal
# 导航到示例项目
cd example

# 重新安装依赖项
rm -rf node_modules && npm install

# 在 Android 上运行示例应用
npx expo run:android

# 在 iOS 上运行示例应用
npx expo run:ios

您应该会看到一个显示文本 "API key: api-key" 的屏幕。

4

创建新的配置插件

插件 是接受 ExpoConfig 并返回修改过的 ExpoConfig 的同步函数。根据约定,这些函数以 with 作为前缀。将您的插件命名为 withMyApiKey,或使用其他名称,只要遵循这个约定即可。

以下是一个基本配置插件函数的示例:

const withMyApiKey = config => { return config; };

您还可以使用 mods,它是异步函数,用于修改本地项目中的文件,例如源代码或配置文件 (plist, xml)。mods 对象不同于其余的应用配置,因为它在初始读取后不会序列化。这使您可以在代码生成时执行操作。

编写配置插件时,请遵循以下注意事项:

  • 插件必须是同步的,其返回值必须是可序列化的,除了添加的任何 mods
  • plugins 在每次 expo/configgetConfig 方法读取配置时被调用。相比之下,mods 仅在 npx expo prebuild 的“同步”阶段被调用。

尽管是可选的,但使用 expo-module-scripts 可以简化插件开发。它为 TypeScript 和 Jest 提供了推荐的默认配置。更多信息请参见 配置插件指南

开始创建您的插件,使用此最小样板。在 TypeScript 中为编写插件创建一个 plugin 目录,并在项目根目录中添加一个 app.plugin.js 文件,这将是插件的入口点。

创建 plugin/tsconfig.json 文件

plugin/tsconfig.json
{ "extends": "expo-module-scripts/tsconfig.plugin", "compilerOptions": { "outDir": "build", "rootDir": "src" }, "include": ["./src"], "exclude": ["**/__mocks__/*", "**/__tests__/*"] }

为插件创建一个 plugin/src/index.ts 文件

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 文件

app.plugin.js
// 此文件配置插件的入口文件。 module.exports = require('./plugin/build');

在您的项目根目录中运行 npm run build plugin 以启动 TypeScript 编译器的观察模式。接下来,通过在 example/app.json 文件中添加以下行,配置您的示例项目以使用您的插件:

example/app.json
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "plugins": ["../app.plugin.js"] } }

当您在 example 目录中运行 npx expo prebuild 命令时,终端会通过控制台语句记录 "my custom plugin"。

Terminal
cd example
npx expo prebuild --clean

要将自定义 API 密钥注入到 AndroidManifest.xmlInfo.plist 中,请使用 expo/config-plugins 提供的帮助器 mods。这些帮助器使修改本地文件变得简单。对于这个示例,使用 withAndroidManifestwithInfoPlist

如其名称所示,withAndroidManifest 允许您读取和修改 AndroidManifest.xml 文件。使用 AndroidConfig 帮助器将元数据项添加到主应用程序,如下所示:

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; };

您可以通过将所有内容合并到单个函数中创建自定义插件:

plugin/src/index.ts
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 字段修改如下:

example/app.json
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "plugins": [["../app.plugin.js", { "apiKey": "custom_secret_api" }]] } }

在进行此更改后,通过在 example 目录中运行 npx expo prebuild --clean 测试插件是否正常工作。此命令会执行您的插件并更新本地文件,将 "MY_CUSTOM_API_KEY" 注入到 AndroidManifest.xmlInfo.plist 中。您可以通过检查 example/android/app/src/main/AndroidManifest.xmlexample/ios/exponativeconfigurationexample/Info.plist 的内容来验证。

5

从模块读取本地值

现在,让您的本地模块读取添加到 AndroidManifest.xmlInfo.plist 中的字段,使用平台特定的方法访问其内容。

在 Android 上,使用 packageManager 类访问 AndroidManifest.xml 文件中的元数据。要读取 "MY_CUSTOM_API_KEY" 值,更新 android/src/main/java/expo/modules/nativeconfiguration/ExpoNativeConfigurationModule.kt 文件:

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 文件,如下所示:

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

运行您的模块

通过本地模块读取添加到本地文件的字段,您现在可以运行示例应用程序,并通过 ExamplePlugin.getApiKey() 函数访问您的自定义 API 密钥。

Terminal
cd example

# 执行您的插件并更新本地文件
npx expo prebuild

# 在 Android 上运行示例应用
npx expo run:android

# 在 iOS 上运行示例应用
npx expo run:ios

后续步骤

恭喜您,您创建了一个与 Android 和 iOS 的 Expo 模块交互的配置插件!

如果您想挑战自己,使插件更具多功能性,欢迎进行此练习。修改插件以允许传入任何任意配置键和值,并添加功能以从模块读取任意键。

Expo Modules API Reference

用于使用 Kotlin 和 Swift 创建本地模块的参考。

额外平台支持

了解如何添加对 macOS 和 tvOS 平台的支持。