Skip to content

Commit e4d2511

Browse files
author
Taois
committed
feat:优化打包体积
1 parent ba84342 commit e4d2511

File tree

5 files changed

+601
-1
lines changed

5 files changed

+601
-1
lines changed
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
#!/usr/bin/env node
2+
3+
import { execSync } from 'child_process';
4+
import { existsSync, rmSync, cpSync, mkdirSync, readdirSync, statSync } from 'fs';
5+
import { join, dirname } from 'path';
6+
import { fileURLToPath } from 'url';
7+
8+
const __filename = fileURLToPath(import.meta.url);
9+
const __dirname = dirname(__filename);
10+
11+
// 定义路径
12+
const rootDir = __dirname;
13+
const tempServerDir = join(rootDir, 'temp-server');
14+
const distBinaryDir = join(rootDir, 'dist-binary');
15+
const distDir = join(rootDir, 'dist');
16+
17+
// 配置选项
18+
const config = {
19+
// pkg优化选项
20+
pkg: {
21+
compress: 'Brotli', // 使用Brotli压缩 (GZip也可选)
22+
publicPackages: '*', // 将所有包标记为public以减少体积
23+
noBytecode: false, // 保持字节码生成以避免兼容性问题
24+
public: true, // 使用public选项加速打包
25+
options: 'max-old-space-size=512' // 限制内存使用
26+
},
27+
// UPX压缩选项
28+
upx: {
29+
enabled: true, // 是否启用UPX压缩
30+
level: 6, // 压缩级别 (1-9, 降低到6以提高兼容性)
31+
keepBackup: true, // 保留备份文件以便回滚
32+
ultraBrute: false, // 是否使用ultra-brute模式
33+
testAfterCompress: true, // 压缩后测试二进制文件
34+
fallbackOnError: true // 如果压缩后无法运行,则回滚到原文件
35+
}
36+
};
37+
38+
console.log('🚀 开始优化打包流程...\n');
39+
40+
// 检查UPX是否可用
41+
function checkUpxAvailable() {
42+
try {
43+
execSync('upx --version', { stdio: 'pipe' });
44+
return true;
45+
} catch (error) {
46+
console.log('⚠️ UPX未安装或不可用,将跳过UPX压缩步骤');
47+
console.log(' 可以从 https://upx.github.io/ 下载UPX');
48+
return false;
49+
}
50+
}
51+
52+
// 格式化文件大小
53+
function formatFileSize(bytes) {
54+
return (bytes / 1024 / 1024).toFixed(2) + ' MB';
55+
}
56+
57+
// 获取文件大小
58+
function getFileSize(filePath) {
59+
if (!existsSync(filePath)) return 0;
60+
return statSync(filePath).size;
61+
}
62+
63+
try {
64+
// 步骤 1: 前端构建
65+
console.log('📦 步骤 1: 构建前端项目...');
66+
execSync('pnpm build:apps', {
67+
stdio: 'inherit',
68+
cwd: rootDir
69+
});
70+
console.log('✅ 前端构建完成\n');
71+
72+
// 步骤 2: 清理 temp-server 中的旧文件
73+
console.log('🧹 步骤 2: 清理 temp-server 中的旧文件...');
74+
75+
const tempServerAppsDir = join(tempServerDir, 'apps');
76+
if (existsSync(tempServerAppsDir)) {
77+
rmSync(tempServerAppsDir, { recursive: true, force: true });
78+
console.log('✅ 已删除旧的 apps 目录');
79+
}
80+
console.log('');
81+
82+
// 步骤 3: 复制构建文件到 temp-server/apps/drplayer
83+
console.log('📁 步骤 3: 复制构建文件到 temp-server...');
84+
if (!existsSync(distDir)) {
85+
throw new Error('dist 目录不存在,请确保前端构建成功');
86+
}
87+
88+
if (!existsSync(tempServerDir)) {
89+
mkdirSync(tempServerDir, { recursive: true });
90+
}
91+
92+
const tempServerDrplayerDir = join(tempServerAppsDir, 'drplayer');
93+
if (!existsSync(tempServerAppsDir)) {
94+
mkdirSync(tempServerAppsDir, { recursive: true });
95+
}
96+
97+
cpSync(distDir, tempServerDrplayerDir, { recursive: true });
98+
console.log('✅ 已将 dist 内容复制到 apps/drplayer');
99+
console.log('');
100+
101+
// 步骤 4: 在 temp-server 目录中打包二进制文件(使用优化选项)
102+
console.log('⚙️ 步骤 4: 使用优化选项打包二进制文件...');
103+
104+
const tempServerNodeModules = join(tempServerDir, 'node_modules');
105+
if (!existsSync(tempServerNodeModules)) {
106+
console.log('📦 安装 temp-server 依赖...');
107+
execSync('pnpm install', {
108+
stdio: 'inherit',
109+
cwd: tempServerDir
110+
});
111+
}
112+
113+
// 构建优化的pkg命令
114+
let pkgCommand = 'pkg . --target node18-win-x64 --output dist-binary/drplayer-server-win.exe';
115+
116+
// 添加压缩选项
117+
if (config.pkg.compress) {
118+
pkgCommand += ` --compress ${config.pkg.compress}`;
119+
}
120+
121+
// 添加public packages选项
122+
if (config.pkg.publicPackages) {
123+
pkgCommand += ` --public-packages "${config.pkg.publicPackages}"`;
124+
}
125+
126+
// 添加public选项
127+
if (config.pkg.public) {
128+
pkgCommand += ' --public';
129+
}
130+
131+
// 添加no-bytecode选项(仅在明确启用时)
132+
if (config.pkg.noBytecode) {
133+
pkgCommand += ' --no-bytecode';
134+
}
135+
136+
// 添加Node.js选项
137+
if (config.pkg.options) {
138+
pkgCommand += ` --options "${config.pkg.options}"`;
139+
}
140+
141+
console.log(`🔧 执行命令: ${pkgCommand}`);
142+
execSync(pkgCommand, {
143+
stdio: 'inherit',
144+
cwd: tempServerDir
145+
});
146+
console.log('✅ 优化二进制文件打包完成\n');
147+
148+
// 步骤 5: 移动二进制文件到 dist-binary 目录
149+
console.log('📦 步骤 5: 移动二进制文件到 dist-binary...');
150+
151+
if (!existsSync(distBinaryDir)) {
152+
mkdirSync(distBinaryDir, { recursive: true });
153+
}
154+
155+
const tempDistBinaryDir = join(tempServerDir, 'dist-binary');
156+
if (existsSync(tempDistBinaryDir)) {
157+
const files = readdirSync(tempDistBinaryDir);
158+
for (const file of files) {
159+
const srcPath = join(tempDistBinaryDir, file);
160+
const destPath = join(distBinaryDir, file);
161+
162+
if (existsSync(destPath)) {
163+
rmSync(destPath, { force: true });
164+
}
165+
166+
cpSync(srcPath, destPath, { recursive: true });
167+
console.log(`✅ 已移动: ${file}`);
168+
}
169+
170+
try {
171+
rmSync(tempDistBinaryDir, { recursive: true, force: true });
172+
} catch (error) {
173+
console.log(`⚠️ 无法删除临时目录: ${error.message}`);
174+
}
175+
}
176+
177+
// 步骤 6: UPX压缩(如果启用且可用)
178+
if (config.upx.enabled && checkUpxAvailable()) {
179+
console.log('\n🗜️ 步骤 6: 使用UPX进行额外压缩...');
180+
181+
const files = readdirSync(distBinaryDir).filter(file => file.endsWith('.exe'));
182+
183+
for (const file of files) {
184+
const filePath = join(distBinaryDir, file);
185+
const originalSize = getFileSize(filePath);
186+
187+
// 创建备份(如果启用)
188+
if (config.upx.keepBackup) {
189+
const backupPath = join(distBinaryDir, file + '.backup');
190+
cpSync(filePath, backupPath);
191+
console.log(`📋 已创建备份: ${file}.backup`);
192+
}
193+
194+
try {
195+
let upxCommand = `upx -${config.upx.level}`;
196+
197+
// 添加额外的UPX选项以获得更好的压缩率
198+
if (config.upx.ultraBrute) {
199+
upxCommand += ' --ultra-brute'; // 尝试所有压缩方法(很慢但压缩率最高)
200+
}
201+
202+
// 添加兼容性选项
203+
upxCommand += ' --force'; // 强制压缩
204+
upxCommand += ` "${filePath}"`;
205+
206+
console.log(`🔧 压缩 ${file}...`);
207+
execSync(upxCommand, { stdio: 'pipe' });
208+
209+
const compressedSize = getFileSize(filePath);
210+
const compressionRatio = ((originalSize - compressedSize) / originalSize * 100).toFixed(1);
211+
212+
console.log(`✅ ${file} 压缩完成:`);
213+
console.log(` 原始大小: ${formatFileSize(originalSize)}`);
214+
console.log(` 压缩后: ${formatFileSize(compressedSize)}`);
215+
console.log(` 压缩率: ${compressionRatio}%`);
216+
217+
// 测试压缩后的二进制文件
218+
if (config.upx.testAfterCompress) {
219+
console.log(`🧪 测试压缩后的二进制文件...`);
220+
try {
221+
// 简单的启动测试 - 运行 --help 命令
222+
execSync(`"${filePath}" --help`, { timeout: 10000, stdio: 'pipe' });
223+
console.log(`✅ 压缩后的二进制文件测试通过`);
224+
} catch (testError) {
225+
console.log(`⚠️ 压缩后的二进制文件测试失败: ${testError.message}`);
226+
227+
if (config.upx.fallbackOnError) {
228+
console.log(`🔄 回滚到原始文件...`);
229+
const backupPath = join(distBinaryDir, file + '.backup');
230+
if (existsSync(backupPath)) {
231+
cpSync(backupPath, filePath);
232+
console.log(`✅ 已回滚到原始未压缩文件`);
233+
console.log(`📝 建议: pkg生成的二进制文件可能与UPX不兼容,使用原始文件`);
234+
}
235+
} else {
236+
throw testError;
237+
}
238+
}
239+
}
240+
241+
} catch (error) {
242+
console.log(`❌ UPX压缩 ${file} 失败: ${error.message}`);
243+
244+
// 如果压缩失败且有备份,恢复原文件
245+
if (config.upx.keepBackup) {
246+
const backupPath = join(distBinaryDir, file + '.backup');
247+
if (existsSync(backupPath)) {
248+
cpSync(backupPath, filePath);
249+
console.log(`🔄 已从备份恢复 ${file}`);
250+
}
251+
}
252+
}
253+
}
254+
} else if (config.upx.enabled) {
255+
console.log('\n⚠️ 跳过UPX压缩步骤(UPX不可用)');
256+
}
257+
258+
// 步骤 7: 清理临时文件
259+
console.log('\n🧹 步骤 7: 清理临时文件...');
260+
261+
const dirsToClean = [
262+
join(tempServerDir, 'apps'),
263+
join(tempServerDir, 'dist-binary')
264+
];
265+
266+
for (const dirPath of dirsToClean) {
267+
if (existsSync(dirPath)) {
268+
try {
269+
rmSync(dirPath, { recursive: true, force: true });
270+
console.log(`✅ 已清理: ${dirPath.replace(tempServerDir, 'temp-server')}`);
271+
} catch (error) {
272+
console.log(`⚠️ 无法清理目录: ${error.message}`);
273+
}
274+
}
275+
}
276+
277+
// 清理UPX备份文件(可选)
278+
if (!config.upx.keepBackup) {
279+
const backupFiles = readdirSync(distBinaryDir).filter(file => file.endsWith('.backup'));
280+
if (backupFiles.length > 0) {
281+
console.log('\n🗑️ 清理备份文件...');
282+
for (const backupFile of backupFiles) {
283+
const backupPath = join(distBinaryDir, backupFile);
284+
rmSync(backupPath, { force: true });
285+
console.log(`✅ 已删除备份: ${backupFile}`);
286+
}
287+
}
288+
}
289+
290+
console.log('\n🎉 优化打包流程完成!');
291+
console.log(`📁 二进制文件位置: ${distBinaryDir}`);
292+
293+
// 显示最终结果
294+
if (existsSync(distBinaryDir)) {
295+
const files = readdirSync(distBinaryDir);
296+
if (files.length > 0) {
297+
console.log('\n📋 最终生成的文件:');
298+
let totalSize = 0;
299+
files.forEach(file => {
300+
const filePath = join(distBinaryDir, file);
301+
const stats = statSync(filePath);
302+
const size = stats.size;
303+
totalSize += size;
304+
console.log(` - ${file} (${formatFileSize(size)})`);
305+
});
306+
console.log(`\n📊 总大小: ${formatFileSize(totalSize)}`);
307+
}
308+
}
309+
310+
} catch (error) {
311+
console.error('\n❌ 优化打包过程中出现错误:');
312+
console.error(error.message);
313+
process.exit(1);
314+
}

0 commit comments

Comments
 (0)