JS 压缩与美化
把一段 JavaScript 压缩成更小的体积,或者把别人压过、折行乱七八糟的代码重新排版。处理前会先做词法扫描,字符串和正则里的内容不会被误删。
上方输入代码后,这里会显示处理结果和体积变化。
为什么不能拿正则去删注释
网上很多一行式的 JS 压缩写法是 code.replace(/\/\/.*$/gm, ''),看起来能用,其实非常危险:const url = 'https://a.com' 里字符串内部的 // 会被当成注释起点,把后面半截代码整块删掉,代码就废了;正则字面量 /https?:\\/\\//g 同样会被误伤。更麻烦的是这种破坏往往不会报错,只是默默地少了一段逻辑。
本工具先跑一遍词法扫描:从前往后逐字符判断当前是字符串、模板字符串、正则、注释还是普通代码,只有在真正处于「代码状态」时才把 //、/* */ 当注释删掉。字符串里的转义(\n、\\')、模板字符串里的 ${...} 嵌套(嵌套里还能再有模板字符串)都会递归处理。源码里的这种例子可以点「填入示例」看到。
最难的一处:/ 是除号还是正则的起点
JavaScript 里 / 有两义:a / b 是除法,/abc/g 是正则。词法层面没法靠一个字符区分,标准做法是看前一个「有效 token」:如果前面是标识符、数字、字符串、)、]、} 这类可以结束表达式的 token,那这个 / 就是除号;如果前面是 (、,、=、return、typeof、case 这类后面应该跟表达式的 token,那就是正则开头。
这套启发式覆盖了绝大多数真实代码,但有两个已知的边界情况:} 之后既可能是正则(块结束,如 function f(){} /re/.test(x) 这种极少见的写法)也可能是除号(对象字面量结束,({a:1} / 2),以及 ++) 之类的组合。所以压缩后的代码请务必在浏览器里跑一遍测试,别只看体积变小了就发布。
为什么压缩时保留换行、也不改名
JavaScript 有自动分号插入(ASI):return 后面换行会插入分号,a\nb 其实是两条语句。如果压缩时把换行全删掉,return 换行再接值、a\n++b 这类代码的语义就会变,产生那种「压缩后就出 bug」的经典问题。保守做法是保留换行、只清掉行内多余空白和注释,这样能保证语义完全不变,代价是省下的体积比 terser 少一些。
变量名混淆(把 longVariableName 改成 a)需要完整的语法树和作用域分析——必须知道哪些是局部变量、哪些是全局引用、eval 和 with 有没有出现,否则就是把代码改坏。同样地,tree-shaking、常量折叠、死代码消除都依赖 AST。这些都属于构建工具(terser、esbuild、swc、rollup)的领域,本工具明确不做。
和 terser 的差距在哪
本工具只做「安全级别的清理」:删注释、去多余空白、可选重新缩进。terser 默认还会压掉局部变量名、合并相邻声明、把 if (x) return 1; return 2 变成 return x?1:2、内联单次使用的函数,通常能再省 20%~40%。如果目的是发布生产包,正确做法是在构建链路里配置压缩器,而不是手动粘贴;
手动压一段代码的场景一般是:往第三方系统里塞一小段脚本、给书签做 bookmarklet(那就还得手动去掉换行,但要自己确认 ASI 安全)、或者临时看看某段代码去掉注释后大概多大。另外,生产环境真正该开的是 gzip / brotli,压缩器省下的是传输前的原始体积,两者叠加效果最好。
上传前请留意许可证头:本工具会保留以 /*!、//! 开头,以及含 @license、@preserve 的注释——这是压缩工具的通行约定(terser 默认也用 /*! 标记保留)。但普通注释一律会被删掉,如果你的许可证声明写在普通注释里,压缩前记得手动改写成 /*! ... */,否则可能违背开源许可要求。