Electron 项目中使用 electron-vite(Vite的Electron 构建工具)时的配置文件,告诉 Vite哪些依赖(包)在打包时不用一起打包,而是运行时让Electron去外部加载;
什么场景会用,比方说你依赖一个声网做音视频能力,然后你业务层本身没啥更新,只是这个SDK有Bug需要修复,这个时候你差量更新声网这个依赖就行了;
为什么有些模块需要被外部化? 举例:dingrtc-electron-sdk;这类第三方 SDK 通常包含:Node.js 原生扩展(C++ 代码编译的二进制模块),无法被 Vite 打包处理
假设要配置依赖dingrtc-electron-sdk去外部加载
typescript
const commonjsPackages = [
'electron',
'electron/main',
'dingrtc-electron-sdk',
] as const;
export default defineConfig({
commonjsExternals(
{ externals: commonjsPackages },
),
});
编译后路径
typescript
// win电脑右键图标=>属性=>打开文件所在的位置;
// mac公司没有mac有的兄弟可以评论区告诉我怎么弄;
resources/app.asar.unpacked/node_modules/dingrtc-electron-sdk
需要外部依赖 adm-zip
typescript
npm i adm-zip -S
实现流程
- 1.获取本地版本号 resources/app.asar.unpacked/node_modules/dingrtc-electron-sdk下package.json里面的版本号{"version": "0.0.1"}
- 2.获取文件服务器最新版本号;
- 3.对比版本号,不一致的时候就去下载zip,并覆盖本地目录; (第三方依赖有时候新版本有问题,得回滚,监测到只要版本号不一致就更新)
使用方法
1.指定线上最新的版本号文件;(可以改为自己的文件地址) const VERSION_URL = 'test.cn/electronDin...';

2.根据版本号获取到zip地址;(可以改为自己的文件地址) const getZipUrl = (version) =>
https://test.cn/electronDingrtcUpdate/dingrtc-electron-sdk-${version}.zip
;

3.在主进程main.js调用

