expo插件修复插件冲突并且编译自动设置key

浏览: 26评论: 0
发布时间: 2025-12-02

在app.json 同目录创建plugins 文件夹,创建文件withAndroidPatcher.js

const { withAppBuildGradle, withGradleProperties, createRunOncePlugin, withDangerousMod} = require('@expo/config-plugins');
const fs = require('fs');
const path = require('path');
 
// 1. 解决react-native-video和@cloudflare/realtimekit-react-native  冲突问题,修改 android/app/build.gradle 添加 exclude 规则
const withExcludedMedia3 = (config) => {
    return withAppBuildGradle(config, (config) => {
        const buildGradle = config.modResults.contents;
 
        // 防止重复添加
        if (buildGradle.includes("exclude group: 'androidx.media3'")) {
            return config;
        }
 
        const exclusionBlock = `
// Added by Expo Config Plugin
configurations.all {
    exclude group: 'androidx.media3', module: 'media3-exoplayer-dash'
    exclude group: 'androidx.media3', module: 'media3-exoplayer-hls'
    exclude group: 'androidx.media3', module: 'media3-exoplayer-smoothstreaming'
    exclude group: 'androidx.media3', module: 'media3-exoplayer-rtsp'
}
`;
 
        // 在 dependencies 块之前添加
        const dependenciesIndex = buildGradle.indexOf('dependencies {');
        if (dependenciesIndex !== -1) {
            config.modResults.contents =
                buildGradle.substring(0, dependenciesIndex) +
                exclusionBlock +
                buildGradle.substring(dependenciesIndex);
        } else {
            // 如果未找到 'dependencies {',则仍然添加到文件末尾作为备用
            console.warn("[ConfigPlugin] Could not find 'dependencies {' block. Appending exclusion block to end of file.");
            config.modResults.contents = buildGradle + exclusionBlock;
        }
        return config;
    });
};
 
// 2. 解决编译时候内存不够的情况,不一定必须,修改 gradle.properties 设置 JVM 参数
const withCustomJvmArgs = (config) => {
    return withGradleProperties(config, (config) => {
        const propertyName = 'org.gradle.jvmargs';
        const propertyValue = '-Xmx4096m -XX:MaxMetaspaceSize=1024m';
 
        // 查找是否已存在该属性
        const existingProp = config.modResults.find(item => item.key === propertyName);
 
        if (existingProp) {
            existingProp.value = propertyValue;
        } else {
            config.modResults.push({
                type: 'property',
                key: propertyName,
                value: propertyValue,
            });
        }
 
        return config;
    });
};
 
// 3. 修复 node包 @cloudflare/realtimekit-react-native 里面缺资源问题,手动创建
 
const withNodeModulesPatch = (config) => {
    // 这里的路径是相对于项目根目录的
    const targetDir = path.resolve(
        // eslint-disable-next-line no-undef
        __dirname,
        '../node_modules/@cloudflare/realtimekit-react-native/android/src/main/res/values'
    );
 
    try {
        if (!fs.existsSync(targetDir)) {
            console.log(`[ConfigPlugin] Creating missing directory: ${targetDir}`);
            fs.mkdirSync(targetDir, { recursive: true });
        }
 
        // 定义 strings.xml 的路径和内容
        const stringsXmlPath = path.join(targetDir, 'strings.xml');
        const stringsXmlContent = `<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="blob_provider_authority">com.cloudflare.realtimekit.blob</string>
</resources>
`;
        // 如果 strings.xml 文件不存在,则创建它
        if (!fs.existsSync(stringsXmlPath)) {
            fs.writeFileSync(stringsXmlPath, stringsXmlContent);
            console.log(`[ConfigPlugin] Created strings.xml in ${targetDir}`);
        }
    } catch (error) {
        console.error(`[ConfigPlugin] Failed to create directory or file: ${error.message}`);
    }
 
    return config;
};
 
// 4. 修改 android/gradle.properties 添加密钥信息
const withKeystoreProperties = (config) => {
    return withGradleProperties(config, (config) => {
        const propsToAdd = {
            'MYAPP_RELEASE_STORE_FILE': 'innoKey.keystore',
            'MYAPP_RELEASE_KEY_ALIAS': 'inno',
            'MYAPP_RELEASE_STORE_PASSWORD': '123456',
            'MYAPP_RELEASE_KEY_PASSWORD': '123456',
        };
 
        // 遍历我们要添加的属性
        Object.entries(propsToAdd).forEach(([key, value]) => {
            const existingProp = config.modResults.find((item) => item.key === key);
            if (existingProp) {
                // 如果存在,更新它
                existingProp.value = value;
            } else {
                // 如果不存在,添加它
                config.modResults.push({
                    type: 'property',
                    key: key,
                    value: value,
                });
            }
        });
 
        return config;
    });
};
 
