说起ugilfy,这是目前前端工程化里最重要的伙伴之一,通过对代码的压缩混淆,它给我们带来了多种好处:
- 压缩:减少代码量,减少网络下载时间以及浏览器的解析时间
- 混淆:一定程度的提升代码阅读难度,有一定程度的保护代码作用
而前端工程化的象征——webpack
,从1.0时期起就内置了uglify插件,简单的配置即可使用该功能。
uglify-js
webpack 1.0~3.0均内置的插件,它伴随前端工程的服役时间最长,同时期也没有太多的竞争者,Closure
一直被批评过于激进,使用者寥寥。
但是,它有一个从诞生起就伴随的缺陷,仅支持ES5代码。随着浏览器的兼容性需求越来越低,很多非现代浏览器都已经逐步被市场淘汰,chrome近几年每年都会升4个大版本。
慢慢的,原生的简洁ES6代码已经可以在浏览器里运行了,这时候uglify-js
就显的有点笨重。对于ES5代码再压缩,可能也比不上直接输出ES6代码呀。
此外,还有一个危险的信号:
在前端界,大家都会去研究和讨论类库的实现,但对于工程上的工具项目,却知之甚少。对于开源项目,输入越多,它越健壮;相反的,关注的少,场景输入少,项目变得黑盒化的同时,也埋下了致命bug的种子。
uglify-es
在时代推动下,迫切需要uglify-js
可以解析ES6代码,于是uglify-es
诞生。webpack 4.0随着自身内部的一次大革新,将uglify-es
内置,并于今年初面世。
但是。。。我用了还不到半年就遇到一个致命bug。
诡异的是,因为没有深究uglify源码,构建出的简单示例并不会出现该问题。
示例代码如下:
// Parent.jsx
import Child from './Child'
class Parent extends React.PureComponent {
onChange = () => {
// 触发rerender
}
render() {
return <div><Child onChange={this.onChange} /></div>
}
}
// Child.jsx
class Child extends React.PureComponent {
componentWillMount() {
fetch('/api').then(this.props.onChange)
}
render() {
return <div />
}
}
最终压缩后的代码形如:
// Parent.js的render函数
return React.createElement(
'div',
null,
React.createElement(class Child extends PureComponent {})
)
可以看到,Child由于只被一处引入,所以不再是独立模块,甚至被处理成了内联。
在js语法里这样处理,似乎没什么问题,但是当处理的是包含生命周期的组件时,就触发了严重的问题,该Child组件会被不断的销毁和创建,引起死循环。
由于部分“玄学”原因,我没找到稳定复现该bug的写法。。。

解决方式是在uglify配置中关闭reduce_vars和reduce_funcs:
uglifyOptions: {
compress: {
reduce_vars: false,
reduce_funcs: false,
},
}
这仅仅是我碰到的问题,搜一下uglify-es
的bug,会发现,这项目确实有点哔——(消音),它已经从并肩作战的战友成功升级成4v6了。。。

terser
随着uglify-es
不再维护(我用着的时候都不知道= =!),webpack也悄悄的更改了内置uglify依赖:
uglify-es is no longer maintained
其实还有很多项目也进行了调整:
Switch minifier from uglify-es to terser
看样子terser是从uglify-es
fork出来的项目,目前看来,该项目维护者非常活跃,在很多uglify-es
的bug issue下都会看到他宣传terser没有bug的身影~
希望该项目可以重新赢回开发者们的信任,让前端工程更完善,效率更高!