使用Webpack打包兼容低版本IE<9
对Webpack打包兼容低版本IE<9时遇到问题的排查过程。
最近在使用webpack打包时,遇到一个问题:
已经添加了polyfill,且转成es3语法的前提下,新增uglify之后IE<9浏览器报错。故,怀疑是uglify做了坏事,一步步追踪下去,发现原来是不太规范的一个写法引起的IE老版本下自身bug。
关于版本
使用不同版本的webpack2,错误依旧复现。
以下讨论的是webpack2.5.1,内置uglify-js2.8.5。
错误1,缺少标识符
这种错误大多是压缩后,对象的属性没有了引号,或是以一些保留字作为了对象的属性,比如”default”。
这种通过对uglify的配置即可解决。这篇文章总结的很好煦涵说Webpack-IE低版本兼容指南。
具体配置可以参考本博客上一篇文章中关于的uglifyJS的配置。
错误2,没有找到某个对象的方法、属性(重点来了!!!)
这个就比较坑了。
表现是,初次访问,没有报错,只有执行某个操作时,才报错。可以推测,仅是进入到某个代码内才报错,也就是说是局部报错。
首先我们通过定位报错的那段代码,发现是个命名函数表达式。类似这样:
再对应至压缩之前的代码是这样的:
从未压缩的代码可以看到,其实b是一个在函数体内对a的一个引用。
然后简单的把b改成a,再执行,发现没有报错了。
根据MDN上关于命名函数表达式的定义,可以知道function 的 test 和外部的 test 变量不是一回事。test 这个变量作用域链的原因,在函数内部使用的时候优先找到了 test 这个 function 定义了。
所以uglify把B -> A
是没有问题的。
那么为什么只会在IE<9会报错呢。
这是因为命名表达式在IE<9的一个JScript bugs.
简单来说,是这样的:正常情况下,b只能在函数体内使用,a与b指向同一个内存地址。如果在函数外引用,会报错,未定义。
但是在IE<9,会创建2个独立的函数,分别给a,和b。以至于a !== b.这样上面这个函数在IE9以下执行的时候,会报函数体内的对象找不到对应的方法或属性。在函数外引用,反而不报错。
看这里的讨论
|
|
解决方案
- 我们在写原始代码时,不要采用匿名函数表达式。而是需要采用具名函数表达式,且与赋值变量不同的名字。
- babel6时,去掉
presets: ["es2015"]
,穷举preset-es2015列表中除了transform-es2015-function-name
之外的其他相关plguins。不需要单独安装每一个transform-es2015-*的plugin, 安装babel-preset-es2015
就可以了。
为什么babel按照es2015转换时,会自动添加function的name呢
funciton添加name的好处大多是为了方便调试。可以观察到调用栈Call Stack。虽然现在大多数高级浏览器都可以自己找到这个匿名函数,但是,当匿名函数的层级比较深时,就找不到了。或者不太高级的浏览器,自己也找不到。加了这个之后,就可以方便的看到调用栈了。
另外呢,还可以便于元编程。函数名也已是ES6的标准之一了,会被自动添加。
babel6 打包时配置
presets: ["es2015"]
,会包括transform-es2015-function-name
, 它的作用就是将es2015 function.name特性应用到所有function中。在babel5的时候有个blacklist选项可以关掉一些不想要的特性。但是babel6的时候去掉了这个配置选项。解决办法是穷举babel的plugins,里面剔除transform-es2015-function-name。
|
|
- 也有讨论在
loose mode
下去掉add function name的特性.transform-es2015-classes loose mode shouldnt add function names
|
|
最后一个问题
按照UglifyJS2文档,配置了support-ie8: true
之后,就可以避免NFE的问题了。但为啥实际测试中还是未果呢。源码继续追踪中。。
参考文章
写在最最后
一开始怀疑是webpack版本问题,验证不是之后。
进而怀疑是因为webpack默认为匿名函数添加函数名,认为是webpack做的这件事。这里遗漏了,webpack其实自身什么都不做,只是一个框架。其他的转换什么的是由各插件做的。
后来在babel在线实验上验证通用会为匿名函数添加函数名。这时可以证实是babel做了这件事。
接下来,怀疑是uglify的问题,但却忘了,只有在IE<9才会报错,在Chrome下是正常的,这时应该怀疑是某种写法在IE下有兼容性问题。