@@ -0,0 +1,670 @@
|
||||
// build-scripts/build.js
|
||||
const { minify } = require('terser');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
// [修复] 设置控制台输出编码为 UTF-8,解决乱码问题
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
// Windows 下设置控制台编码
|
||||
process.stdout.setDefaultEncoding('utf8');
|
||||
process.stderr.setDefaultEncoding('utf8');
|
||||
// 尝试设置控制台代码页为 UTF-8
|
||||
const { execSync } = require('child_process');
|
||||
try {
|
||||
execSync('chcp 65001 >nul 2>&1', { encoding: 'utf8' });
|
||||
} catch (e) {
|
||||
// 忽略编码设置错误
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略编码设置错误
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================================
|
||||
// --- 🚀 核心配置区 ---
|
||||
// ====================================================================================
|
||||
|
||||
const rootDir = path.join(__dirname, '..');
|
||||
const outputDir = path.join(rootDir, 'app_dist');
|
||||
|
||||
// ====================================================================================
|
||||
// --- 🔍 自动化识别需要保留的内容 ---
|
||||
// ====================================================================================
|
||||
|
||||
/**
|
||||
* [自动化] 从 preload.js 和 main.js 中提取所有 IPC 通信频道名称
|
||||
* @returns {Promise<Set<string>>} - 返回所有频道名称的集合
|
||||
*/
|
||||
async function extractIpcChannels() {
|
||||
const channels = new Set();
|
||||
|
||||
try {
|
||||
// 从 preload.js 提取
|
||||
const preloadPath = path.join(rootDir, 'preload.js');
|
||||
if (await fs.pathExists(preloadPath)) {
|
||||
const preloadContent = await fs.readFile(preloadPath, 'utf8');
|
||||
// 匹配 ipcRenderer.invoke('channel'), ipcRenderer.send('channel'), ipcRenderer.on('channel')
|
||||
const preloadRegex = /ipcRenderer\.(?:invoke|send|on)\(['"]([^'"]+)['"]/g;
|
||||
let match;
|
||||
while ((match = preloadRegex.exec(preloadContent)) !== null) {
|
||||
channels.add(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// 从 main.js 提取
|
||||
const mainPath = path.join(rootDir, 'main.js');
|
||||
if (await fs.pathExists(mainPath)) {
|
||||
const mainContent = await fs.readFile(mainPath, 'utf8');
|
||||
// 匹配 ipcMain.handle('channel'), ipcMain.on('channel')
|
||||
const mainRegex = /ipcMain\.(?:handle|on)\(['"]([^'"]+)['"]/g;
|
||||
let match;
|
||||
while ((match = mainRegex.exec(mainContent)) !== null) {
|
||||
channels.add(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return channels;
|
||||
} catch (error) {
|
||||
try {
|
||||
process.stderr.write(Buffer.from(`❌ 提取 IPC 频道失败: ${error.message || error}\n`, 'utf8'));
|
||||
} catch (e) {
|
||||
console.error('❌ 提取 IPC 频道失败:', error);
|
||||
}
|
||||
return channels;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [自动化] 从 contextBridge.exposeInMainWorld 中提取所有暴露的 API 名称
|
||||
* @returns {Promise<Set<string>>} - 返回所有 API 名称的集合
|
||||
*/
|
||||
async function extractExposedApis() {
|
||||
const apis = new Set();
|
||||
|
||||
try {
|
||||
const preloadPath = path.join(rootDir, 'preload.js');
|
||||
if (await fs.pathExists(preloadPath)) {
|
||||
const content = await fs.readFile(preloadPath, 'utf8');
|
||||
// 匹配 contextBridge.exposeInMainWorld('electronAPI', { ... })
|
||||
const exposeRegex = /contextBridge\.exposeInMainWorld\(['"]([^'"]+)['"]/g;
|
||||
let match;
|
||||
while ((match = exposeRegex.exec(content)) !== null) {
|
||||
apis.add(match[1]);
|
||||
}
|
||||
|
||||
// 提取对象中的所有方法名
|
||||
const objectRegex = /contextBridge\.exposeInMainWorld\([^,]+,\s*\{([^}]+)\}/s;
|
||||
const objectMatch = content.match(objectRegex);
|
||||
if (objectMatch) {
|
||||
const objectContent = objectMatch[1];
|
||||
// 匹配方法名: methodName:
|
||||
const methodRegex = /([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g;
|
||||
let methodMatch;
|
||||
while ((methodMatch = methodRegex.exec(objectContent)) !== null) {
|
||||
apis.add(methodMatch[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return apis;
|
||||
} catch (error) {
|
||||
try {
|
||||
process.stderr.write(Buffer.from(`❌ 提取暴露 API 失败: ${error.message || error}\n`, 'utf8'));
|
||||
} catch (e) {
|
||||
console.error('❌ 提取暴露 API 失败:', error);
|
||||
}
|
||||
return apis;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [自动化] 从所有 JS 文件中提取 export 的类名、函数名和变量名
|
||||
* @returns {Promise<{names: Set<string>, strings: Set<string>}>} - 返回标识符和关键字符串
|
||||
*/
|
||||
async function extractExportedIdentifiers() {
|
||||
const names = new Set();
|
||||
const strings = new Set();
|
||||
|
||||
// 添加 ES6 模块关键字
|
||||
['export', 'default', 'import', 'from', 'as', 'module', 'exports'].forEach(s => strings.add(s));
|
||||
|
||||
try {
|
||||
const jsDir = path.join(rootDir, 'src', 'js');
|
||||
const files = await findJsFiles(jsDir);
|
||||
|
||||
for (const filePath of files) {
|
||||
try {
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
|
||||
// 提取 export default class ClassName
|
||||
const classExportRegex = /export\s+default\s+class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
||||
let match;
|
||||
while ((match = classExportRegex.exec(content)) !== null) {
|
||||
names.add(match[1]);
|
||||
}
|
||||
|
||||
// 提取 export default new ClassName()
|
||||
const newExportRegex = /export\s+default\s+new\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
||||
while ((match = newExportRegex.exec(content)) !== null) {
|
||||
names.add(match[1]);
|
||||
}
|
||||
|
||||
// 提取 export default functionName
|
||||
const funcExportRegex = /export\s+default\s+(?:function\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
||||
while ((match = funcExportRegex.exec(content)) !== null) {
|
||||
names.add(match[1]);
|
||||
}
|
||||
|
||||
// 提取 export { name1, name2 }
|
||||
const namedExportRegex = /export\s+\{([^}]+)\}/g;
|
||||
while ((match = namedExportRegex.exec(content)) !== null) {
|
||||
const exports = match[1].split(',').map(s => s.trim().split(/\s+as\s+/)[0].trim());
|
||||
exports.forEach(name => {
|
||||
if (name && /^[a-zA-Z_$]/.test(name)) {
|
||||
names.add(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 提取 window.xxx = 或 window['xxx'] =
|
||||
const windowAssignRegex = /window\.([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/g;
|
||||
while ((match = windowAssignRegex.exec(content)) !== null) {
|
||||
names.add(match[1]);
|
||||
strings.add(match[1]);
|
||||
}
|
||||
|
||||
// 提取 window['xxx'] =
|
||||
const windowBracketRegex = /window\[['"]([^'"]+)['"]\]\s*=/g;
|
||||
while ((match = windowBracketRegex.exec(content)) !== null) {
|
||||
names.add(match[1]);
|
||||
strings.add(match[1]);
|
||||
}
|
||||
} catch (err) {
|
||||
// 忽略单个文件的错误
|
||||
}
|
||||
}
|
||||
|
||||
return { names, strings };
|
||||
} catch (error) {
|
||||
try {
|
||||
process.stderr.write(Buffer.from(`❌ 提取导出标识符失败: ${error.message || error}\n`, 'utf8'));
|
||||
} catch (e) {
|
||||
console.error('❌ 提取导出标识符失败:', error);
|
||||
}
|
||||
return { names, strings };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [自动化] 从代码中提取所有类的方法名(通过分析类定义)
|
||||
* @returns {Promise<Set<string>>} - 返回所有方法名的集合
|
||||
*/
|
||||
async function extractClassMethods() {
|
||||
const methods = new Set();
|
||||
|
||||
try {
|
||||
const jsDir = path.join(rootDir, 'src', 'js');
|
||||
const files = await findJsFiles(jsDir);
|
||||
|
||||
for (const filePath of files) {
|
||||
try {
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
|
||||
// 提取类方法: 匹配 class 内部的方法定义
|
||||
// 1. 匹配 async methodName() { 或 methodName() {
|
||||
const classMethodRegex = /(?:async\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/g;
|
||||
let match;
|
||||
while ((match = classMethodRegex.exec(content)) !== null) {
|
||||
const methodName = match[1];
|
||||
// 排除常见的关键字和内置方法
|
||||
if (!['constructor', 'toString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'catch', 'then', 'finally'].includes(methodName)) {
|
||||
methods.add(methodName);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 也匹配对象方法: methodName: function() { 或 methodName: () => {
|
||||
const objectMethodRegex = /([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:\s*(?:function|\([^)]*\)\s*=>)/g;
|
||||
while ((match = objectMethodRegex.exec(content)) !== null) {
|
||||
const methodName = match[1];
|
||||
if (!['constructor', 'toString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf'].includes(methodName)) {
|
||||
methods.add(methodName);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 匹配 this.methodName 调用,提取方法名
|
||||
const thisMethodRegex = /this\.([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?:\(|\.bind)/g;
|
||||
while ((match = thisMethodRegex.exec(content)) !== null) {
|
||||
const methodName = match[1];
|
||||
if (!['constructor', 'toString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf'].includes(methodName)) {
|
||||
methods.add(methodName);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// 忽略单个文件的错误
|
||||
}
|
||||
}
|
||||
|
||||
return methods;
|
||||
} catch (error) {
|
||||
try {
|
||||
process.stderr.write(Buffer.from(`❌ 提取类方法失败: ${error.message || error}\n`, 'utf8'));
|
||||
} catch (e) {
|
||||
console.error('❌ 提取类方法失败:', error);
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [自动化] 从 main.js 中提取所有使用的 Node.js 内置模块方法名
|
||||
* @returns {Promise<Set<string>>} - 返回所有方法名的集合
|
||||
*/
|
||||
async function extractNodeBuiltinMethods() {
|
||||
const methods = new Set();
|
||||
|
||||
try {
|
||||
const mainPath = path.join(rootDir, 'main.js');
|
||||
if (await fs.pathExists(mainPath)) {
|
||||
const content = await fs.readFile(mainPath, 'utf8');
|
||||
|
||||
// 提取 path.xxx, fs.xxx, crypto.xxx, os.xxx 等方法调用
|
||||
const builtinModuleRegex = /(?:path|fs|crypto|os|https|http|stream|child_process|worker_threads|electron)\.([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?:\(|\[)/g;
|
||||
let match;
|
||||
while ((match = builtinModuleRegex.exec(content)) !== null) {
|
||||
methods.add(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加常用的 Node.js 内置模块方法(确保不被压缩)
|
||||
const commonNodeMethods = [
|
||||
// path 模块
|
||||
'join', 'resolve', 'dirname', 'basename', 'extname', 'parse', 'format', 'normalize', 'isAbsolute',
|
||||
// fs 模块
|
||||
'readFile', 'writeFile', 'readFileSync', 'writeFileSync', 'existsSync', 'mkdirSync', 'statSync', 'readdirSync',
|
||||
'createReadStream', 'createWriteStream', 'access', 'accessSync', 'unlink', 'unlinkSync', 'rmdir', 'rmdirSync',
|
||||
// crypto 模块
|
||||
'createHash', 'createHmac', 'randomBytes', 'createCipheriv', 'createDecipheriv',
|
||||
// os 模块
|
||||
'platform', 'arch', 'homedir', 'tmpdir', 'uptime', 'totalmem', 'freemem', 'cpus', 'networkInterfaces',
|
||||
// stream 模块
|
||||
'pipeline', 'Readable', 'Writable', 'Transform',
|
||||
// child_process 模块
|
||||
'exec', 'execSync', 'spawn', 'spawnSync',
|
||||
// https/http 模块
|
||||
'get', 'request', 'createServer',
|
||||
// worker_threads 模块
|
||||
'Worker', 'isMainThread', 'parentPort',
|
||||
// Electron 模块
|
||||
'app', 'BrowserWindow', 'ipcMain', 'ipcRenderer', 'dialog', 'shell', 'nativeTheme', 'screen', 'session',
|
||||
'getPath', 'isPackaged', 'getVersion', 'getLocale', 'quit', 'exit', 'relaunch',
|
||||
'on', 'handle', 'send', 'invoke', 'webContents', 'loadURL', 'loadFile', 'show', 'hide', 'minimize', 'maximize', 'close',
|
||||
'showMessageBox', 'showOpenDialog', 'showSaveDialog', 'openExternal', 'openPath',
|
||||
'shouldUseDarkColors', 'themeSource', 'getAllDisplays', 'getPrimaryDisplay',
|
||||
'defaultSession', 'fromPartition', 'webRequest', 'onHeadersReceived', 'setPreloads'
|
||||
];
|
||||
|
||||
commonNodeMethods.forEach(method => methods.add(method));
|
||||
|
||||
return methods;
|
||||
} catch (error) {
|
||||
try {
|
||||
process.stderr.write(Buffer.from(`❌ 提取 Node.js 内置方法失败: ${error.message || error}\n`, 'utf8'));
|
||||
} catch (e) {
|
||||
console.error('❌ 提取 Node.js 内置方法失败:', error);
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归查找所有 JS 文件
|
||||
*/
|
||||
async function findJsFiles(dir) {
|
||||
let results = [];
|
||||
try {
|
||||
const list = await fs.readdir(dir);
|
||||
for (const file of list) {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = await fs.stat(filePath);
|
||||
if (stat && stat.isDirectory()) {
|
||||
results = results.concat(await findJsFiles(filePath));
|
||||
} else if (filePath.endsWith('.js')) {
|
||||
results.push(filePath);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略错误
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// ====================================================================================
|
||||
// --- ⚙️ 构建脚本执行区 ---
|
||||
// ====================================================================================
|
||||
|
||||
const sourcesToProcess = [
|
||||
'main.js',
|
||||
'preload.js',
|
||||
'config',
|
||||
'src'
|
||||
];
|
||||
|
||||
/**
|
||||
* 判断是否为关键文件(需要特殊处理)
|
||||
*/
|
||||
function isCriticalFile(filePath) {
|
||||
const criticalPatterns = [
|
||||
/main\.js$/,
|
||||
/preload\.js$/,
|
||||
/configManager\.js$/,
|
||||
/mainPage\.js$/,
|
||||
/uiManager\.js$/,
|
||||
/toolStatusManager\.js$/,
|
||||
/toolHealthChecker\.js$/,
|
||||
/toolHealthCheckModal\.js$/
|
||||
];
|
||||
return criticalPatterns.some(pattern => pattern.test(filePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 Terser 压缩代码(轻量级,不会导致方法调用问题)
|
||||
* @param {string} code - 原始代码
|
||||
* @param {string} filePath - 文件路径
|
||||
* @param {Object} reservedData - 预提取的保留数据
|
||||
* @returns {Promise<string>} - 压缩后的代码
|
||||
*/
|
||||
async function minifyCode(code, filePath, reservedData) {
|
||||
try {
|
||||
const { reservedNames, reservedStrings } = reservedData;
|
||||
|
||||
const result = await minify(code, {
|
||||
compress: {
|
||||
drop_console: false, // 保留 console,方便调试
|
||||
drop_debugger: true,
|
||||
pure_funcs: [], // 不删除任何函数调用
|
||||
passes: 2 // 压缩次数
|
||||
},
|
||||
mangle: {
|
||||
reserved: Array.from(reservedNames), // 保留的标识符
|
||||
// [修复] 完全禁用属性压缩,避免类方法被压缩导致的问题
|
||||
properties: false // 禁用属性压缩,确保 this.methodName 不被压缩
|
||||
},
|
||||
format: {
|
||||
comments: false // 删除注释
|
||||
},
|
||||
keep_classnames: true, // 保留类名
|
||||
keep_fnames: true, // [修复] 保留函数名,确保类方法名不被压缩
|
||||
safari10: true // Safari 10 兼容性
|
||||
});
|
||||
|
||||
return result.code || code;
|
||||
} catch (error) {
|
||||
const errorMsg = `⚠️ 压缩 ${path.relative(outputDir, filePath)} 失败,使用原始代码: ${error.message}`;
|
||||
try {
|
||||
process.stderr.write(Buffer.from(errorMsg + '\n', 'utf8'));
|
||||
} catch (e) {
|
||||
console.error(errorMsg);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主构建函数
|
||||
*/
|
||||
async function run() {
|
||||
// [修复] 确保输出使用 UTF-8 编码
|
||||
const log = (message) => {
|
||||
try {
|
||||
process.stdout.write(Buffer.from(message + '\n', 'utf8'));
|
||||
} catch (e) {
|
||||
console.log(message);
|
||||
}
|
||||
};
|
||||
|
||||
log('🧹 [1/6] 清理旧的构建目录...');
|
||||
await fs.remove(outputDir);
|
||||
await fs.ensureDir(outputDir);
|
||||
|
||||
log('🔄 [2/6] 复制并清理文件用于发布...');
|
||||
const packageJsonPath = path.join(rootDir, 'package.json');
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
const prodPackageJson = {
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
productName: packageJson.productName,
|
||||
description: packageJson.description,
|
||||
main: packageJson.main,
|
||||
author: packageJson.author,
|
||||
dependencies: packageJson.dependencies
|
||||
};
|
||||
await fs.writeJson(path.join(outputDir, 'package.json'), prodPackageJson, { spaces: 2 });
|
||||
log('- 已清理并写入 package.json');
|
||||
|
||||
// 复制 lang 目录
|
||||
const langSrc = path.join(rootDir, 'lang');
|
||||
const langDest = path.join(outputDir, 'lang');
|
||||
if (await fs.pathExists(langSrc)) {
|
||||
await fs.copy(langSrc, langDest);
|
||||
log('- 已复制 lang 目录到 app_dist');
|
||||
} else {
|
||||
throw new Error(`❌ 找不到 lang 目录:${langSrc}`);
|
||||
}
|
||||
|
||||
// 复制 config-template.ini
|
||||
const configTemplatePath = path.join(rootDir, 'build-scripts', 'config-template.ini');
|
||||
const configTemplateDest = path.join(outputDir, 'config-template.ini');
|
||||
if (await fs.pathExists(configTemplatePath)) {
|
||||
await fs.copy(configTemplatePath, configTemplateDest);
|
||||
log('- 已复制 config-template.ini 到 app_dist');
|
||||
} else {
|
||||
throw new Error(`❌ 找不到 config-template.ini:${configTemplatePath}`);
|
||||
}
|
||||
|
||||
// 复制其他源文件
|
||||
for (const source of sourcesToProcess) {
|
||||
const sourcePath = path.join(rootDir, source);
|
||||
const destPath = path.join(outputDir, source);
|
||||
if (await fs.pathExists(sourcePath)) {
|
||||
await fs.copy(sourcePath, destPath);
|
||||
}
|
||||
}
|
||||
log('- 其他所有源文件已复制。');
|
||||
|
||||
// 自动化识别需要保留的内容(只执行一次)
|
||||
log('🔍 [3/6] 自动化识别需要保留的内容...');
|
||||
const ipcChannels = await extractIpcChannels();
|
||||
const exposedApis = await extractExposedApis();
|
||||
const exportedIds = await extractExportedIdentifiers();
|
||||
const classMethods = await extractClassMethods();
|
||||
const nodeBuiltinMethods = await extractNodeBuiltinMethods();
|
||||
|
||||
// 合并所有需要保留的内容
|
||||
const reservedNames = new Set();
|
||||
const reservedStrings = new Set();
|
||||
|
||||
// 添加 IPC 频道到字符串保留列表
|
||||
ipcChannels.forEach(ch => reservedStrings.add(ch));
|
||||
|
||||
// 添加暴露的 API
|
||||
exposedApis.forEach(api => {
|
||||
reservedNames.add(api);
|
||||
reservedStrings.add(api);
|
||||
});
|
||||
|
||||
// 添加导出标识符
|
||||
exportedIds.names.forEach(name => reservedNames.add(name));
|
||||
exportedIds.strings.forEach(str => reservedStrings.add(str));
|
||||
|
||||
// 添加类方法名
|
||||
classMethods.forEach(method => reservedNames.add(method));
|
||||
|
||||
// [修复] 添加 MainPage 的关键方法名到保留列表(防止方法被压缩)
|
||||
// 这些方法名必须保留,否则 this.methodName.bind() 会失败
|
||||
const mainPageMethods = [
|
||||
'renderWelcomePage',
|
||||
'renderToolHealthCheckPage',
|
||||
'renderLogsPage',
|
||||
'renderSettingsPage',
|
||||
'updateActiveNavButton',
|
||||
'navigateTo',
|
||||
'initializePage',
|
||||
'init',
|
||||
'bindWindowControls',
|
||||
'bindNavigationEvents',
|
||||
'bindThemeToggle',
|
||||
'addRippleEffectListener',
|
||||
'bindGlobalKeyListener',
|
||||
'loadLanguage',
|
||||
'autoCheckUpdates',
|
||||
'listenForDownloadProgress',
|
||||
'listenForGlobalNetworkSpeed',
|
||||
'handleSecretTrigger',
|
||||
'renderTrafficChart',
|
||||
'handleLanguageChange',
|
||||
'updateLockedToolsList',
|
||||
'updateToolHealthStats',
|
||||
'bindSettingsPageEvents'
|
||||
];
|
||||
mainPageMethods.forEach(method => {
|
||||
reservedNames.add(method);
|
||||
reservedStrings.add(method);
|
||||
});
|
||||
|
||||
// 添加 UIManager 的关键方法名到保留列表
|
||||
const uiManagerMethods = [
|
||||
'getHealthIndicator',
|
||||
'renderToolboxPage',
|
||||
'_renderCurrentPage',
|
||||
'_filterAndRenderTools'
|
||||
];
|
||||
uiManagerMethods.forEach(method => {
|
||||
reservedNames.add(method);
|
||||
reservedStrings.add(method);
|
||||
});
|
||||
|
||||
// 添加 Node.js 内置模块方法名(重要!防止 path.join 等被压缩)
|
||||
nodeBuiltinMethods.forEach(method => {
|
||||
reservedNames.add(method);
|
||||
reservedStrings.add(method);
|
||||
});
|
||||
|
||||
// 添加 JavaScript 原生方法
|
||||
['bind', 'call', 'apply', 'toString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable'].forEach(name => {
|
||||
reservedNames.add(name);
|
||||
reservedStrings.add(name);
|
||||
});
|
||||
|
||||
// 添加 Electron 和浏览器相关
|
||||
['electronAPI', 'window', 'document', 'require', 'module', 'exports', 'ipcRenderer', 'ipcMain', 'contextBridge', 'BrowserWindow', 'app', 'process', 'global', 'console'].forEach(name => {
|
||||
reservedNames.add(name);
|
||||
reservedStrings.add(name);
|
||||
});
|
||||
|
||||
// 添加 Node.js 内置模块名
|
||||
['path', 'fs', 'crypto', 'os', 'https', 'http', 'stream', 'child_process', 'worker_threads', 'electron', 'util', 'events', 'buffer'].forEach(name => {
|
||||
reservedNames.add(name);
|
||||
reservedStrings.add(name);
|
||||
});
|
||||
|
||||
log(`- 识别完成: ${ipcChannels.size} 个 IPC 频道, ${exposedApis.size} 个暴露 API, ${exportedIds.names.size} 个导出标识符, ${classMethods.size} 个类方法, ${nodeBuiltinMethods.size} 个 Node.js 内置方法`);
|
||||
log(`- 总计保留: ${reservedNames.size} 个标识符, ${reservedStrings.size} 个字符串`);
|
||||
|
||||
// 准备保留数据对象
|
||||
const reservedData = { reservedNames, reservedStrings };
|
||||
|
||||
log('📦 [4/6] 压缩 JavaScript 文件...');
|
||||
const jsFiles = await findJsFiles(outputDir);
|
||||
|
||||
// 分离关键文件和普通文件
|
||||
const criticalFiles = [];
|
||||
const normalFiles = [];
|
||||
for (const filePath of jsFiles) {
|
||||
if (isCriticalFile(filePath)) {
|
||||
criticalFiles.push(filePath);
|
||||
} else {
|
||||
normalFiles.push(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
log(`- 发现 ${criticalFiles.length} 个关键文件,${normalFiles.length} 个普通文件`);
|
||||
|
||||
// 处理所有文件
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
for (const filePath of jsFiles) {
|
||||
try {
|
||||
const code = await fs.readFile(filePath, 'utf8');
|
||||
const minified = await minifyCode(code, filePath, reservedData);
|
||||
await fs.writeFile(filePath, minified);
|
||||
successCount++;
|
||||
if (successCount % 10 === 0) {
|
||||
try {
|
||||
process.stdout.write(Buffer.from(`\r- 已处理: ${successCount}/${jsFiles.length}`, 'utf8'));
|
||||
} catch (e) {
|
||||
process.stdout.write(`\r- 已处理: ${successCount}/${jsFiles.length}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const relativePath = path.relative(outputDir, filePath);
|
||||
const errorMsg = `\n❌ 处理 ${relativePath} 失败: ${error.message || error}`;
|
||||
try {
|
||||
process.stderr.write(Buffer.from(errorMsg + '\n', 'utf8'));
|
||||
} catch (e) {
|
||||
console.error(errorMsg);
|
||||
}
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
log(`\n- 处理完成: ${successCount} 成功, ${failCount} 失败`);
|
||||
|
||||
log('📊 [5/6] 生成构建报告...');
|
||||
const buildReport = {
|
||||
timestamp: new Date().toISOString(),
|
||||
version: packageJson.version,
|
||||
filesProcessed: {
|
||||
total: jsFiles.length,
|
||||
critical: criticalFiles.length,
|
||||
normal: normalFiles.length,
|
||||
success: successCount,
|
||||
failed: failCount
|
||||
},
|
||||
security: {
|
||||
minificationEnabled: true,
|
||||
reservedIdentifiers: {
|
||||
ipcChannels: ipcChannels.size,
|
||||
exposedApis: exposedApis.size,
|
||||
exportedIdentifiers: exportedIds.names.size,
|
||||
classMethods: classMethods.size,
|
||||
nodeBuiltinMethods: nodeBuiltinMethods.size,
|
||||
total: reservedNames.size
|
||||
},
|
||||
autoDetectionEnabled: true
|
||||
}
|
||||
};
|
||||
await fs.writeJson(
|
||||
path.join(outputDir, 'build-report.json'),
|
||||
buildReport,
|
||||
{ spaces: 2 }
|
||||
);
|
||||
log('- 构建报告已生成');
|
||||
|
||||
log('✅ [6/6] 构建过程成功完成!');
|
||||
log(`📦 最终的应用文件已准备就绪,位于: ${outputDir}`);
|
||||
log(`🔐 安全措施: 代码压缩 + 自动识别保留内容`);
|
||||
log(`📈 处理统计: ${jsFiles.length} 个文件 (${successCount} 成功, ${failCount} 失败)`);
|
||||
}
|
||||
|
||||
// 运行构建
|
||||
run().catch(err => {
|
||||
const errorMsg = `❌ 在构建过程中发生错误:\n${err.stack || err.message || err}`;
|
||||
try {
|
||||
process.stderr.write(Buffer.from(errorMsg + '\n', 'utf8'));
|
||||
} catch (e) {
|
||||
console.error(errorMsg);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user