使用危险模块
了解危险模块以及如何在创建配置插件时使用它们。
Expo 中的危险模块可通过字符串操作和正则表达式直接访问原生项目文件。虽然 现有模组插件 是推荐的方法,但危险的 mods 可以充当现有 mod 插件无法实现的修改的逃生舱。
¥Dangerous mods in Expo provide direct access to native project files through string manipulation and regular expressions. While existing mod plugins are the recommended approach, dangerous mods serve as an escape hatch for modifications that cannot be achieved through existing mod plugins.
Why are they considered dangerous?
Automated direct source code manipulation does not typically compose well. For example, if a dangerous mod replaces text in a source file, and a subsequent dangerous mod expects the original text to be there (perhaps it uses the original text as an anchor for a regular expression) then it is unlikely produce the desired result — depending on how it is written, it may either throw an error or log. Other types of mods are less prone to this type of problem, although it can happen with mods that manipulate source files directly like withAndroidManifest
and withPodfile
.
Unlike standard mods, which can run multiple times safely, dangerous mods are rarely guaranteed to be idempotent. Running the same dangerous mod multiple times may produce different results, cause duplicate modifications, or break the target file entirely.
何时使用危险模块
¥When to use a dangerous mod
在以下情况下,请考虑使用危险模块:
¥Consider using a dangerous mod when:
-
无法使用标准 mod 进行修改:你需要的修改不受现有修改插件(如
withAndroidManifest
、withPodfile
等)支持,或者库需要标准插件未涵盖的特定原生修改。¥Can't make the modification with a standard mod: The modification you need isn't supported by existing mod plugins like
withAndroidManifest
,withPodfile
, and so on, or if a library requires specific native modifications that aren't covered by standard plugins. -
与旧版 Expo SDK 的兼容性:你使用的是旧版 Expo SDK,其中不包含所需的 mod 插件。
¥Legacy Expo SDK compatibility: You are targeting an older Expo SDK version that doesn't include the mod plugin you need.
-
需要使用正则表达式或替换函数修改文本:你需要执行现有 mod 插件不支持的复杂文本操作。Expo 在内部使用危险的模块进行大型文件系统重构,例如,当库的名称更改时。
¥Need to modify text with regexes or replace functions: You need to perform intricate text manipulations that existing mod plugins do not support. Expo uses dangerous mods internally for large file system refactoring, for example, when a library's name changes.
如何使用危险模块
¥How to use a dangerous mod
在实际场景中,你可以按照 创建配置插件部分 中的标准配置插件使用模式,直接在项目中使用本节中描述的示例配置插件。但是,有了现有的名为 withPodfile
的 mod 插件,你无需使用这个危险的 mod。以下示例仅用于演示如何创建和使用危险的模态框。
¥In a real-world scenario, you can use the example config plugin described in this section directly in your project by following the standard config plugin usage pattern from the Creating a config plugin section. However, with the existing mod plugin called withPodfile
, you don't have to use the dangerous mod. The example below is just for demonstration of how a dangerous mod can be created and used.
让我们看一个配置插件示例,该插件用于修改原生目录(ios)中的文件。当你在 Expo 项目中使用持续原生生成时,这非常有用。借助此配置插件,原生文件 (ios/Podfile) 将在 npx expo prebuild
命令运行时随时更新,无论你是手动运行还是使用 EAS Build。当现有 mod 插件无法编辑和更新原生目录中的文件时,此示例是一个理想的用例。
¥Let's take a look at an example config plugin to modify a file inside a native directory (ios). This is useful when you are using Continuous Native Generation in your Expo project. With the help of this config plugin, the native file (ios/Podfile) will update anytime the npx expo prebuild
command runs, whether you run it manually or using EAS Build). This example is an ideal use case when an existing mod plugin cannot edit and update a file inside a native directory.
按照 创建配置插件部分 中创建配置插件的目录结构和步骤(步骤 3、4 和 5),假设此配置插件是在 Expo 项目的 plugins 目录中创建的:
¥Following the directory structure and steps to create a config plugin (steps 3, 4, and 5) from Creating a config plugin section, let's assume this config plugin is created inside the plugins directory of your Expo project:
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 插件会在预构建过程中自动将 CocoaPod 依赖添加到项目的原生 ios/Podfile 中。它使用 withDangerousMod
直接提供对原生文件系统的访问,并在原生项目生成后、安装任何 CocoaPod 依赖之前运行。
¥In the example above, the plugin withCustomPodfile will add a CocoaPod dependency automatically to your project's native ios/Podfile during the prebuild process. It uses withDangerousMod
to provide access to the native file system directly and run after the native project is generated, but before any CocoaPod dependency is installed.
Podfile 需要直接文本操作,这可以通过在 addCustomMod
函数中使用正则表达式模式来实现。此过程还需要将 CocoaPod 依赖插入到 Podfile 中的特定位置,该位置位于 use_expo_modules!
语句之后。
¥The Podfile requires direct text manipulation, which is done using a regex pattern inside addCustomMod
function. This process also requires that the CocoaPod dependency is inserted into the Podfile at a specific location, which is after the use_expo_modules!
statement.
withDangerousMod
语法和要求
¥withDangerousMod
syntax and requirements
使用 withDangerousMod
需要特定参数:
¥Using withDangerousMod
requires certain parameters:
-
原生平台(Android 或 iOS)
¥A native platform (android or ios)
-
一个异步函数,接收具有文件系统访问权限的
config
对象¥An asynchronous function that receives
config
object with file system access -
在原生目录中访问的相对文件名/路径
¥Relative file name/path to access inside the native directory
-
读取现有文件,修改其内容,并写回文件
¥Reading the existing file, modifying its contents, and writing back to the file
-
(可选)在预构建过程中执行插件时,记录成功和失败状态的自定义消息。
¥(Optional) Log custom messages for success and failure state when a plugin executes during the prebuild process
以下代码片段提供了必填字段的框架,以及使用 withDangerousMod
时配置插件的结构:
¥The code snippet below provides a skeleton of the required field and how the config plugin can be structured when using 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. Async modification function // 3. Build file paths const filePath = path.join( config.modRequest.platformProjectRoot, // Native project root 'path/to/file' // Relative path to target file ); try { // 4. Read existing file, modify its contents, and write back to the file let contents = await fs.readFile(filePath, 'utf8'); contents = modifyContents(contents); await fs.writeFile(filePath, contents); // 5. Log success and failure states console.log('✅ Successfully modified file'); } catch (error) { console.warn('⚠️ File modification failed:', error); } return config; }, ]); }; // Helper functions to use regex to modify the contents of the file
配置插件中的可用路径
¥Available paths in config plugins
配置插件中可用的不同路径属性:
¥Different path properties available in config plugins:
路径 | 类型 | 描述 |
---|---|---|
config.modRequest.projectRoot | string | package.json 所在的通用应用项目根目录。用于解析资源、读取 package.json 和跨平台操作。请务必验证目录是否存在且包含 package.json 文件。 |
config.modRequest.platformProjectRoot | string | 特定于平台的项目根目录(projectRoot/android 或 projectRoot/ios)。用于平台特定的文件操作,例如修改原生配置文件。确保平台目录相对于主 projectRoot 存在。 |
config.modRequest.projectName | string | [仅限 iOS] 用于构建 iOS 文件路径的项目名称组件(例如,projectRoot/ios/[projectName]/)。用于 iOS 特定的文件路径构建。仅适用于 iOS 平台,且应与实际的 Xcode 项目结构匹配。 |
config.modRequest.introspect | boolean | 是否在内省模式下运行,该模式不应进行任何文件系统更改。使用 true 时,模块应该只读取和分析文件,而不能写入。用于配置分析和验证。 |
config.modRequest.ignoreExistingNativeFiles | boolean | 是否忽略现有的原生文件。用于基于模板的操作,尤其会影响授权和其他原生配置,以确保与预构建预期保持一致。 |
使用危险模块时的注意事项
¥Considerations when using a dangerous mod
使用危险模块时,请考虑以下事项:
¥When using a dangerous mod, consider the following:
-
有限的幂等性保证。标准模组通常是幂等的,无需使用 clean 标志即可运行,而危险模组很少能保证幂等。这意味着多次运行相同的危险模块可能会产生不同的结果或导致问题。
¥Limited idempotency guarantees. Unlike standard mods, which are generally idempotent and can work without the clean flag, dangerous mods are rarely guaranteed to be idempotent. This means running the same dangerous mod multiple times may produce different results or cause issues.
-
此功能尚处于实验阶段,容易出现问题。请谨慎使用
withDangerousMod
,因为它将来可能会发生变化。在每次 SDK 发布时,请彻底测试你的危险模块,因为当原生模板发生更改时,它们特别容易损坏。¥Experimental and prone to breakage. Be careful using
withDangerousMod
as it is subject to change in the future. Test your dangerous mods thoroughly with each SDK release, as they are especially prone to breakage when native template changes occur. -
使用标准 mod 插件。Android 和 iOS 都提供
withAndroidManifest
、withPodfile
、withPodfileProperties
等 mod 插件,用于执行常见的原生文件修改。仅在没有 可用的现有模组插件 来处理你的用例时,才使用危险模块。¥Use standard mod plugins. Both Android and iOS offer mod plugins like
withAndroidManifest
,withPodfile
,withPodfileProperties
, and so on, to perform common native file modifications. Only use a dangerous mod when there are no existing mod plugins available to handle your use case. -
不要假设文件存在。在读取/写入文件之前,请务必检查原生目录及其相对路径。如果你使用 CNG,你可以随时运行
npx expo prebuild
来创建原生 android 和 ios 目录并手动验证文件的存在。¥Don't assume a file exists. Always check the native directory and the relative path to the file before reading/writing to it. If you use CNG, you can always run
npx expo prebuild
to create native android and ios directories and manually verify a file's existence. -
危险模组优先运行。危险修改的执行顺序可能不可靠,因为危险修改会先于其他修改器运行。这可能会影响构建过程的可预测性,并可能与其他修改发生冲突。
¥Dangerous mods run first. The order in which dangerous mods execute might be unreliable since dangerous mods run before other modifiers. This can affect the predictability of your build process and may cause conflicts with other modifications.