400 lines
14 KiB
JavaScript
400 lines
14 KiB
JavaScript
const fs = require('fs');
|
||
const path = require('path');
|
||
const { execSync } = require('child_process');
|
||
const { minify } = require('terser');
|
||
|
||
|
||
// 定义项目根目录
|
||
const rootDir = __dirname;
|
||
// 定义输出目录
|
||
const distDir = path.join(rootDir, 'dist');
|
||
// 定义要排除的目录和文件
|
||
const excludeDirs = ['node_modules', '.git', 'dist', '.vscode', 'scripts'];
|
||
const excludeFiles = ['.gitignore', 'package-lock.json', 'yarn.lock', 'release.js', 'README.md'];
|
||
|
||
// 统计信息变量
|
||
let totalJsOriginalSize = 0;
|
||
let totalJsCompressedSize = 0;
|
||
let jsFileCount = 0;
|
||
const jsSizeDetails = [];
|
||
// 存储包含console语句的JS文件
|
||
const filesWithConsole = [];
|
||
// 存储压缩错误的JS文件
|
||
const filesWithCompressionError = [];
|
||
// 存储压缩后仍含console语句的JS文件
|
||
const filesWithConsoleAfterCompression = [];
|
||
|
||
// 获取当前日期,用于生成zip文件名(格式:YYYY-MM-DD)
|
||
function getCurrentDate() {
|
||
const now = new Date();
|
||
const year = now.getFullYear();
|
||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||
const day = String(now.getDate()).padStart(2, '0');
|
||
return `${year}-${month}-${day}`;
|
||
}
|
||
|
||
// 定义wxml压缩函数
|
||
function compressWxml(content) {
|
||
// 移除HTML注释
|
||
content = content.replace(/<!--[\s\S]*?-->/g, '');
|
||
|
||
// 移除多余的空白字符(空格、换行、制表符)
|
||
// 保留标签内的必要空格
|
||
content = content.replace(/\s+/g, ' ');
|
||
|
||
// 移除标签前后的空格
|
||
content = content.replace(/\s*</g, '<');
|
||
content = content.replace(/>\s*/g, '>');
|
||
|
||
return content;
|
||
}
|
||
|
||
// 定义js压缩函数 - 使用Terser去除console语句
|
||
function compressJs(content, filePath) {
|
||
return content;
|
||
try {
|
||
// 使用Terser进行压缩
|
||
const result = minify(content, {
|
||
compress: {
|
||
drop_console: true, // 去除所有console语句
|
||
drop_debugger: true, // 去除debugger语句
|
||
dead_code: true,
|
||
passes: 3 // 增加压缩次数
|
||
},
|
||
mangle: false, // 不混淆变量名,保持代码可读性
|
||
output: {
|
||
ascii_only: true, // 确保输出ASCII字符
|
||
comments: false // 去除所有注释
|
||
}
|
||
});
|
||
|
||
if (result.error) {
|
||
console.warn('Terser compression failed for', filePath, ':', result.error.message);
|
||
if (filePath && !filesWithCompressionError.includes(filePath)) {
|
||
filesWithCompressionError.push(filePath);
|
||
}
|
||
return content; // 出错时返回原始内容
|
||
}
|
||
|
||
return result.code || content;
|
||
} catch (error) {
|
||
console.warn('Error in compressJs for', filePath, ':', error.message);
|
||
if (filePath && !filesWithCompressionError.includes(filePath)) {
|
||
filesWithCompressionError.push(filePath);
|
||
}
|
||
return content; // 捕获异常时返回原始内容
|
||
}
|
||
}
|
||
|
||
|
||
// 定义css压缩函数
|
||
function compressCss(content) {
|
||
// 移除单行注释
|
||
content = content.replace(/\/\/.*$/gm, '');
|
||
|
||
// 移除多行注释
|
||
content = content.replace(/\/\*[\s\S]*?\*\//g, '');
|
||
|
||
// 移除多余的空白字符
|
||
content = content.replace(/\s+/g, ' ');
|
||
|
||
// 移除CSS属性前后的空格
|
||
content = content.replace(/\s*([{:;}])\s*/g, '$1');
|
||
|
||
// 移除行首和行尾的空格
|
||
content = content.replace(/^\s+|\s+$/gm, '');
|
||
|
||
return content;
|
||
}
|
||
|
||
// 定义json压缩函数
|
||
function compressJson(content) {
|
||
try {
|
||
// 使用JSON.parse和JSON.stringify进行压缩
|
||
// 确保中文等非ASCII字符不会被转义
|
||
const jsonObj = JSON.parse(content);
|
||
return JSON.stringify(jsonObj, null, 0);
|
||
} catch (error) {
|
||
// 如果JSON解析失败,返回原始内容
|
||
console.warn('Failed to compress JSON, using original content:', error.message);
|
||
return content;
|
||
}
|
||
}
|
||
|
||
// 输出JS文件压缩统计结果
|
||
function outputJsCompressionStats() {
|
||
if (jsFileCount === 0) {
|
||
console.log('\nNo JS files were processed.');
|
||
return;
|
||
}
|
||
|
||
console.log('\n=== JS File Compression Statistics ===');
|
||
|
||
// 输出每个文件的详细信息
|
||
console.log('\nIndividual File Statistics:');
|
||
jsSizeDetails.forEach(file => {
|
||
console.log(`${file.path}`);
|
||
console.log(` Original: ${file.originalSize} bytes`);
|
||
console.log(` Compressed: ${file.compressedSize} bytes`);
|
||
console.log(` Saved: ${file.sizeDiff} bytes (-${file.compressionRatio}%)`);
|
||
console.log('');
|
||
});
|
||
|
||
// 计算整体统计信息
|
||
const totalSaved = totalJsOriginalSize - totalJsCompressedSize;
|
||
const overallRatio = totalJsOriginalSize > 0 ? (totalSaved / totalJsOriginalSize * 100).toFixed(2) : '0.00';
|
||
|
||
// 输出整体统计结果
|
||
console.log('=== Overall JS Compression Results ===');
|
||
console.log(`Total JS files processed: ${jsFileCount}`);
|
||
console.log(`Total original size: ${totalJsOriginalSize} bytes`);
|
||
console.log(`Total compressed size: ${totalJsCompressedSize} bytes`);
|
||
console.log(`Total saved: ${totalSaved} bytes (-${overallRatio}%)`);
|
||
console.log('=====================================');
|
||
}
|
||
|
||
// 递归复制目录并压缩文件(wxml, js, css, json)
|
||
function copyAndCompressDir(sourceDir, targetDir) {
|
||
// 创建目标目录
|
||
if (!fs.existsSync(targetDir)) {
|
||
fs.mkdirSync(targetDir, { recursive: true });
|
||
}
|
||
|
||
const files = fs.readdirSync(sourceDir);
|
||
|
||
files.forEach(file => {
|
||
const sourcePath = path.join(sourceDir, file);
|
||
const targetPath = path.join(targetDir, file);
|
||
const stats = fs.statSync(sourcePath);
|
||
|
||
// 跳过排除的目录和文件
|
||
if (excludeDirs.includes(file) || excludeFiles.includes(file)) {
|
||
return;
|
||
}
|
||
|
||
if (stats.isDirectory()) {
|
||
// 递归处理子目录
|
||
copyAndCompressDir(sourcePath, targetPath);
|
||
} else {
|
||
const extname = path.extname(file);
|
||
let compressedContent;
|
||
let content;
|
||
|
||
try {
|
||
// 根据文件类型选择不同的处理方式
|
||
if (extname === '.wxml') {
|
||
// 压缩wxml文件
|
||
content = fs.readFileSync(sourcePath, { encoding: 'utf8' });
|
||
compressedContent = compressWxml(content);
|
||
fs.writeFileSync(targetPath, compressedContent, { encoding: 'utf8' });
|
||
console.log(`Compressed and copied: ${sourcePath}`);
|
||
} else if (extname === '.js') {
|
||
// 压缩js文件
|
||
content = fs.readFileSync(sourcePath, { encoding: 'utf8' });
|
||
|
||
// 检测是否包含console语句
|
||
const hasConsole = /console\.(log|warn|error|info|debug|trace|table|dir|time|timeEnd)/i.test(content);
|
||
if (hasConsole) {
|
||
filesWithConsole.push(sourcePath);
|
||
}
|
||
|
||
compressedContent = compressJs(content, sourcePath);
|
||
|
||
// 检测压缩后是否仍包含console语句
|
||
const hasConsoleAfterCompression = /console\.(log|warn|error|info|debug|trace|table|dir|time|timeEnd)/i.test(compressedContent);
|
||
if (hasConsoleAfterCompression) {
|
||
filesWithConsoleAfterCompression.push({ sourcePath, targetPath });
|
||
}
|
||
|
||
fs.writeFileSync(targetPath, compressedContent, { encoding: 'utf8' });
|
||
|
||
// 统计JS文件大小变化
|
||
const originalSize = Buffer.from(content).length;
|
||
const compressedSize = Buffer.from(compressedContent).length;
|
||
const sizeDiff = originalSize - compressedSize;
|
||
const compressionRatio = originalSize > 0 ? (sizeDiff / originalSize * 100).toFixed(2) : '0.00';
|
||
|
||
totalJsOriginalSize += originalSize;
|
||
totalJsCompressedSize += compressedSize;
|
||
jsFileCount++;
|
||
jsSizeDetails.push({
|
||
path: sourcePath,
|
||
originalSize,
|
||
compressedSize,
|
||
sizeDiff,
|
||
compressionRatio
|
||
});
|
||
|
||
console.log(`Compressed and copied: ${sourcePath} - ${originalSize} → ${compressedSize} bytes (-${compressionRatio}%)`);
|
||
} else if (extname === '.css') {
|
||
// 压缩css文件
|
||
content = fs.readFileSync(sourcePath, { encoding: 'utf8' });
|
||
compressedContent = compressCss(content);
|
||
fs.writeFileSync(targetPath, compressedContent, { encoding: 'utf8' });
|
||
console.log(`Compressed and copied: ${sourcePath}`);
|
||
} else if (extname === '.json') {
|
||
// 压缩json文件
|
||
content = fs.readFileSync(sourcePath, { encoding: 'utf8' });
|
||
compressedContent = compressJson(content);
|
||
fs.writeFileSync(targetPath, compressedContent, { encoding: 'utf8' });
|
||
console.log(`Compressed and copied: ${sourcePath}`);
|
||
} else {
|
||
// 直接复制其他文件
|
||
fs.copyFileSync(sourcePath, targetPath);
|
||
console.log(`Copied: ${sourcePath}`);
|
||
}
|
||
} catch (error) {
|
||
console.error(`Error processing file ${sourcePath}:`, error.message);
|
||
// 发生错误时,直接复制原始文件
|
||
fs.copyFileSync(sourcePath, targetPath);
|
||
console.log(`Fallback: Copied original file ${sourcePath}`);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 使用系统命令创建zip压缩包
|
||
function createZipArchive(sourceDir, outputPath) {
|
||
try {
|
||
// 根据操作系统选择命令
|
||
if (process.platform === 'win32') {
|
||
// Windows系统使用PowerShell命令,排除文件名后缀为 -mp-weixin.zip 的文件
|
||
// 使用PowerShell的管道功能和Where-Object来排除特定文件
|
||
const command = `PowerShell.exe -Command "Get-ChildItem -Path \"${sourceDir}\" | Where-Object { $_.Name -notlike '*\-mp\-weixin\.zip' } | Compress-Archive -DestinationPath \"${outputPath}\" -Force"`;
|
||
execSync(command, { shell: 'cmd.exe' });
|
||
} else {
|
||
// Linux/Mac系统使用zip命令,排除文件名后缀为 -mp-weixin.zip 的文件
|
||
const command = `zip -r \"${outputPath}\" * -x \"*-mp-weixin.zip\"`;
|
||
execSync(command, { cwd: sourceDir });
|
||
}
|
||
console.log(`Zip archive created: ${outputPath}`);
|
||
} catch (error) {
|
||
console.error('Error creating zip archive:', error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
function cleanDistDir(distDir) {
|
||
if (fs.existsSync(distDir)) {
|
||
// 清理dist目录下除**-mp-weixin.zip文件外的所有内容
|
||
const files = fs.readdirSync(distDir);
|
||
files.forEach(file => {
|
||
const filePath = path.join(distDir, file);
|
||
const stats = fs.statSync(filePath);
|
||
|
||
// 只保留符合特定命名模式的zip文件(**-mp-weixin.zip)
|
||
if (stats.isFile() && path.extname(file) === '.zip' && file.endsWith('-mp-weixin.zip')) {
|
||
console.log(`Keeping zip file: ${file}`);
|
||
} else {
|
||
if (stats.isDirectory()) {
|
||
fs.rmSync(filePath, { recursive: true, force: true });
|
||
console.log(`Removed directory: ${file}`);
|
||
} else {
|
||
fs.unlinkSync(filePath);
|
||
console.log(`Removed file: ${file}`);
|
||
}
|
||
}
|
||
});
|
||
console.log('Cleaned dist directory (excluding -mp-weixin.zip files)');
|
||
}
|
||
}
|
||
|
||
// 主函数
|
||
function main() {
|
||
try {
|
||
// 解析命令行参数
|
||
const args = process.argv.slice(2);
|
||
const keepDist = args.includes('--keep-dist') || args.includes('-k');
|
||
const noZip = args.includes('--no-zip') || args.includes('-n');
|
||
|
||
// 清理dist目录(如果存在且不保留)
|
||
if (fs.existsSync(distDir)) {
|
||
cleanDistDir(distDir);
|
||
} else {
|
||
// 创建dist目录
|
||
fs.mkdirSync(distDir, { recursive: true });
|
||
console.log('Created dist directory');
|
||
}
|
||
|
||
// 复制并压缩文件到dist目录
|
||
console.log('Start copying and compressing files...');
|
||
copyAndCompressDir(rootDir, distDir);
|
||
console.log('Files copied and compressed successfully!');
|
||
|
||
// 输出JS文件压缩统计结果
|
||
outputJsCompressionStats();
|
||
|
||
// 生成zip文件名(格式:POCT检测分析平台-定制化-YYYY-MM-DD-mp-weixin.zip)
|
||
const currentDate = getCurrentDate();
|
||
const zipFileName = `POCT检测分析平台-定制化-${currentDate}-mp-weixin.zip`;
|
||
const zipPath = path.join(distDir, zipFileName);
|
||
|
||
// 创建zip压缩包(如果未指定--no-zip参数)
|
||
if (!noZip) {
|
||
console.log('Creating zip archive...');
|
||
createZipArchive(distDir, zipPath);
|
||
} else {
|
||
console.log('Skipping zip archive creation (--no-zip specified)');
|
||
}
|
||
|
||
// 清理dist目录(如果不保留)
|
||
if (!keepDist) {
|
||
cleanDistDir(distDir);
|
||
}
|
||
|
||
console.log('\nAll tasks completed successfully!');
|
||
console.log(`Dist directory: ${distDir}`);
|
||
console.log(`Zip file: ${zipPath}`);
|
||
|
||
// 输出包含console语句的JS文件列表
|
||
if (filesWithConsole.length > 0) {
|
||
console.log('\n=== JS Files with Console Statements ===');
|
||
console.log(`Found ${filesWithConsole.length} file(s) containing console statements:`);
|
||
filesWithConsole.forEach(file => {
|
||
console.log(`- ${file}`);
|
||
});
|
||
console.log('======================================');
|
||
} else {
|
||
console.log('\nNo JS files contain console statements.');
|
||
}
|
||
|
||
// 输出压缩错误的JS文件列表
|
||
if (filesWithCompressionError.length > 0) {
|
||
console.log('\n=== JS Files with Compression Errors ===');
|
||
console.log(`Found ${filesWithCompressionError.length} file(s) with compression errors:`);
|
||
filesWithCompressionError.forEach(file => {
|
||
console.log(`- ${file}`);
|
||
});
|
||
console.log('======================================');
|
||
} else {
|
||
console.log('\nNo JS files had compression errors.');
|
||
}
|
||
|
||
// 输出压缩后仍含有console语句的JS文件列表
|
||
if (filesWithConsoleAfterCompression.length > 0) {
|
||
console.log('\n=== JS Files with Console Statements After Compression ===');
|
||
console.log(`Found ${filesWithConsoleAfterCompression.length} file(s) still containing console statements after compression:`);
|
||
filesWithConsoleAfterCompression.forEach((file, index) => {
|
||
console.log(`${index + 1}. ${file.targetPath}`);
|
||
});
|
||
console.log('\n=================================================');
|
||
} else {
|
||
console.log('\nNo JS files contain console statements after compression.');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error occurred:', error);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
// 执行主函数
|
||
main();
|
||
|
||
// 输出帮助信息
|
||
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
||
console.log('Usage: node release.js [options]');
|
||
console.log('Options:');
|
||
console.log(' --keep-dist, -k Keep existing dist directory contents');
|
||
console.log(' --no-zip, -n Skip zip archive creation');
|
||
console.log(' --help, -h Show this help message');
|
||
} |