开发和调试插件
编辑页面
了解 Expo 配置插件的开发最佳实践和调试技巧。
For the complete documentation index, see llms.txt. Use this file to discover all available pages.
开发插件是扩展 Expo 生态系统的绝佳方式。不过,有时你会想要调试你的插件。本页面介绍了开发和调试插件的一些最佳实践。
插件开发
使用 modifier 预览 实时调试你的插件结果。
为了让插件开发更轻松,我们已为 expo-module-scripts 添加了插件支持。
有关使用 TypeScript 和 Jest 构建插件的更多信息,请参阅 配置插件指南。
安装依赖
在提供配置插件的库中使用以下依赖:
{ "dependencies": {}, "devDependencies": { "expo": "^56.0.0" }, "peerDependencies": { "expo": ">=56.0.0" }, "peerDependenciesMeta": { "expo": { "optional": true } } }
- 你可以更新
expo的具体版本,以便针对特定版本进行构建。 - 对于依赖核心稳定 API 的简单配置插件,例如只修改 AndroidManifest.xml 或 Info.plist 的插件,你可以像上面的示例那样使用较宽松的依赖。
- 你也可以将
expo-module-scripts作为开发依赖安装,但这不是必需的。
导入配置插件包
expo/config-plugins 和 expo/config 包会从 expo 包中重新导出。
const { ... } = require('expo/config-plugins'); const { ... } = require('expo/config');
通过 expo 包导入可确保你使用的是 expo 包所依赖的 expo/config-plugins 和 expo/config 包的版本。
如果你不通过这种方式从 expo 的重新导出中导入该包,你可能会意外导入到一个不兼容的版本
(这取决于使用该模块的开发者所使用的包管理器中模块提升的实现细节),或者根本无法导入该模块(如果使用诸如 Yarn Berry 或 pnpm 等包管理器的“plug and play”功能)。
Config 类型直接从 expo/config 导出,因此无需安装或从 expo/config-types 导入:
import { ExpoConfig, ConfigContext } from 'expo/config';
最佳实践:mod
- 避免使用正则表达式: 静态修改 是关键。如果你想修改 Android gradle 文件中的某个值,可以考虑使用
gradle.properties。如果你想修改 Podfile 中的一些代码,可以考虑将值写入 JSON,并让 Podfile 读取这些静态值。 - 避免在 mod 中执行耗时较长的任务,例如发起网络请求或安装 Node 模块。
- 不要在 mod 中添加交互式终端提示。
- 仅在危险 mod 中生成、移动和删除新文件。否则会破坏 内省。
- 利用内置的配置插件,例如
withXcodeProject,以尽量减少文件被读取和解析的次数。 - 使用 prebuild 内部采用的 XML 解析库,这有助于防止代码被不必要地重新排列。
插件结构和脚手架
版本管理
默认情况下,npx expo prebuild 会在与项目所使用的 Expo SDK 版本相关联的 源模板 上运行转换。SDK 版本定义在 app.json 中,或者根据项目已安装的 expo 版本推断得出。
例如,当 Expo SDK 升级到 React Native 的新版本时,模板可能会发生重大变化,以适应 React Native 或 Android、iOS 新版本带来的变更。
如果你的插件主要使用 静态修改,那么它通常可以在不同 SDK 版本之间正常工作。如果它使用正则表达式来转换应用代码,那么你一定需要注明你的插件适用于哪个 Expo SDK 版本。在 SDK 发布周期中,会有一个 beta 期间,你可以在正式发布前测试你的插件是否适用于新版本。
插件属性
属性用于自定义插件在 prebuild 期间的工作方式。它们必须始终是静态值(不能是函数或 Promise)。请考虑以下类型:
type StaticValue = boolean | number | string | null | StaticArray | StaticObject; type StaticArray = StaticValue[]; interface StaticObject { [key: string]: StaticValue | undefined; }
之所以需要静态属性,是因为应用配置必须可序列化为 JSON,才能用作应用清单。
如果可能,请尽量让插件在不使用 props 的情况下也能工作,这将有助于解析工具,例如 expo install 或 VS Code Expo Tools 更好地工作。请记住,你添加的每一个属性都会增加复杂度,使未来更难修改,并增加你需要测试的功能数量。在可行的情况下,优先使用良好的默认值,而不是强制配置。
开发环境
工具
我们强烈建议安装 Expo Tools VS Code 扩展,因为它会对插件执行自动验证,并显示错误信息,以及为 Config Plugin 开发带来其他一些体验改进。
搭建一个 playground 环境
你可以很容易地使用 JS 来开发插件,但如果你想设置 Jest 测试并使用 TypeScript,你会需要一个 monorepo。
monorepo 可以让你在一个 node 模块上进行开发,并像它已发布到 npm 一样将其导入到你的应用配置中。Expo 配置插件内置了完整的 monorepo 支持,所以你只需要搭建一个项目即可。
在 monorepo 的 packages/ 目录中,创建一个模块,并在其中 引导一个 config plugin。
手动运行插件
如果你不想搭建 monorepo,也可以尝试手动运行插件:
- 在包含 config plugin 的包中运行
npm pack - 在你的测试项目中,运行
npm install path/to/react-native-my-package-1.0.0.tgz,这会把该包添加到你的 package.json 的dependencies对象中。 - 将该包添加到你的 app.json 中的
plugins数组:{ "plugins": ["react-native-my-package"] }- 如果你安装了 VS Code Expo Tools,插件应当可以自动补全。
- 如果你需要更新该包,请修改包的 package.json 中的
version,然后重复上述过程。
使用插件修改原生文件
修改 AndroidManifest.xml
在使用 config plugin 之前,包应尝试使用内置的 AndroidManifest.xml 合并系统。这可用于静态的、非可选的功能,比如权限。这样可以确保功能在构建时而不是 prebuild 时被合并,从而最大限度地减少由于用户忘记执行 prebuild 而导致配置被遗漏的可能性。缺点是用户无法使用 introspection 来预览更改并调试任何潜在问题。
以下是一个包的 AndroidManifest.xml 示例,它注入了一个必需的权限:
<manifest package="expo.modules.filesystem" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET"/> </manifest>
如果你正在为本地项目构建插件,或者你的包需要更多控制,那么你应该实现一个插件。
你可以使用内置类型和辅助工具来简化处理复杂对象的过程。
下面是一个向默认的 <application android:name=".MainApplication" /> 添加 <meta-data android:name="..." android:value="..."/> 的示例。
import { AndroidConfig, ConfigPlugin, withAndroidManifest } from 'expo/config-plugins'; import { ExpoConfig } from 'expo/config'; // 使用辅助工具可以保持错误信息统一,并有助于减少 XML 格式变更。 const { addMetaDataItemToMainApplication, getMainApplicationOrThrow } = AndroidConfig.Manifest; export const withMyCustomConfig: ConfigPlugin = config => { return withAndroidManifest(config, async config => { // 修改器可以是 async 的,但尽量保持它们运行快速。 config.modResults = await setCustomConfigAsync(config, config.modResults); return config; }); }; // 将这个函数从 mod 中拆分出来会更容易测试。 async function setCustomConfigAsync( config: Pick<ExpoConfig, 'android'>, androidManifest: AndroidConfig.Manifest.AndroidManifest ): Promise<AndroidConfig.Manifest.AndroidManifest> { const appId = 'my-app-id'; // 获取 <application /> 标签,如果不存在则抛出错误。 const mainApplication = getMainApplicationOrThrow(androidManifest); addMetaDataItemToMainApplication( mainApplication, // `android:name` 的值 'my-app-id-key', // `android:value` 的值 appId ); return androidManifest; }
修改 Info.plist
使用 withInfoPlist 比直接在 app.json 中静态修改 expo.ios.infoPlist 对象要更安全一些,因为它会读取 Info.plist 的内容,并将其与 expo.ios.infoPlist 合并,这意味着你可以尝试避免自己的更改被覆盖。
下面是一个向 Info.plist 添加 GADApplicationIdentifier 的示例:
import { ConfigPlugin, withInfoPlist } from 'expo/config-plugins'; // 传入 `<string>` 来指定这个插件需要一个字符串属性。 export const withCustomConfig: ConfigPlugin<string> = (config, id) => { return withInfoPlist(config, config => { config.modResults.GADApplicationIdentifier = id; return config; }); };
修改 iOS Podfile
iOS 的 Podfile 是 CocoaPods 的配置文件,而 CocoaPods 是 iOS 上的依赖管理器。它类似于 iOS 的 package.json。 Podfile 是一个 Ruby 文件,这意味着你不能安全地通过 Expo config plugins 去修改它,而应该选择其他方式,例如 Expo Autolinking hooks。
我们确实提供了一种可以安全操作 Podfile 的机制,但它非常有限。
带版本的 模板 Podfile 被硬编码为从一个静态 JSON 文件 Podfile.properties.json 中读取,我们提供了一个 mod(ios.podfileProperties,withPodfileProperties)来安全地读写这个文件。
这被 expo-build-properties 用于配置 JavaScript 引擎。
将插件添加到 pluginHistory
_internal.pluginHistory 的创建是为了防止在从旧版 UNVERSIONED 插件迁移到版本化插件时,重复运行插件。
import { ConfigPlugin, createRunOncePlugin } from 'expo/config-plugins'; // 保持名称和版本与它的包同步。 const pkg = require('my-cool-plugin/package.json'); const withMyCoolPlugin: ConfigPlugin = config => config; // 一个辅助方法,用于包装 `withRunOnce` 并将项目追加到 `pluginHistory`。 export default createRunOncePlugin( // 要保护的插件。 withMyCoolPlugin, // 用于跟踪插件是否已经运行过的标识符。 pkg.name, // 可选的 version 属性,如果省略,默认值为 UNVERSIONED。 pkg.version );
配置 Android 应用启动
你可能会发现,你的项目需要在 JS 引擎启动之前先完成配置。
例如,在 Android 上的 expo-splash-screen 中,我们需要在 MainActivity.java 的 onCreate 方法里指定 resize mode。
我们不是尝试通过一个危险的 mod,用正则表达式把这些更改不安全地注入到 MainActivity 中,而是使用一套生命周期钩子和静态设置系统,
以安全地确保该功能在所有受支持的 Android 语言(Java、Kotlin)、Expo 版本以及 config plugins 的组合中都能正常工作。
这个系统由三个组件组成:
ReactActivityLifecycleListeners:由expo-modules-core暴露的一个接口,用于在项目的ReactActivity的onCreate方法被调用时获取原生回调。withStringsXml:由expo/config-plugins暴露的一个 mod,它会向 Android 的 strings.xml 文件写入一个属性,库可以安全地读取该 strings.xml 值并进行初始设置。字符串 XML 值遵循指定格式以保持一致性。SingletonModule(可选):由expo-modules-core暴露的一个接口,用于在原生模块和ReactActivityLifecycleListeners之间创建共享接口。
请考虑这个示例:我们希望在 onCreate 方法被调用后,直接为 Android Activity 的某个属性设置一个自定义的“value”字符串。
我们可以通过创建一个 node 模块 expo-custom、实现 expo-modules-core,以及 Expo config plugins 来安全地做到这一点:
首先,我们在 Android 原生模块中注册 ReactActivity 监听器;只有在用户的项目中已设置 expo-modules-core 支持时,这个监听器才会被调用(这在通过 Expo CLI、Create React Native App、Ignite CLI 以及 Expo prebuilding 引导的项目中是默认配置)。
package expo.modules.custom import android.content.Context import expo.modules.core.BasePackage import expo.modules.core.interfaces.ReactActivityLifecycleListener class CustomPackage : BasePackage() { override fun createReactActivityLifecycleListeners(activityContext: Context): List<ReactActivityLifecycleListener> { return listOf(CustomReactActivityLifecycleListener(activityContext)) } // ... }
接下来我们实现 ReactActivity 监听器,它会接收 Context,并且能够读取项目的 strings.xml 文件。
package expo.modules.custom import android.app.Activity import android.content.Context import android.os.Bundle import android.util.Log import expo.modules.core.interfaces.ReactActivityLifecycleListener class CustomReactActivityLifecycleListener(activityContext: Context) : ReactActivityLifecycleListener { override fun onCreate(activity: Activity, savedInstanceState: Bundle?) { // 在 JS 引擎启动之前执行静态任务。 // 这些值通过 config plugins 定义。 var value = getValue(activity) if (value != "") { // 对 Activity 执行某些需要静态值的操作... } } // 命名规则是 node 模块名(`expo-custom`)加上 value 名称(`value`),并使用下划线作为分隔符 // 即 `expo_custom_value` // `@expo/vector-icons` + `iconName` -> `expo__vector_icons_icon_name` private fun getValue(context: Context): String = context.getString(R.string.expo_custom_value).toLowerCase() }
我们必须定义默认的 string.xml 值,用户会通过在自己的 strings.xml 文件中使用相同的 name 属性在本地覆盖它。
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="expo_custom_value" translatable="false"></string> </resources>
在这一点上,bare 用户可以通过在本地 strings.xml 文件中创建一个字符串来配置这个值(前提是他们也已经设置了 expo-modules-core 支持):
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="expo_custom_value" translatable="false">我爱 Expo</string> </resources>
对于 managed 用户,我们可以通过 Expo config plugin(安全地!)暴露这个功能:
const { AndroidConfig, withStringsXml } = require('expo/config-plugins'); function withCustom(config, value) { return withStringsXml(config, config => { config.modResults = setStrings(config.modResults, value); return config; }); } function setStrings(strings, value) { // 用于添加 string.xml JSON 项或覆盖同名现有项的辅助函数。 return AndroidConfig.Strings.setStringItem( [ // 以 JSON 表示的 XML // <string name="expo_custom_value" translatable="false">value</string> { $: { name: 'expo_custom_value', translatable: 'false' }, _: value }, ], strings ); }
现在,managed Expo 用户可以这样与这个 API 交互:
{ "expo": { "plugins": [["expo-custom", "我爱 Expo"]] } }
通过重新运行 npx expo prebuild -p(eas build -p android,或 npx expo run:ios),用户现在就可以看到这些更改,并且它们已经安全地应用到他们的 managed 项目中!
正如你从这个示例中看到的那样,我们在很大程度上依赖应用代码(expo-modules-core)与应用代码(原生项目)进行交互。这确保了我们的 config plugins 安全且可靠,希望能长期如此!
调试配置插件
你可以通过运行 EXPO_DEBUG=1 expo prebuild 来调试配置插件。如果启用了 EXPO_DEBUG,将会打印插件堆栈日志,这些日志有助于查看哪些 mod 运行了,以及它们的运行顺序。要查看所有静态插件解析错误,请启用 EXPO_CONFIG_PLUGIN_VERBOSE_ERRORS,这通常只对插件作者有用。默认情况下,一些自动插件错误会被隐藏,因为它们通常与版本问题有关,而且不是很有帮助(也就是,旧版包还没有配置插件)。
运行 npx expo prebuild --clean 会在编译前移除生成的原生目录。
你也可以运行 npx expo config --type prebuild 来打印插件结果,而不评估 mods(不会生成代码)。
Expo CLI 命令可以使用 EXPO_PROFILE=1 进行性能分析。
内省
内省是一种高级技术,用于读取修饰器的已评估结果,而不在项目中生成任何代码。
它可用于快速调试静态修改的结果,而无需运行 prebuild。
你可以通过使用 vscode-expo 的预览功能与内省进行实时交互。
你可以在项目中运行 expo config --type introspect 来尝试内省。
内省仅支持一部分修饰器:
android.manifestandroid.gradlePropertiesandroid.stringsandroid.colorsandroid.colorsNightandroid.stylesios.infoPlistios.entitlementsios.expoPlistios.podfileProperties
内省仅适用于安全的修饰器(如 JSON、XML、plist、properties 这类静态文件),
ios.xcodeproj除外,因为它通常需要文件系统更改,使其不是幂等的。
内省的工作方式是创建自定义的基础 mod,它们像默认的基础 mod 一样工作,不同之处在于它们不会在最后将 modResults 写入磁盘。
它们不会持久化,而是将结果保存到应用配置中的 _internal.modResults 下,后接 mod 的名称
例如 ios.infoPlist mod 会保存到 _internal.modResults.ios.infoPlist: {}。
作为一个真实世界的例子,eas-cli 使用内省来确定托管应用中最终的 iOS entitlements 将是什么,这样它就可以在构建之前将它们与 Apple Developer Portal 同步。内省也可以作为一个方便的调试和开发工具。
旧版插件
为了让 eas build 的行为与经典的 expo build 服务保持一致,我们增加了对“旧版插件(legacy plugins)”的支持:当这些插件安装到项目中时,会自动应用到项目里。
例如,假设某个项目安装了 expo-camera,但在 app.json 中没有配置 plugins: ['expo-camera']。
Expo CLI 会自动将 expo-camera 添加到 plugins 中,以确保项目中会添加所需的摄像头和麦克风权限。
用户仍然可以通过手动将 expo-camera 添加到 plugins 数组中来自定义该插件,而手动定义的插件会优先于自动插件。
你可以运行 expo config --type prebuild,并查看 _internal.pluginHistory 属性,来调试哪些插件被添加了。
这会显示一个对象,其中包含所有使用 expo/config-plugins 中的 withRunOnce 插件添加的插件。
注意,expo-location 使用的是 version: '11.0.0',而 react-native-maps 使用的是 version: 'UNVERSIONED'。这意味着以下几点:
expo-location和react-native-maps都已安装在项目中。expo-location使用的是项目中node_modules/expo-location/app.plugin.js里的插件- 项目中安装的
react-native-maps版本没有插件,因此它会回退到随expo-cli一起提供的、用于旧版支持的未版本化插件。
{ _internal: { pluginHistory: { 'expo-location': { name: 'expo-location', version: '11.0.0', }, 'react-native-maps': { name: 'react-native-maps', version: 'UNVERSIONED', }, }, }, };
为了获得最 稳定 的体验,你应该尽量让项目中没有 UNVERSIONED 插件。这是因为 UNVERSIONED 插件可能不支持你项目中的原生代码。
例如,假设你的项目中有一个 UNVERSIONED 的 Facebook 插件,如果 Facebook 的原生代码或插件发生了破坏性变更,就会破坏项目预构建的方式,并导致构建时报错。
静态修改
插件可以使用正则表达式来转换应用代码,但这些修改是危险的,因为如果模板随着时间发生变化,正则表达式就会变得难以预测(同样地,如果用户手动修改了某个文件,或者使用了自定义模板,也会如此)。下面是一些你不应该手动修改的文件示例,以及可行的替代方案。
Android Gradle 文件
Gradle 文件可以用 Groovy 或 Kotlin 编写。它们用于管理 Android 应用中的依赖、版本以及其他设置。
不要直接使用 withProjectBuildGradle、withAppBuildGradle 或 withSettingsGradle mods 来修改它们,而应使用静态的 gradle.properties 文件。
gradle.properties 是一组静态的键/值对,groovy 文件可以从中读取。例如,假设你想在 Groovy 中控制某个开关:
expo.react.jsEngine=hermes
然后在后面的 Gradle 文件中:
project.ext.react = [enableHermes: findProperty('expo.react.jsEngine') ?: 'jsc']
- 对于
gradle.properties中的键,请使用以.分隔的驼峰命名,通常以expo前缀开头,以表明该属性由 prebuild 管理。 - 要访问该属性,请使用以下两个全局方法之一:
property:获取属性,如果属性未定义则抛出错误。findProperty:获取属性,如果属性缺失则不会抛出错误。这通常可以与?:运算符一起使用,以提供默认值。
通常,你应该只通过 Expo Autolinking 与 Gradle 文件交互,这会为项目文件提供一个程序化接口。
iOS AppDelegate
某些模块可能需要向项目的 AppDelegate 添加委托方法。可以通过使用 AppDelegate subscribers 安全地完成,也可以通过 withAppDelegate mod 以危险方式完成(强烈不推荐)。
使用 AppDelegate subscribers 可以让原生 Expo 模块以安全且可靠的方式响应重要事件。
下面是一些 AppDelegate subscribers 实际工作的示例。此外,你还可以在 GitHub 上的社区仓库中找到许多示例(其中一个示例)。
expo-linking:LinkingAppDelegateSubscriber.swift(openURL)expo-notifications:NotificationsAppDelegateSubscriber.swift(didRegisterForRemoteNotificationsWithDeviceToken、didFailToRegisterForRemoteNotificationsWithError、didReceiveRemoteNotification)
iOS CocoaPods Podfile
Podfile 可以通过正则表达式进行自定义(之所以认为这很危险,是因为这类更改不容易组合,并且多个更改很可能发生冲突),但更可靠的方式是在名为 Podfile.properties.json 的 JSON 文件中设置配置值。下面看看 podfile_properties 是如何用来定制 Podfile 的:
require 'json' podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {} platform :ios, podfile_properties['ios.deploymentTarget'] || '16.4' target 'yolo27' do use_expo_modules! # ... # podfile_properties['your_property'] end
通常,你应该只通过 Expo Autolinking 与 Podfile 交互,这会为项目文件提供一个程序化接口。
自定义基础 modifier
Expo CLI 的 npx expo prebuild 命令会使用 @expo/prebuild-config 来获取默认的基础 modifiers。这些默认值只管理一部分常见文件;如果你想管理自定义文件,可以通过添加新的基础 modifier 在本地实现。
例如,假设你想增加对 ios/*/AppDelegate.h 文件的支持,可以通过添加一个 ios.appDelegateHeader modifier 来实现。
这个示例使用
tsx来提供简单的本地 TypeScript 支持,这并不是严格必需的。了解更多。
import { ConfigPlugin, IOSConfig, Mod, withMod, BaseMods } from 'expo/config-plugins'; import fs from 'fs'; /** * 一个向 prebuild 配置添加新基础 modifier 的插件。 */ export function withAppDelegateHeaderBaseMod(config) { return BaseMods.withGeneratedBaseMods<'appDelegateHeader'>(config, { platform: 'ios', providers: { // 添加一条自定义规则,为 mods.ios.appDelegateHeader 上的 mods 提供 AppDelegate header 数据 appDelegateHeader: BaseMods.provider<IOSConfig.Paths.AppDelegateProjectFile>({ // 获取传递给 read 方法的本地文件路径。 getFilePath({ modRequest: { projectRoot } }) { const filePath = IOSConfig.Paths.getAppDelegateFilePath(projectRoot); // 将 .m 替换为 .h if (filePath.endsWith('.m')) { return filePath.substr(0, filePath.lastIndexOf('.')) + '.h'; } // 可能是 Swift 项目... throw new Error(`Could not locate a valid AppDelegate.h at root: "${projectRoot}"`); }, // 从文件系统读取输入文件。 async read(filePath) { return IOSConfig.Paths.getFileInfo(filePath); }, // 将生成的输出写入文件系统。 async write(filePath: string, { modResults: { contents } }) { await fs.promises.writeFile(filePath, contents); }, }), }, }); } /** * (工具)提供用于修改的 AppDelegate header 文件。 */ export const withAppDelegateHeader: ConfigPlugin<Mod<IOSConfig.Paths.AppDelegateProjectFile>> = ( config, action ) => { return withMod(config, { platform: 'ios', mod: 'appDelegateHeader', action, }); }; // (示例)输出 modifier 的内容。 export const withSimpleAppDelegateHeaderMod = config => { return withAppDelegateHeader(config, config => { console.log('modify header:', config.modResults); return config; }); };
要使用这个新的基础 mod,请将其添加到 plugins 数组中。基础 mod 必须 放在最后,排在所有使用该 mod 的其他插件之后,因为它必须在流程结束时将结果写入磁盘。
// 使用 TS 处理外部文件所需 require('tsx/cjs'); import { withAppDelegateHeaderBaseMod, withSimpleAppDelegateHeaderMod, } from './withAppDelegateHeaderBaseMod.ts'; export default ({ config }) => { if (!config.plugins) config.plugins = []; config.plugins.push( withSimpleAppDelegateHeaderMod, // 基础 mod 必须放在最后 withAppDelegateHeaderBaseMod ); return config; };
更多信息请参见 添加此功能支持的 PR。
expo install
当使用 npx expo install 命令安装一个 node 模块时,如果它包含 config plugin,就会自动将其添加到项目的 app config 中。这让设置更容易,也有助于防止用户忘记添加插件。不过,这也有几个注意事项:
npx expo install只会自动通过根目录下的 app.config.js 文件将 config plugin 添加到 app manifest 中。添加这条规则是为了防止像lodash这样流行的软件包被误认为是 config plugin,从而破坏 prebuild。- 当前没有机制可以检测某个 config plugin 是否具有必需的 props。因此,
expo install只会添加插件,不会尝试添加任何额外 props。例如,expo-camera有可选的额外 props,所以plugins: ['expo-camera']是有效的,但如果它有必需的 props,那么expo-camera就会抛出错误。 - 只有当用户的项目使用静态 app config(app.json 和 app.config.json)时,插件才可以被自动添加。如果用户在使用 app.config.js 的项目中运行
expo install expo-camera,他们会看到类似下面的警告:
Cannot automatically write to dynamic config at: app.config.js Please add the following to your app config { "plugins": [ "expo-camera" ] }