首页 / 技术 / 一键清理项目中的 console.log —— 一个安全的批量删除工具 一键清理项目中的 console.log —— 一个安全的批量删除工具 📅 2026-06-30 17:01:21 👁 8 阅读 ✍ 王乐园 📂 技术 # Node.js 在项目开发过程中,我们经常会在代码中留下 `console.log` 用于调试。项目上线前需要清理这些调试代码,但如果手动逐个文件查找删除,既耗时又容易遗漏。 于是我写了一个 Node.js 脚本,能够自动扫描整个项目并批量删除所有 `console.log` 语句。 # 一键清理项目中的 console.log —— 一个安全的批量删除工具 ## 背景 在项目开发过程中,我们经常会在代码中留下 console.log 用于调试。项目上线前需要清理这些调试代码,但如果手动逐个文件查找删除,既耗时又容易遗漏。 于是我写了一个 Node.js 脚本,能够**自动扫描整个项目并批量删除所有 console.log 语句**。 ## 遇到的问题 起初以为简单正则替换就行,但实际踩了不少坑: | 问题 | 说明 | |------|------| | 多行 console.log | 跨行调用,简单正则无法匹配 | | 嵌套括号 | 括号嵌套时,正则容易多删或少删 | | 行内混用 | 不能把整行删掉,只删 console.log 部分 | | 注释掉的 | 被注释的调试代码也需要一并清理 | | 行尾符被转换 | Windows 项目的 CRLF 被转成 LF,git 显示整文件变更 | | 误伤 template/style | .vue 文件的 CSS 和标签区域被意外修改 | ## 解决方案 核心思路是**用括号匹配算法替代纯正则**,逐字符遍历,精确计算括号深度来定位 console.log(...) 的完整范围。 ### 架构设计 ``` 入口 main() ├── getAllFiles() 递归获取所有目标文件 ├── detectLineEnding() 检测原始行尾符(CRLF/LF) └── removeConsoleLog() 入口分发 ├── .vue → processVueFile() 只处理 script 区域 │ └── processScriptContent() └── .js → processJsFile() 处理整个文件 └── processScriptContent() ├── findMatchingParenMultiLine() 多行括号匹配 ├── findMatchingParenInLine() 单行括号匹配 └── removeInlineConsoleLog() 行内精确移除 ``` ### 关键设计一:括号匹配算法 逐字符遍历,维护括号深度计数器,同时跟踪字符串状态(跳过字符串内的括号),精确匹配闭合位置。支持跨行匹配: ``` // 伪代码示意 function findMatchingParen(lines, startLine, startCol): depth = 0 inString = false for each line from startLine: for each char in line: if inString: if char is escape: skip if char closes string: inString = false continue if char opens string: inString = true if char is '(': depth++ if char is ')': depth-- if depth == 0: return current line index return -1 ``` ### 关键设计二:.vue 文件只处理 script 区域 用正则将 script 标签区域提取出来,只对该区域执行删除,template 和 style 完全不动: ```javascript // 只匹配 script 区域,其他区域原样保留 const scriptRegex = /(<script[^>]*>)([\s\S]*?)(<\/script>)/g; content.replace(scriptRegex, (match, openTag, body, closeTag) => { return openTag + processScriptContent(body, lineEnding) + closeTag; }); ``` ### 关键设计三:保留原始行尾符 处理前先检测文件是 CRLF 还是 LF,split 和 join 都用原始行尾符,避免 git 显示整文件变更: ```javascript function detectLineEnding(content) { const crlf = (content.match(/\r\n/g) || []).length; const lf = (content.match(/[^\r]\n/g) || []).length; return crlf > lf ? '\r\n' : '\n'; } ``` ### 关键设计四:精确 trimEnd 只对实际删除了 console.log 的行执行 trimEnd,其他行保持原样不动: ```javascript const { line: newLine, changed } = removeInlineConsoleLog(line); // 仅改动行做清理,其余行保持原样 result.push(changed ? newLine.trimEnd() : line); ``` ## 支持的场景 | 场景 | 处理方式 | |------|----------| | 单行独立 | 整行删除 | | 多行跨行 | 多行全部删除 | | 行内混用 | 只删 console.log 部分,保留其他代码 | | 被注释掉的 | 整行删除 | | 嵌套括号 | 精确匹配删除 | | 尾部分号 | 分号一并删除 | ## 使用方法 1. 将脚本文件放到项目根目录 2. 执行命令: ```bash node remove_console.cjs ``` 3. 查看输出,确认处理结果: ``` [已处理] pages/user/user.vue (删除 3 行) [已处理] utils/request.js (删除 5 行) 完成!共处理 12 个文件,删除 47 行 console.log 相关代码,耗时 0.35s ``` 4. 用 git diff 检查变更,确认无误即可 ## 安全保护 - .vue 文件只处理 script 区域,template / style 原样保留 - 保留原始行尾符(CRLF / LF),不会造成整文件行尾变更 - 仅对实际删除了 console.log 的行做 trimEnd,其余行保持原样 - 自动排除 node_modules / unpackage / dist / .git 目录 - 不删除 console.error / console.warn 等其他 console 方法 - 不影响 console.log 所在行的其他逻辑代码 ## 核心处理逻辑源码 以下是脚本中 console.log 删除的核心处理逻辑: ```javascript function processScriptContent(scriptContent, lineEnding) { const lines = scriptContent.split(lineEnding); const result = []; let lineIdx = 0; while (lineIdx < lines.length) { const line = lines[lineIdx]; // 场景1:被注释掉的 console.log → 整行删除 if (/^\s*\/\/\s*console\.log\s*\(/.test(line)) { const logStart = line.indexOf('console.log'); const parenStart = line.indexOf('(', logStart); const closeLineIdx = findMatchingParenMultiLine(lines, lineIdx, parenStart); lineIdx = closeLineIdx !== -1 ? closeLineIdx + 1 : lineIdx + 1; continue; } // 场景2:独立一行的 console.log → 整行(含多行)删除 if (/^\s*console\.log\s*\(/.test(line)) { const logStart = line.indexOf('console.log'); const parenStart = line.indexOf('(', logStart); const closeLineIdx = findMatchingParenMultiLine(lines, lineIdx, parenStart); if (closeLineIdx !== -1) { const closeLine = lines[closeLineIdx]; const closeParenPos = closeLine.lastIndexOf(')'); const afterLog = closeLine.slice(closeParenPos + 1).replace(/[;\s]/g, ''); if (!afterLog) { lineIdx = closeLineIdx + 1; continue; } } } // 场景3:行内混用的 console.log → 精确移除 const { line: newLine, changed } = removeInlineConsoleLog(line); result.push(changed ? newLine.trimEnd() : line); lineIdx++; } let finalContent = result.join(lineEnding); // 压缩连续空行 const multiEmpty = lineEnding.repeat(3); const doubleEmpty = lineEnding.repeat(2); while (finalContent.includes(multiEmpty)) { finalContent = finalContent.split(multiEmpty).join(doubleEmpty); } return finalContent; } ``` ## 行内精确移除逻辑 对于 console.log 和其他代码混在同一行的情况,需要精确移除 console.log 而保留其他代码: ```javascript function removeInlineConsoleLog(line) { let result = line; let changed = false; let safety = 0; while (result.includes('console.log') && safety < 50) { safety++; const logIdx = result.indexOf('console.log'); if (logIdx === -1) break; const parenStart = result.indexOf('(', logIdx); if (parenStart === -1) break; const closeParen = findMatchingParenInLine(result, parenStart); if (closeParen === -1) break; // 排除 xxx.console.log 的情况 if (logIdx > 0 && /\w/.test(result[logIdx - 1])) break; // 向前删除连续空白,向后删除分号 let deleteStart = logIdx; while (deleteStart > 0 && /[ \t]/.test(result[deleteStart - 1])) { deleteStart--; } let deleteEnd = closeParen + 1; while (deleteEnd < result.length && /[;\s]/.test(result[deleteEnd])) { deleteEnd++; } result = result.slice(0, deleteStart) + result.slice(deleteEnd); changed = true; } return { line: result, changed }; } ``` ## 总结 这个工具的核心思路是**用括号匹配算法替代纯正则**,从根源上解决了多行、嵌套等复杂场景下的匹配问题。同时通过 .vue 文件分区处理、行尾符检测、精确 trimEnd 等策略,确保 git diff 中不会出现无意义的变更。 如果你的项目也需要清理 console.log,可以参考上述核心逻辑自行实现,只需根据项目结构调整文件扫描和过滤规则即可。
// 评论区
// 最新评论