// 5. 修改 android/app/build.gradle
const withAppBuildGradleModification = (config) => {
    return withAppBuildGradle(config, (config) => {
        let buildGradle = config.modResults.contents;
        // --- 步骤 A: 添加 signingConfigs.release ---
        // 我们寻找 "signingConfigs {" 这个代码块
        const signingConfigsAnchor = 'signingConfigs {';
        // 定义我们要插入的代码块
        const releaseSigningConfig = `
        release {
            if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
                storeFile file(MYAPP_RELEASE_STORE_FILE)
                storePassword MYAPP_RELEASE_STORE_PASSWORD
                keyAlias MYAPP_RELEASE_KEY_ALIAS
                keyPassword MYAPP_RELEASE_KEY_PASSWORD
            }
        }
    `;
        // 只有当文件中还没有这段代码时才添加,防止重复
        if (buildGradle.includes(signingConfigsAnchor) && !buildGradle.includes("MYAPP_RELEASE_STORE_FILE")) {
            // 将 release 配置插在 "signingConfigs {" 后面
            buildGradle = buildGradle.replace(
                signingConfigsAnchor,
                signingConfigsAnchor + releaseSigningConfig
            );
        }
 
        // --- 步骤 B: 修改 buildTypes.release 使用这个签名 ---
        // 很多默认模板里,release 下面写的是 signingConfig signingConfigs.debug
        // 我们尝试将其替换为 release。
 
        // 正则解释:查找 buildTypes 代码块内的 release 代码块,并替换里面的 signingConfig
        // 这是一个简单的文本替换策略,适用于大多数标准 Expo/RN 项目结构
        if (buildGradle.includes('signingConfig signingConfigs.debug')) {
            // 注意:这里可能会误伤 debug 块,但通常 debug 块不需要显式写这句话(它是默认的)。
            // 只有 release 块为了方便调试通常会被显式设置为 debug。
            // 为了安全起见,我们做一个带上下文的更安全的替换。
            const releaseBlockRegex = /(buildTypes\s*\{[\s\S]*?release\s*\{[\s\S]*?)(signingConfig\s+signingConfigs\.debug)([\s\S]*?\})/s;
            if (releaseBlockRegex.test(buildGradle)) {
                buildGradle = buildGradle.replace(releaseBlockRegex, '$1signingConfig signingConfigs.release$3');
            } else {
                // 如果找不到 signingConfig signingConfigs.debug,尝试直接把 signingConfigs.debug 替换掉
                // 这是备选方案
                buildGradle = buildGradle.replace('signingConfig signingConfigs.debug', 'signingConfig signingConfigs.release');
            }
        } else {
            // 如果原本没有配置签名,我们需要在 release { 里面加一句
            // 这种情况比较少见,通常 Expo 预构建会有默认值。
            // 如果你需要处理这种情况,可以再写正则匹配 release { } 并在末尾插入。
        }
 
        config.modResults.contents = buildGradle;
        return config;
    });
};
// 6. 复制 innoKey.keystore 到 android/app 目录
const withKeystoreCopy = (config) => {
    return withDangerousMod(config, [
        'android',
        async (config) => {
            const projectRoot = config.modRequest.projectRoot;
            // Expo 的 platformProjectRoot 在 android 编译时指向的是 'android' 文件夹
            const platformRoot = config.modRequest.platformProjectRoot;
 
            // 源文件:假设 innoKey.keystore 就在你的项目根目录下
            const sourcePath = path.join(projectRoot, 'innoKey.keystore');
 
            // 目标文件:android/app/innoKey.keystore
            const destPath = path.join(platformRoot, 'app', 'innoKey.keystore');
 
            if (fs.existsSync(sourcePath)) {
                try {
                    fs.copyFileSync(sourcePath, destPath);
                    console.log(`[ConfigPlugin] ✅ 已成功将 keystore 复制到: ${destPath}`);
                } catch (e) {
                    console.error(`[ConfigPlugin] ❌ 复制 keystore 失败: ${e.message}`);
                }
            } else {
                console.warn(`[ConfigPlugin] ⚠️ 警告: 在根目录未找到 ${sourcePath}。请确保文件存在,否则打包可能会失败。`);
            }
 
            return config;
        },
    ]);
};
// 主函数:组合所有功能
const withAndroidPatcher = (config) => {
    console.log('[ConfigPlugin] 自定义插件运行withExcludedMedia3');
    config = withExcludedMedia3(config);
    console.log('[ConfigPlugin] 自定义插件运行withCustomJvmArgs');
    config = withCustomJvmArgs(config);
    console.log('[ConfigPlugin] 自定义插件运行withNodeModulesPatch');
    config = withNodeModulesPatch(config);
    console.log('[ConfigPlugin] 自定义插件运行withKeystoreProperties');
    config = withKeystoreProperties(config);
    console.log('[ConfigPlugin] 自定义插件运行withAppBuildGradleModification');
    config = withAppBuildGradleModification(config);
    console.log('[ConfigPlugin] 自定义插件运行withKeystoreCopy');
    config = withKeystoreCopy(config);
    return config;
};
 
module.exports = createRunOncePlugin(withAndroidPatcher, 'withAndroidPatcher', '1.0.0');
 

修改app.json 下 plugins属性,

增加个属性./plugins/withAndroidPatcher 比如:

{
    "expo": {
        "plugins": [
            "./plugins/withAndroidPatcher"
        ]
    }
}