英文里有很多单词目前目前还没有翻译恰当的中文词汇与它们对应,导致平时学习和沟通时产生混淆。对于前端开发者来说,日常使用第三方库时一定注意过同时存在 *.js
和 *.min.js
、*.css
和 *.min.css
这样成对命名的文件,将前者处理成后者的过程,即“Minification”,中文语境一般称为“压缩”。与此同时,使用压缩软件压缩文件体积的过程,即“Compression”,也被称之为“压缩”。所以“把这些 JS 文件压缩一下”就成了一个考验你语言理解能力的玄学问题……
1 Minification
Minification 是动词 minify 的名词形式(minify 的意思是“to minimize or lessen the size of something”,也就是“最小化或者减小某物的尺寸大小”),这里把它翻译成“最小化”,而不是常规的“压缩”。将代码最小化的工具被称为“minifier”,既然与它对应的“magnifier”被译作“放大器”,那么这里也不妨把“minifier”翻译成“缩小器”。
Wikipedia 上这么解释编程中的最小化概念:最小化是指从解释型编程语言、标记语言源码中移除所有不必要字符(且不改变原有的功能)的过程。这些不必要字符通常包括空白符、换行符、注释、块分隔符(比如大括号),它们只是用于增加代码的可读性,执行的时候是非必须的。最小化可以减小源码的大小,使得网络传输时更加高效。
不要将最小化和代码混淆(Code Obfuscation)、数据压缩(Data Compression)混为一谈。最小化的代码很容易通过代码格式化、美化工具逆向处理。最小化的代码也不需要经过解压就能和源码一样被解释器解释执行。
2 Minification in JS
先从一个例子直接感受下什么是最小化,以下示例代码摘自 2001 年 Douglas Crockford 对 JSMin 项目的介绍。
// is.js
// (c) 2001 Douglas Crockford
// 2001 June 3
// is
// The -is- object is used to identify the browser. Every browser edition
// identifies itself, but there is no standard way of doing it, and some of
// the identification is deceptive. This is because the authors of web
// browsers are liars. For example, Microsoft's IE browsers claim to be
// Mozilla 4. Netscape 6 claims to be version 5.
// Warning: Do not use this awful, awful code or any other thing like it.
// Seriously.
var is = {
ie: navigator.appName == 'Microsoft Internet Explorer',
java: navigator.javaEnabled(),
ns: navigator.appName == 'Netscape',
ua: navigator.userAgent.toLowerCase(),
version: parseFloat(navigator.appVersion.substr(21)) ||
parseFloat(navigator.appVersion),
win: navigator.platform == 'Win32'
}
is.mac = is.ua.indexOf('mac') >= 0;
if (is.ua.indexOf('opera') >= 0) {
is.ie = is.ns = false;
is.opera = true;
}
if (is.ua.indexOf('gecko') >= 0) {
is.ie = is.ns = false;
is.gecko = true;
}
最小化后:
var is={ie:navigator.appName=='Microsoft Internet Explorer',java:navigator.javaEnabled(),ns:navigator.appName=='Netscape',ua:navigator.userAgent.toLowerCase(),version:parseFloat(navigator.appVersion.substr(21))||parseFloat(navigator.appVersion),win:navigator.platform=='Win32'}
is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){is.ie=is.ns=false;is.opera=true;}
if(is.ua.indexOf('gecko')>=0){is.ie=is.ns=false;is.gecko=true;}
目前以 UglifyJS 为代表的 minifier,对代码的处理已经远远不止移除空白符、换行符、注释、块分隔符这么简单,UglifyJS 会解析 JavaScript 代码,构建抽象语法树(AST),优化 AST,还可以将局部变量重命名为字母、移除没有引用到的变量,最终生成文件体积更小的 JavaScript 代码。
UglifyJS 包含以下部分:
- A parser which produces an abstract syntax tree (AST) from JavaScript code.
- A code generator which outputs JavaScript code from an AST, also providing the option to get a source map.
- A compressor (optimizer) — it uses the transformer API to optimize an AST into a smaller one.
- A mangler — reduce names of local variables to (usually) single-letters.
- A scope analyzer, which is a tool that augments the AST with information about where variables are defined/referenced etc.
- A tree walker — a simple API allowing you to do something on every node in the AST.
- A tree transformer — another API intended to transform the tree.
3 History
自从 2001 年 JSMin 发布以来,涌现了不少新的 minifier。
- 2006 年,Dojo Toolkit 发布基于 Java 和 Rhino 的 ShrinkSafe。
- 2007 年,Yahoo! 发布基于 Java 和 Rhino 的 YUI Compressor。
- 2009 年,Google 发布基于 Java 和 Rhino 的 Closure Compiler,实现了 Source Map 特性。
- 2009 年,Microsoft 的 ASP.NET 团队发布 Microsoft Ajax Minifier。
- 2010 年,Mihai Bazon 发布基于 Node.js 的 UglifyJS1。
- 2012 年,Mihai Bazon 发布重写版的 UglifyJS2,支持 Source Map。
- 2015 年,Babel 团队 发布 Babili,后更名为 babel-minify,支持 ES2015 语法。
- 2016 年,Google 发布 JavaScript 版的 Closure Compiler,支持 ES2015 语法(注:非重写版,而是将 Java 源码转换成了 JavaScript)。
- 2017 年,Rich Harris 发布 Butternut,支持 ES2015 语法。
注:
- Rhino 是 Mozilla 开源的使用 Java 实现的 JavaScript 引擎。
- 2008年,Google Chromium 团队开源使用 C++ 实现的 JavaScript 引擎 V8。
- 2009 年,Ryan Dahl 发布基于 V8 引擎的 Node.js。
- Source Map 目前还没有独立的官网和 GitHub 主页,提案都放在 Google Docs 上。
4 JS Minifiers 及其生态
下面列出了一些常见的 minifier,可以搭配 webpack、Rollup、Gulp 等构建工具使用。
- JSMin: The JavaScript Minification Filter.
- ShrinkSafe: ShrinkSafe is a standalone Java-based JavaScript compressor which utilizes Rhino to parse code and safely shorten the results.
- YUI Compressor: The Yahoo! JavaScript and CSS Compressor.
- Google Closure Compiler: Check, compile, optimize and compress Javascript with Closure-Compiler using Java.
closure-compiler-js repo: Check, compile, transpile, optimize and compress JavaScript with Closure Compiler in JS.- closure-compiler-npm repo: Package for managing and documenting closure-compiler for use via npm.
- google-closure-compiler
- google-closure-compiler-java (compiler.jar)
- google-closure-compiler-js (jscomp.js)
- google-closure-compiler-linux (compiler)
- google-closure-compiler-osx (compiler)
- google-closure-compiler-windows (compiler.exe)
- Microsoft Ajax Minifier (AjaxMin)
- UglifyJS (uglify-js): JavaScript parser, minifier, compressor and beautifier toolkit.
- Esmangle: mangler / minifier for Mozilla Parser API AST
- Babili (babel-minify): An ES6+ aware minifier based on the Babel toolchain.
- babel-preset-minify: Babel preset for all minify plugins.
- babel-minify-webpack-plugin: A Webpack Plugin for babel-minify
- Butternut: The fast, future-friendly minifier.
5 JS Minifiers Benchmarks
可以参考 Benchmarks - babel-minify。
- babel-minify 和 UglifyJS 的比较 - Babeljs
- What are the differences between UglifyJS and Closure Compiler - Quora
- babel-minify vs terser (instead uglify-js) - StackOverflow
6 常用 JS 库、框架使用哪些 minifier?
- React 使用 google-closure-compiler
- Vue 使用 terser
- three.js 使用 google-closure-compiler
- lodash 使用 uglify-js
- jQuery 使用 uglify-js
7 JS Minifiers 使用中遇到的问题
7.1 服务器使用了 gzip 还需要再用 minifier 处理吗?
建议将 minification 和 gzip 搭配使用,可以参考 The Difference Between Minification and Gzipping - CSS-Tricks。
7.2 webpack/Rollup 支持 Tree Shaking,还需要再用 minifier 处理吗?
webpack/Rollup 支持作用域提升(Scope Hoisting)和 Tree Shaking,但还是需要 minifier 才能发挥效果。
7.3 在打包工具中使用 UglifyJS/Terser 报错或者卡住
ERROR in vendor.js from UglifyJs Unexpected token: name (fn) [vendor.js:37118,6]
这种错误一般是因为 UglifyJS 目前只支持 ES5,可以切换到 Terser,或者使用 Babel 预先转译代码(包括 node_modules
中相关的模块)。
92% chunk asset optimization TerserPlugin
这种错误一般是因为引入了 *.min.js
文件,UglifyJS/Terser 处理 minified 代码会导致性能下降,建议在打包工具的配置中对相关 minified 文件进行排除。可以参考 uglify 压缩报错问题及 es5-imcompatible-versions - 知乎专栏。
7.4 UglifyJS/Terser 没有 changelog ?!
注意:Terser 从 v3.15.0 开始提供 changelog。
UglifyJS 目前并没有 changelog 或者 release notes,一方面维护者没有精力撰写,另一方面一直有用户请求提供,同时维护者指责没有人愿意贡献。除了 changelog,UglifyJS 还存在不遵循语义化版本号规则的问题,例如 3.2.2
升级到 3.3.2
会出现不兼容。
相关 Issues:
- Changelog & Release Quality Control #2130 - mishoo/UglifyJS2
- Request for a CHANGELOG #2744 - mishoo/UglifyJS2
- Please provide a changelog 🙏 #2950 - mishoo/UglifyJS2
- Changelog is missing #167 - terser-js/terser