使用危险的 mod

编辑

了解危险的 mods 及其在创建配置插件时的使用方法。


Expo 中的危险 mods 通过字符串操作和正则表达式提供对原生项目文件的直接访问。虽然 现有的 mod 插件 是推荐的方法,但危险的 mods 作为无法通过现有 mod 插件实现的修改的逃生开关。

为什么它们被认为是危险的?

自动的直接源代码操作通常效果不佳。例如,如果一个危险 mod 替换了源文件中的文本,而后来的危险 mod 又期望原始文本存在(可能它将原始文本用作正则表达式的锚点),那么它很可能无法产生所需的结果——根据其编写方式,它可能出现错误或生成日志。其他类型的 mods 较少容易出现此类问题,虽然直接操作源文件的 mods,如 withAndroidManifestwithPodfile 也可能遇到这种情况。

与可以安全运行多次的标准 mods 不同,危险 mods 很少被保证是幂等的。多次运行同一危险 mod 可能会产生不同的结果、导致重复修改或完全破坏目标文件。

何时使用危险 mod

考虑在以下情况下使用危险 mod:

  • 无法使用标准 mod 进行修改:所需的修改不被现有的 mod 插件支持,如 withAndroidManifestwithPodfile 等,或者库需要特定的原生修改,而这些并不在标准插件的覆盖范围内。
  • 老旧 Expo SDK 兼容性:您正在针对一个不包含所需 mod 插件的旧版本 Expo SDK。
  • 需要用正则表达式或替换函数修改文本:您需要进行现有 mod 插件不支持的复杂文本操作。例如,Expo 在内部使用危险 mods 进行大型文件系统重构,例如库名更改时。

如何使用危险 mod

在实际场景中,您可以直接在项目中使用本节中描述的示例配置插件,遵循 创建配置插件部分 的标准配置插件使用模式。然而,对于名为 withPodfile 的现有 mod 插件,您无需使用危险 mod。下面的示例只是为了演示如何创建和使用危险 mod。

让我们看看一个示例配置插件,以修改原生目录中的一个文件 (ios)。当您在 Expo 项目中使用持续的原生生成时,这非常有用。在该配置插件的帮助下,原生文件 (ios/Podfile) 将在每次运行 npx expo prebuild 命令时更新,无论您是手动运行还是使用 EAS Build)。当现有 mod 插件无法编辑和更新原生目录中的文件时,此示例是一个理想的用例。

遵循 创建配置插件部分 中创建配置插件的目录结构和步骤(步骤 3、4 和 5),假设该配置插件是在您 Expo 项目的 plugins 目录中创建的:

withCustomPodfile.ts
import { ConfigPlugin, IOSConfig, withDangerousMod } from 'expo/config-plugins'; import fs from 'fs/promises'; import path from 'path'; const withCustomPodfile: ConfigPlugin = config => { return withDangerousMod(config, [ 'ios', async config => { const podfilePath = path.join(config.modRequest.platformProjectRoot, 'Podfile'); try { let contents = await fs.readFile(podfilePath, 'utf8'); const projectName = IOSConfig.XcodeUtils.getProjectName(config.modRequest.projectRoot); contents = addCustomPod(contents, projectName); await fs.writeFile(podfilePath, contents); console.log('✅ Successfully added custom pod to Podfile'); } catch (error) { console.warn('⚠️ Podfile not found, skipping modification'); } return config; }, ]); }; function addCustomPod(contents: string, projectName: string): string { if (contents.includes("pod 'Alamofire'")) { console.log('Alamofire pod already exists, skipping'); return contents; } const targetRegex = new RegExp( `(target ['"]${projectName}['"] do[\\s\\S]*?use_expo_modules!)`, 'm' ); return contents.replace(targetRegex, `$1\n pod 'Alamofire', '~> 5.6'`); } export default withCustomPodfile;

在上面的示例中,插件 withCustomPodfile 将在预构建过程中自动向您的项目的原生 ios/Podfile 添加 CocoaPod 依赖项。它使用 withDangerousMod 直接访问原生文件系统,并在生成原生项目之后但在安装任何 CocoaPod 依赖项之前运行。

