SamHwang1990/blog

IE8 下访问webpack.UglifyJsPlugin 压缩的代码出错

SamHwang1990 opened this issue · 7 comments

环境

  • 浏览器:IE8
  • webpack: <=v1.14.0
  • UglifyJS: >= 2.7.0

问题分析

使用IE8 加载webpack 打包压缩后的代码时,会出现类似'undefined' 为空或不是对象的错误。

在进行简单的调试后,造成错误的原因是变量指向被改变了,表象是:压缩后的变量le原本是指向jQuery对象的,但在运行时却找不到jQuery对象下的属性或方法导致出错。

由于压缩后的代码比较难理清,所以,也找不到到底是哪段代码造成了变量覆盖问题。

对UglifyJS 进行了一下研究,大概确定是mangle阶段出了问题,貌似是做变量名混淆的。而在相关的选项中,有一个选项比较相关:--screw-ie8

查找了一下该选项的意义,看到有以下一段提交记录

Regardless of the --screw-ie setting, the names will not be leaked.
Code relying on the IE bug will not work properly after mangling.

Without --screw-ie: a hack has been added to the mangler to avoid
using the same name for a function expression and some other variable in
the same scope. This keeps legit code working, at the (negligible,
indeed) cost of one more identifier.

With --screw-ie you allow the mangler to name function expressions
with the same identifier as another variable in scope. After mangling
code might break in IE<9.

其中--screw-ie后来改名为--screw-ie8,大致上是对IE8 下带名字的函数表达式(Named Function Expression, NFE)做了一下hack。

IE8 的JS 引擎对NFE 有以下bug:JScript NFE bugs。简单理解就是,IE8 下NFE 会在作用域内声明并初始化一个变量,该变量名为函数表达式的名,值为该函数表达式。而这种行为在标准的Javascript 语法中是不存在的。

当UglifyJS 在mangle阶段,若发现--screw-ie8参数为false,则尝试绕开这些bug,使函数表达式的名字不会与作用域中其他变量名冲突。

而在UglifyJS ~v2.7.0后,参数--screw-ie8就默认为true了,即,如果不显式声明参数为false,则mangle的结果很可能造成NFE变量覆盖了作用域内的同名变量。提交历史:Enable --screw-ie8 by default

解决方案

理论上,只要我们在调用UglifyJS 时,显式设置--screw-ie8false即可避开压缩后的代码在IE8 下报错。如果使用UglifyJS API 调用的话,选项参数至少如下:

{
  mangle: {
    screw_ie8: false
  }
}

基于webpack.optimize.UglifyJsPlugin 的解决方案

当开发者是结合webpack、webpack.optimize.UglifyJsPlugin 来做打包和压缩的话,情况就稍微复杂了。

webpack@1.14.0之前,{ mangle: { screw_ie8: false } }都没有被正确传给UglifyJS,代码如下:

// webpack/lib/optimize/UglifyJsPlugin.js

if(options.mangle !== false) {
  ast.figure_out_scope();		// 正确应该是将options 传到这个函数调用处
  ast.compute_char_frequency(options.mangle || {});
  ast.mangle_names(options.mangle || {});
  if(options.mangle && options.mangle.props) {
    uglify.mangle_properties(ast, options.mangle.props);
  }
}

上面代码没有将screw_ie8参数传到ast.figure_out_scope();导致IE8 报错的问题没法绕开,除非屏蔽整个mangle,但这样源码泄漏的风险就很高了。

前几天作者终于将该bug 提交到webpack@v1.x了:Pass mangle options to ast.figure_out_scope in uglify。但新版还没发布,所以,暂时先将webpack 依赖版本指向该hash:

// package.json

"devDependencies": {
  "webpack": "webpack/webpack#f66f024"
}

参考链接:

nimoc commented

这是天坑,我在开发模式下没有压缩代码。IE8正常,发布时压缩代码不正常。

nimoc commented

或者使用 "webpack": "1.13.2"
因为 1.13.2 依赖的是 uglify-js@2.6.0
https://github.com/webpack/webpack/blob/v1.13.2/package.json#L19

webpack@1.14.0 未解决此问题

nimoc commented

再补充个,因为 uglify2 karma@1.4.0 不支持IE8 ,需要使用 karma@1.3.0
karma-runner/karma#2556

https://github.com/fast-flow/support-ie8

在 webpack1 里面还有这个问题
webpack/webpack#1912

真的是天坑。多谢楼主。
在 tags 里面找到了 1.15.0 这个版本已经修复了这个问题。应该也是 webpack1 的最后一个大版本了。
另外,1.15.0 依赖的是 uglifyJS 2.7.5