Ignite 模板中的 plugins 目录:用 Expo Config Plugins 定制原生配置的完整指南
【免费下载链接】igniteInfinite Red's battle-tested React Native project boilerplate, along with a CLI, component/model generators, and more! 9 years of continuous development and counting.项目地址: https://gitcode.com/GitHub_Trending/ig/ignite
本文以 Ignite 锅炉板(boilerplate)中 plugins 目录文档 为核心骨架,系统讲解 Expo Config Plugins 的作用机制、在 Ignite 应用中的目录约定,以及如何通过app.config.ts将自定义插件接入构建流程。读完本文,你将掌握「零原生代码改动」地注入原生配置的方法,能够为应用添加或修改 Expo 插件,并理解 Ignite 锅炉板自带的app.json/app.config.ts双层配置是如何协作的。
为什么要有一个 plugins 目录
在 Ignite 生成的 Expo 应用中,原生工程(iOS 的 Xcode 工程、Android 的 Gradle 工程)默认不是由你手工维护的,而是由prebuild(预构建)过程在构建时生成或同步的。这一工作流在 Expo 生态中被称为 Continuous Native Generation(CNG):你维护的是一份"配置即源码",原生代码则由 Expo 根据配置自动生成。
CNG 工作流带来的直接收益是:
- 不需要直接操作 Gradle 与 CocoaPods;
- 升级依赖时无需反复迁移手工的原生代码修改;
- 大量流行库都提供现成的 Config Plugins,且自定义 Config Plugin 的编写是官方支持的。
在这套体系下,如果你需要调整原生配置——比如修改 iOS 权限声明、Android 清单、启动屏参数、字体加载等——正确姿势不是去ios/、android/目录里手工改文件(那会被下一次 prebuild 覆盖),而是编写一个Expo Config Plugin。
Ignite 锅炉板因此专门在项目根目录约定了一个plugins目录:按照 Boilerplate.md 对目录结构的说明,它用于存放在 prebuild 生成原生代码过程中需要应用的、任何自定义的 Expo Config Plugins。
配置解析链路:app.json → app.config.ts → plugins 数组
要理解插件如何生效,先要看清 Ignite 锅炉板的两层配置文件(详见 app.json/app.config.ts 说明):
- app.json:静态配置。包含应用名、slug、图标、iOS/Android 基础设置等,也包含默认的
plugins数组; - app.config.ts:动态配置。它接收从
app.json解析出来的ExpoConfig,经过处理后输出 Expo 的最终配置。
app.config.ts的核心实现如下:
import { ExpoConfig, ConfigContext } from "@expo/config" // 使用 tsx/cjs,让 Config Plugins 可以直接用 TypeScript 编写而无需预编译 import "tsx/cjs" module.exports = ({ config }: ConfigContext): Partial<ExpoConfig> => { const existingPlugins = config.plugins ?? [] return { ...config, ios: { ...config.ios, privacyManifests: { NSPrivacyAccessedAPITypes: [ { NSPrivacyAccessedAPIType: "NSPrivacyAccessedAPICategoryUserDefaults", NSPrivacyAccessedAPITypeReasons: ["CA92.1"], }, ], }, }, plugins: [...existingPlugins], } }这段代码透露了几个关键设计:
config.plugins ?? []:动态配置先读取静态配置app.json里已有的插件数组,保证自定义逻辑永远建立在既有插件之上,而不是覆盖它们;plugins: [...existingPlugins]:最终返回的配置把原有插件原样透传,你添加的新插件也应追加到这个数组中;import "tsx/cjs":这是 Ignite 让 Config Plugins 直接用 TypeScript 编写、无需手工编译成 JS 的关键一行;- iOS privacyManifests:锅炉板默认注入了一份 Apple 隐私清单起步配置(
UserDefaults访问原因CA92.1),这是为了满足苹果隐私清单要求而内置的示例,可根据应用实际使用的 API 继续扩充。
如何添加自定义插件:两步接入
根据 Plugins.md 的说明,在 Ignite 应用中接入自定义 Config Plugin 只需两步:
第 1 步:创建插件
在项目根目录的plugins目录下新建一个 TypeScript 文件,导出一个"修改ExpoConfig"的函数。这个函数会被 Expo 在 prebuild 时调用,返回修改后的配置对象(或通过 mods 修改原生工程文件)。
第 2 步:接入动态配置
在 app.config.ts 中导入你的插件,并追加到plugins数组:
// In app.config.ts plugins: [...existingPlugins, require("./plugins/yourCustomPlugin").yourCustomPlugin]这里有两个值得注意的细节:
- 使用
require而不是import:因为app.config.ts本身以 CommonJS 形式导出,同时配合tsx/cjs也能解析 TypeScript 编写的插件文件; - 插件文件被组织在
plugins目录下,路径以项目根目录为基准,例如./plugins/yourCustomPlugin。
一个完整的自定义插件示例
把两步串起来,一个最小可用的自定义插件长这样。在plugins/下创建withAppName.ts:
import { ConfigPlugin, withInfoPlist } from "@expo/config-plugins" // 一个典型的 Config Plugin:接收 config,返回修改后的 config const withCustomAppName: ConfigPlugin<{ appName?: string }> = (config, props) => { // 示例:通过 mods 修改 iOS 的 Info.plist return withInfoPlist(config, (config) => { config.modResults.CFBundleDisplayName = props?.appName ?? "My Ignite App" return config }) } export { withCustomAppName }然后在app.config.ts的返回对象中接入:
module.exports = ({ config }: ConfigContext): Partial<ExpoConfig> => { const existingPlugins = config.plugins ?? [] return { ...config, plugins: [ ...existingPlugins, [require("./plugins/withAppName").withCustomAppName, { appName: "示例应用" }], ], } }注意插件在数组中可以有两种写法:
- 字符串形式:
"expo-font",不带参数; - 元组形式:
["expo-splash-screen", { image: "..." }],第二项是传给插件的配置对象。
锅炉板自带的插件实例:从 app.json 看配置参数
打开 Ignite 锅炉板的 app.json,可以看到默认plugins数组已经预置了 5 个插件,它们是理解插件参数写法的现成范例:
| 插件 | 写法 | 关键参数 |
|---|---|---|
expo-localization | 字符串 | 无参数,启用本地化能力 |
expo-font | 字符串 | 无参数,支持加载自定义字体 |
expo-splash-screen | 元组 | image(启动图路径)、imageWidth(300)、resizeMode("contain")、backgroundColor("#191015") |
react-native-edge-to-edge | 元组 | android.parentTheme("Light")、android.enforceNavigationBarContrast(false) |
expo-build-properties | 字符串 | 无参数,暴露 EAS 构建时的原生构建属性配置 |
以expo-splash-screen为例,它的参数写法展示了元组形式插件的通用结构:第一个元素是模块名,第二个元素是传给插件的 props 对象。这种结构同样适用于你自己编写的带参插件。
另外,从 Ignite CLI 的 new.ts 源码可以看出,生成项目时 CLI 会直接向app.json的plugins数组动态push插件(例如实验性的expo-router),这印证了plugins数组是 Expo 配置体系中一个可编程追加的入口。
关键要点与 mods 的注意事项
综合 Plugins.md 与锅炉板源码,可以提炼出以下关键结论:
- Config Plugins 的本质:它们扩展应用的配置,将原生模块的集成自动化——你写的是声明式的配置,而不是维护一堆原生样板代码;
- 约定目录:自定义插件统一创建在
plugins目录,并在app.config.ts中追加到plugins数组,两个环节缺一不可; - 复杂场景使用 mods:对于超出配置字段范围的复杂原生定制(如改 Gradle 脚本、改 AndroidManifest 的具体节点),Expo 提供了
mods(修改器)机制。但mods 必须谨慎使用——它们直接操作原生工程生成结果,理解不深时容易造成难以排查的构建问题; - CI/构建验证:插件是在 prebuild 阶段被调用的,接入新插件后应重新生成并验证原生工程,确认产物符合预期后再提交配置。
进一步阅读
- plugins 目录官方说明
- app.json / app.config.ts 配置详解
- Ignite 锅炉板目录结构总览
- Expo CNG(Continuous Native Generation)工作流
- Expo 与 Ignite 的协作关系与 Config Plugin 定位
- 动态配置实现:boilerplate/app.config.ts
- 静态配置与内置插件:boilerplate/app.json
【免费下载链接】igniteInfinite Red's battle-tested React Native project boilerplate, along with a CLI, component/model generators, and more! 9 years of continuous development and counting.项目地址: https://gitcode.com/GitHub_Trending/ig/ignite
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考