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"
]
}
}