核心代码
typescript
const { app } = require('electron');
const fs = require('fs/promises');
const fsSync = require('fs');
const path = require('path');
const https = require('https');
const AdmZip = require('adm-zip'); // 需要安装: npm install adm-zip
// 1. 本地 SDK 目录(asar.unpacked 内)
const localDir = path.join(
process.resourcesPath,
'app.asar.unpacked',
'node_modules',
'dingrtc-electron-sdk'
);
// 2. 线上版本接口
const VERSION_URL = 'https://test.cn/electronDingrtcUpdate/package.json';
// 3. 线上 zip 包地址
const getZipUrl = (version) =>
`https://test.cn/electronDingrtcUpdate/dingrtc-electron-sdk-${version}.zip`;
/**
* 读取本地 SDK 版本号
*/
async function getLocalVersion() {
try {
const packageJsonPath = path.join(localDir, 'package.json');
// 检查文件是否存在
await fs.access(packageJsonPath);
// 读取版本号
const content = await fs.readFile(packageJsonPath, 'utf8');
const packageInfo = JSON.parse(content);
return packageInfo.version || '0.0.0';
} catch (error) {
console.warn('获取本地版本失败,可能是首次安装', error.message);
return '0.0.0'; // 默认为初始版本
}
}
/**
* 获取线上版本号
*/
async function getRemoteVersion() {
return new Promise((resolve, reject) => {
https.get(VERSION_URL, (response) => {
let data = '';
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
try {
const remoteInfo = JSON.parse(data);
if (remoteInfo.version) {
resolve(remoteInfo.version);
} else {
reject(new Error('线上版本信息格式不正确'));
}
} catch (error) {
reject(new Error(`解析线上版本失败: ${error.message}`));
}
});
}).on('error', (error) => {
reject(new Error(`获取线上版本失败: ${error.message}`));
});
});
}
/**
* 比较版本号 (简单比较,适用于 x.y.z 格式)
* @returns true 如果 remoteVersion 大于 localVersion 则需要更新
*/
function shouldUpdate(localVersion, remoteVersion) {
const localParts = localVersion.split('.').map(Number);
const remoteParts = remoteVersion.split('.').map(Number);
for (let i = 0; i < Math.max(localParts.length, remoteParts.length); i++) {
const local = localParts[i] || 0;
const remote = remoteParts[i] || 0;
if (remote > local) return true;
if (remote < local) return false;
}
return false; // 版本相同
}
/**
* 下载文件到临时路径
*/
async function downloadFile(url, tempFilePath) {
return new Promise((resolve, reject) => {
const file = fsSync.createWriteStream(tempFilePath);
https.get(url, (response) => {
if (response.statusCode !== 200) {
file.destroy();
return reject(new Error(`下载失败,状态码: ${response.statusCode}`));
}
response.pipe(file);
file.on('finish', () => {
file.close(() => {
resolve(tempFilePath);
});
});
}).on('error', (error) => {
fs.unlink(tempFilePath).catch(() => {});
reject(new Error(`下载过程出错: ${error.message}`));
});
});
}
/**
* 备份原目录
*/
async function backupOriginalDir() {
const backupDir = `${localDir}_backup_${Date.now()}`;
try {
// 如果目录存在则备份
if (fsSync.existsSync(localDir)) {
await fs.rename(localDir, backupDir);
console.log(`已备份原目录到: ${backupDir}`);
}
return backupDir;
} catch (error) {
console.warn(`备份目录失败: ${error.message}`);
return null;
}
}
/**
* 解压并覆盖文件
*/
async function extractAndReplace(zipPath, targetDir) {
try {
// 创建目标目录(如果不存在)
await fs.mkdir(targetDir, { recursive: true });
// 解压 zip
const zip = new AdmZip(zipPath);
zip.extractAllTo(targetDir, true); // true 表示覆盖现有文件
console.log(`文件已解压到: ${targetDir}`);
return true;
} catch (error) {
throw new Error(`解压文件失败: ${error.message}`);
}
}
/**
* 主函数:检查并更新 DingRTC SDK
* @returns {Object} 更新结果
*/
export async function checkAndUpdateDingRtc() {
let tempZipPath = null;
let backupDir = null;
try {
console.log('开始检查 DingRTC SDK 更新...');
// 1. 获取版本信息
const [localVersion, remoteVersion] = await Promise.all([
getLocalVersion(),
getRemoteVersion()
]);
console.log(`本地版本: ${localVersion}, 线上版本: ${remoteVersion}`);
// 2. 比较版本
// if (!shouldUpdate(localVersion, remoteVersion)) {
// return {
// updated: false,
// message: '已是最新版本',
// localVersion,
// remoteVersion
// };
// }
if (localVersion === remoteVersion) {
console.log('版本相同,无需更新');
return {
updated: false,
message: '已是最新版本',
localVersion,
remoteVersion
};
}
// 3. 准备临时文件
const tempDir = path.join(app.getPath('temp'), `dingrtc_update_${Date.now()}`);
await fs.mkdir(tempDir, { recursive: true });
tempZipPath = path.join(tempDir, `dingrtc-sdk-${remoteVersion}.zip`);
// 4. 下载更新包
console.log(`开始下载更新包: ${getZipUrl(remoteVersion)}`);
await downloadFile(getZipUrl(remoteVersion), tempZipPath);
// 5. 备份原目录
backupDir = await backupOriginalDir();
// 6. 解压并替换
console.log('开始更新 SDK...');
await extractAndReplace(tempZipPath, localDir);
// 7. 清理临时文件
await fs.rm(tempDir, { recursive: true, force: true });
// 8. 验证更新结果
const newLocalVersion = await getLocalVersion();
if (newLocalVersion !== remoteVersion) {
throw new Error(`更新验证失败,实际版本: ${newLocalVersion}, 期望版本: ${remoteVersion}`);
}
console.log('DingRTC SDK 更新成功');
// 升级成功删除备份
await fs.remove(backupDir);
return {
updated: true,
message: '更新成功',
localVersion,
remoteVersion,
newLocalVersion
};
} catch (error) {
console.error('更新失败:', error.message);
// 回滚操作:如果有备份,尝试恢复
if (backupDir && fsSync.existsSync(backupDir)) {
try {
// 先删除可能损坏的目录
if (fsSync.existsSync(localDir)) {
await fs.rm(localDir, { recursive: true, force: true });
}
// 恢复备份
await fs.rename(backupDir, localDir);
console.log('已回滚到更新前的版本');
} catch (rollbackError) {
console.error('回滚失败:', rollbackError.message);
}
}
return {
updated: false,
message: `更新失败: ${error.message}`,
error: error.message
};
} finally {
// 确保临时文件被清理
if (tempZipPath && fsSync.existsSync(tempZipPath)) {
try {
await fs.unlink(tempZipPath);
} catch (cleanupError) {
console.warn('清理临时文件失败:', cleanupError.message);
}
}
}
}
主进程main.js使用
typescript
const { app } = require("electron");
import { checkAndUpdateDingRtc } from './update-dingrtc.js';
app.whenReady().then(async () => {
await checkAndUpdateDingRtc();
});