Podfile 需要直接文本操作,这通过 addCustomMod 函数中的正则表达式模式进行。该过程还需要在 use_expo_modules! 语句之后,在 Podfile 的特定位置插入 CocoaPod 依赖项。

withDangerousMod 语法和要求

使用 withDangerousMod 需要某些参数:

  1. 一个原生平台(androidios
  2. 一个异步函数,它接收 config 对象,并具有文件系统访问权限
  3. 相对文件名/路径,以便在原生目录中访问
  4. 读取现有文件、修改其内容并写回文件
  5. (可选)在插件在预构建过程中执行时记录成功和失败状态的自定义消息

下面的代码片段提供了所需字段的框架以及在使用 withDangerousMod 时配置插件的结构:

import { ConfigPlugin, withDangerousMod } from 'expo/config-plugins'; import fs from 'fs/promises'; import path from 'path'; const myPlugin: ConfigPlugin = config => { return withDangerousMod(config, [ 'platform', // 1. "ios" | "android" async config => { // 2. 异步修改函数 // 3. 构建文件路径 const filePath = path.join( config.modRequest.platformProjectRoot, // 原生项目根目录 'path/to/file' // 相对路径到目标文件 ); try { // 4. 读取现有文件,修改其内容,并写回文件 let contents = await fs.readFile(filePath, 'utf8'); contents = modifyContents(contents); await fs.writeFile(filePath, contents); // 5. 记录成功和失败状态 console.log('✅ Successfully modified file'); } catch (error) { console.warn('⚠️ File modification failed:', error); } return config; }, ]); }; // 使用正则表达式修改文件内容的帮助函数

配置插件中的可用路径

配置插件中可用的不同路径属性:

路径类型描述
config.modRequest.projectRootstring通用应用项目根目录,package.json 所在位置。用于解析资产,读取 package.json 和跨平台操作。始终确认该目录存在并包含 package.json
config.modRequest.platformProjectRootstring平台特定项目根目录(projectRoot/androidprojectRoot/ios)。用于平台特定的文件操作,例如修改原生配置文件。确保平台目录相对于主 projectRoot 存在。
config.modRequest.projectNamestring[仅限 iOS] 构造 iOS 文件路径的项目名称组件(例如,projectRoot/ios/[projectName]/)。用于构建特定于 iOS 的文件路径。仅在 iOS 平台可用,应与实际 Xcode 项目结构匹配。
config.modRequest.introspectboolean是否在仅读取和分析文件而不进行文件系统更改的内省模式下运行。当为 true 时,mods 应仅读取和分析文件而不进行写操作。用于配置分析和验证。
config.modRequest.ignoreExistingNativeFilesboolean是否忽略现有的原生文件。用于基于模板的操作,特别影响授权和其他原生配置,以确保与预构建期望的一致性。

使用危险 mod 时的注意事项

使用危险 mod 时,请考虑以下几点:

  • 有限的幂等性保证。 与通常具有幂等性的标准 mods 不同,危险 mods 很少被保证是幂等的。这意味着多次运行同一危险 mod 可能会产生不同的结果或导致问题。
  • 实验性和易破坏。 使用 withDangerousMod 时要小心,因为它可能在将来发生变化。请在每个 SDK 发布版本中彻底测试您的危险 mods,因为它们在原生模板更改发生时尤其容易出现问题。
  • 使用标准 mod 插件。 Android 和 iOS 都提供像 withAndroidManifestwithPodfilewithPodfileProperties 等 mod 插件来执行常见的原生文件修改。仅在没有 现有 mod 插件可用 处理您的用例时使用危险 mod。
  • 不要假设文件存在。在读取/写入之前始终检查原生目录及文件的相对路径。如果您使用 CNG,可以始终运行 npx expo prebuild 来创建原生 androidios 目录,并手动验证文件的存在。
  • 危险 mods 首先运行。 危险 mods 的执行顺序可能不可靠,因为危险 mods 在其他修饰符之前运行。这可能会影响您的构建过程的可预测性,并导致与其他修改的冲突。