Files
mp-weixin-2811-xcx.aigc-qui…/release.js

548 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
// 检测代码是否已被压缩
function isCodeCompressed(content) {
// 如果代码没有换行符或换行符很少,很可能是已压缩的
const lineBreaks = (content.match(/\n/g) || []).length;
const hasManyLineBreaks = lineBreaks > content.length / 50; // 平均每50个字符至少有一个换行
// 如果代码有很多连续的分号或逗号,很可能是已压缩的
const hasManyConsecutiveSemicolons = /;{2,}/.test(content);
const hasManyConsecutiveCommas = /,{2,}/.test(content);
// 如果代码没有注释,很可能是已压缩的
const hasComments = /\/\/|\/\*/.test(content);
return !hasManyLineBreaks || hasManyConsecutiveSemicolons || hasManyConsecutiveCommas || !hasComments;
}
// 定义js压缩函数 - 使用Terser去除console语句、未使用变量和注释
function compressJs(content, filePath) {
return content;
try {
// 检测代码是否已被压缩
const isCompressed = isCodeCompressed(content);
// 先尝试使用正则表达式直接去除console语句
let processedContent = content;
// 匹配完整的console函数调用支持嵌套括号
const consoleRegex = /console\.(log|warn|error|info|debug|trace|table|dir|time|timeEnd|assert|clear|count|countReset|group|groupCollapsed|groupEnd|memory|profile|profileEnd|timeStamp)\s*\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)\s*;/gi;
processedContent = processedContent.replace(consoleRegex, '');
// 配置基础压缩选项
const baseOptions = {
compress: {
drop_console: true, // 去除所有console语句
drop_debugger: true, // 去除debugger语句
dead_code: true, // 移除死代码
unused: true, // 移除未使用的变量
toplevel: true, // 清理顶层作用域未使用的变量
passes: isCompressed ? 15 : 10, // 大幅增加压缩次数
reduce_funcs: true, // 合并或移除未使用的函数
collapse_vars: true, // 折叠定义后不再修改的变量
sequences: true, // 合并连续的变量声明
evaluate: true, // 提前计算常量表达式
unsafe: true, // 已压缩代码启用更激进的压缩策略
unsafe_comps: true, // 优化比较操作
reduce_vars: true, // 合并或移除变量
join_vars: true, // 合并变量声明
side_effects: false, // 假设函数调用没有副作用
pure_funcs: ['console.log', 'console.warn', 'console.error', 'console.info', 'console.debug'], // 标记这些函数为纯函数,可以安全移除
pure_getters: true, // 假设getter函数没有副作用
unsafe_math: true, // 允许不安全的数学优化
unsafe_proto: true, // 允许不安全的原型优化
unsafe_regexp: true, // 允许不安全的正则表达式优化
conditionals: true, // 优化条件表达式
comparisons: true, // 优化比较操作
booleans: true, // 优化布尔表达式
typeofs: true // 优化typeof操作
},
mangle: false, // 不混淆变量名,保持代码可读性
format: {
ascii_only: true, // 确保输出ASCII字符
comments: false, // 去除所有注释
beautify: false // 不美化输出
},
// 为已压缩代码启用更严格的处理
parse: {
bare_returns: true, // 允许顶级return语句
expression: false // 禁用表达式模式
}
};
// 使用Terser进行压缩
const result = minify(processedContent, baseOptions);
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('=====================================');
}
// 递归处理dist目录下已有的JS文件再次压缩以去除console语句、未使用变量和注释
function processExistingJsFiles(distDir) {
console.log(`Processing directory: ${distDir}`);
const files = fs.readdirSync(distDir);
console.log(`Found files: ${files.join(', ')}`);
files.forEach(file => {
const filePath = path.join(distDir, file);
const stats = fs.statSync(filePath);
if (stats.isDirectory()) {
// 递归处理子目录
processExistingJsFiles(filePath);
} else if (path.extname(file) === '.js') {
// 处理JS文件
console.log(`Found JS file: ${filePath}`);
try {
let content = fs.readFileSync(filePath, { encoding: 'utf8' });
// 检测是否包含console语句
const hasConsole = /console\.(log|warn|error|info|debug|trace|table|dir|time|timeEnd)/i.test(content);
if (hasConsole) {
filesWithConsole.push(filePath);
}
// 直接使用正则表达式去除console语句优化版本减少复杂度
let processedContent = content;
// 匹配完整的console函数调用支持嵌套括号
const consoleRegex = /console\.(log|warn|error|info|debug|trace|table|dir|time|timeEnd|assert|clear|count|countReset|group|groupCollapsed|groupEnd|memory|profile|profileEnd|timeStamp)\s*\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)\s*;/gi;
processedContent = processedContent.replace(consoleRegex, '');
// 去除单行注释
processedContent = processedContent.replace(/\/\/.*$/gm, '');
// 去除多行注释(优化版本,避免过度匹配)
processedContent = processedContent.replace(/\/\*[\s\S]*?\*\//g, '');
// 然后再使用terser进行压缩
const compressedContent = compressJs(processedContent, filePath);
// 检测压缩后是否仍包含console语句
const hasConsoleAfterCompression = /console\.(log|warn|error|info|debug|trace|table|dir|time|timeEnd)/i.test(compressedContent);
if (hasConsoleAfterCompression) {
filesWithConsoleAfterCompression.push({ sourcePath: filePath, targetPath: filePath });
}
// 确保即使内容变化很小也要写入文件
const finalContent = compressedContent;
// 写入文件(无论是否有变化)
fs.writeFileSync(filePath, finalContent, { encoding: 'utf8' });
console.log(`Processed: ${filePath}`);
// 更新统计信息
const originalSize = Buffer.from(content).length;
const compressedSize = Buffer.from(finalContent).length;
const sizeDiff = originalSize - compressedSize;
const compressionRatio = originalSize > 0 ? (sizeDiff / originalSize * 100).toFixed(2) : '0.00';
totalJsOriginalSize += originalSize;
totalJsCompressedSize += compressedSize;
jsFileCount++;
jsSizeDetails.push({
path: filePath,
originalSize,
compressedSize,
sizeDiff,
compressionRatio
});
console.log(` Original: ${originalSize} bytes`);
console.log(` Compressed: ${compressedSize} bytes`);
console.log(` Saved: ${sizeDiff} bytes (-${compressionRatio}%)`);
} catch (error) {
console.error(`Error processing file ${filePath}:`, error.message);
}
}
});
}
// 递归复制目录并压缩文件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');
const onlyProcessDist = args.includes('--only-process-dist') || args.includes('-p'); // 只处理dist目录下已有的JS文件
// 清理dist目录如果存在且不保留
if (fs.existsSync(distDir)) {
if (!keepDist && !onlyProcessDist) {
cleanDistDir(distDir);
}
} else {
// 创建dist目录
fs.mkdirSync(distDir, { recursive: true });
console.log('Created dist directory');
}
// 根据参数决定执行哪种流程
if (onlyProcessDist) {
// 只处理dist目录下已有的JS文件
console.log('Start processing existing JS files in dist directory...');
processExistingJsFiles(distDir);
console.log('Processing existing JS files completed!');
} else {
// 复制并压缩文件到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目录如果不保留且不是只处理dist目录
if (!keepDist && !onlyProcessDist) {
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(' --only-process-dist, -p Only process existing JS files in dist directory');
console.log(' --help, -h Show this help message');
}