From 30ee81521bee6c2f401195b26c7670892386f241 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Mon, 29 Dec 2025 18:19:46 +0800 Subject: [PATCH] =?UTF-8?q?chore(release.js):=20=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release.js | 178 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 163 insertions(+), 15 deletions(-) diff --git a/release.js b/release.js index f0a6595..319916f 100644 --- a/release.js +++ b/release.js @@ -49,24 +49,79 @@ function compressWxml(content) { return content; } -// 定义js压缩函数 - 使用Terser去除console语句 +// 检测代码是否已被压缩 +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 { - // 使用Terser进行压缩 - const result = minify(content, { + // 检测代码是否已被压缩 + 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, - passes: 3 // 增加压缩次数 + 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, // 不混淆变量名,保持代码可读性 - output: { + format: { ascii_only: true, // 确保输出ASCII字符 - comments: false // 去除所有注释 + 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); @@ -153,6 +208,87 @@ function outputJsCompressionStats() { 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) { // 创建目标目录 @@ -306,20 +442,31 @@ function main() { 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)) { - cleanDistDir(distDir); + if (!keepDist && !onlyProcessDist) { + 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!'); + // 根据参数决定执行哪种流程 + 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(); @@ -337,8 +484,8 @@ function main() { console.log('Skipping zip archive creation (--no-zip specified)'); } - // 清理dist目录(如果不保留) - if (!keepDist) { + // 清理dist目录(如果不保留且不是只处理dist目录) + if (!keepDist && !onlyProcessDist) { cleanDistDir(distDir); } @@ -396,5 +543,6 @@ if (process.argv.includes('--help') || process.argv.includes('-h')) { 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'); } \ No newline at end of file