Webpack4+Babel7+ES6兼容IE8的实现


Posted in Javascript onApril 10, 2019

前阵子重构了一个挺有意思的项目,是一个基于浏览器环境的数据采集sdk。公司各个产品的前端页面中都嵌入了这个sdk,用于采集用户的行为数据,上传到公司的大数据平台,为后续的运营决策分析提供数据支撑。

笔者接手这个项目的时候,前任开发者已经把功能都写差不多了。唯一需要做的就是做下模块化拆分和代码规范,以便后续的开发维护。模块化拆分用webpack,代码规范用eslint。既然要重构,那就顺手用es6重写吧。callback也不要了,全换成promise,async、await也用起来,反正怎么爽怎么写。

问PM浏览器最低兼容到哪个版本,PM说兼容公司各个产品所兼容的最低版本就行。和公司各个产品的前端负责人沟通后发现,居然有兼容IE8的,真是我了个fk。

google了一下Webpack+Babel+ES6兼容IE8,果然坑很多。试了好几篇博客给出的方案,都跑不通。也没怎么研究具体哪里有问题,因为那些解决方案里面的webpack和babel都是旧版的,跑通了也不高兴用。笔者分析了那些博客中提出的几个关键性问题,然后参考webpack和babel最新的官方文档,总结出一套最新的Webpack4+Babel7+ES6兼容IE8的方案。

ES6兼容IE8需要解决四个问题

语法支持

IE浏览器不支持ES6的语法,只在IE10、IE11中支持了部分ES6的API,所以在IE浏览器中使用ES6需要把ES6的代码编译成ES5才能执行。方法也很简单,就是用babel-loader。这部分没什么坑,所以我也就不细说了。给个网站,大家可以自行查看ES5、ES6在各浏览器版本中的支持情况

https://kangax.github.io/compat-table/es6/

ES3保留关键字

如果在IE8下通过object.propertyName的方式使用ES3中的保留关键字(比如default、class、catch),就会报错

SCRIPT1048: 缺少标识符

webpack有一款loader插件es3ify-loader专门用来处理ES3的关键字兼容问题。这个插件的作用就是把这些保留字给你加上引号,使用字符串的形式引用。

// 编译前
function(t) { return t.default; }

// 编译后
function(t) { return t["default"]; }

然而,笔者亲身实践后发现,UglifyJS本来就已经提供了对IE浏览器的支持,不需要额外引入es3ify-loader。webpack默认的UglifyJS配置不支持ie8,需要手动配下。

{
 mode: 'production',
 optimization: {
  minimizer: [
   new UglifyJsPlugin({
    uglifyOptions: {
     ie8: true
    }
   })
  ]
 }
}

执行环境

解决了前面两个问题只能保证语法上不报错,但使用ES6中的API(比如Promise)还是会报错。另外,IE8对ES5的API支持也很差,只支持了少量的API,有些API还只是支持部分功能(比如Object.defineProperty)。所以,要在IE8中完美运行ES6的代码,不仅需要填充ES6的API,还要填充ES5的API。

babel为此提供了两种解决方案: @babel/polyfill、@babel/runtime。具体使用方法官方文档已经写的很详细了,笔者就不赘述了。这里纠正墨白同学文中的一个错误,就是@babel/polyfill现在已经支持按需加载,准确的说也不能算是错误,因为墨白同学在写这篇文章的时候还不支持按需加载。具体方法我就不细说了,文档里都有,配置下browserlist和@babel/preset-env的useBuiltsIns属性就可以了。

我只说下我在实际开发过程中碰到的坑。

虽然@babel/polyfill、@babel/runtime都支持按需加载,但都只能识别出业务代码中使用到的缺失的API,如果第三方库有用到这些缺失的API,babel不能识别出来,自然也就不能填充进来。比如regenerator-runtime中用到的Object.create和Array.prototype.forEach。解决办法是,在入口文件处手动引入缺失的API。

模块化加载

笔者原来是想用ES6的模块化加载方案,因为这样可以利用webpack的tree shaking,移除冗余代码,使打包出来的文件体积更小。但在IE8下测试发现Object.defineProperty会报错'Accessors not supported!'。报错代码如下

if ('get' in Attributes || 'set' in Attributes) throw TypeError('Accessors not supported!');

我用@babel/plugin-transform-modules-commonjs转成commonjs加载就可以把这个坑绕过去,但同时也意味着放弃了tree shaking。

总结

package.json

{
 "devDependencies": {
  "@babel/core": "^7.2.2",
  "@babel/plugin-transform-runtime": "^7.2.0",
  "@babel/preset-env": "^7.1.0",
  "@babel/runtime": "^7.3.4",
  "babel-loader": "^8.0.4",
  "core-js": "^3.0.1",
  "uglifyjs-webpack-plugin": "^2.0.1",
  "webpack": "^4.20.2",
  "webpack-cli": "^3.1.2",
  "webpack-dev-server": "^3.1.9",
  "webpack-merge": "^4.1.4"
 }
}

webpack配置

{
 module: {
  rules: [
   {
    test: /\.js$/,
    exclude: /(node_modules|bower_components)/,
    use: {
     loader: 'babel-loader',
     options: {
      presets: [
       '@babel/preset-env'
      ],
      plugins: [
       [
        '@babel/plugin-transform-runtime'
       ],
       [
        // 笔者为了兼容IE8才用了这个插件,代价是不能tree shaking
        // 没有IE8兼容需求的同学可以把这个插件去掉
        '@babel/plugin-transform-modules-commonjs'
       ]
      ]
     }
    }
   }
  ]
 },
 optimization: {
  minimizer: [
   new UglifyJsPlugin({
    sourceMap: true,
    uglifyOptions: {
     ie8: true,
    }
   })
  ]
 }
}

入口文件按需引入缺失的API

require('core-js/features/object/define-property')
require('core-js/features/object/create')
require('core-js/features/object/assign')
require('core-js/features/array/for-each')
require('core-js/features/array/index-of')
require('core-js/features/function/bind')
require('core-js/features/promise')

最后附上文章开头提到的sdk源码,笔者已将公司业务相关代码去除,将通用部分开源。https://github.com/xtTech/dc-sdk-js

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
一个加载js文件的小脚本
Jun 28 Javascript
Dom与浏览器兼容性说明
Oct 25 Javascript
各浏览器对click方法的支持差异小结
Jul 31 Javascript
js移除事件 js绑定事件实例应用
Nov 28 Javascript
html的DOM中Event对象onabort事件用法实例
Jan 21 Javascript
AngularJS使用指令增强标准表单元素功能
Jul 01 Javascript
Angular 2父子组件数据传递之@Input和@Output详解(下)
Jul 05 Javascript
react-native ListView下拉刷新上拉加载实现代码
Aug 03 Javascript
JavaScript折半查找(二分查找)算法原理与实现方法示例
Aug 06 Javascript
微信小程序dom操作的替代思路实例分析
Dec 06 Javascript
微信小程序实现卡片左右滑动效果的示例代码
May 01 Javascript
Js视频播放器插件Video.js使用方法详解
Feb 04 Javascript
微信小程序第三方框架对比 之 wepy / mpvue / taro
Apr 10 #Javascript
用node撸一个监测复联4开售短信提醒的实现代码
Apr 10 #Javascript
从0到1搭建Element的后台框架的方法步骤
Apr 10 #Javascript
详解vue.js移动端配置flexible.js及注意事项
Apr 10 #Javascript
小程序分享模块超级详解(推荐)
Apr 10 #Javascript
关于JavaScript 数组你应该知道的事情(推荐)
Apr 10 #Javascript
Vue中computed、methods与watch的区别总结
Apr 10 #Javascript
You might like
非常实用的php弹出错误警告函数扩展性强
2014/01/17 PHP
PHP里面把16进制的图片数据显示在html的img标签上(实现方法)
2017/05/02 PHP
laravel利用中间件做防非法登录和权限控制示例
2019/10/21 PHP
PHP基于array_unique实现二维数组去重
2020/07/14 PHP
javascript 遍历验证所有文本框的值
2009/08/27 Javascript
jquery 简单导航实现代码
2009/09/11 Javascript
jquery 淡入淡出效果的简单实现
2014/02/07 Javascript
js设置文本框中焦点位置在最后的示例代码(简单实用)
2014/03/04 Javascript
JQuery 给元素绑定click事件多次执行的解决方法
2014/09/09 Javascript
Bootstrap入门书籍之(四)菜单、按钮及导航
2016/02/17 Javascript
分离与继承的思想实现图片上传后的预览功能:ImageUploadView
2016/04/07 Javascript
在Html中使用Requirejs进行模块化开发实例详解
2016/04/15 Javascript
浅谈jQuery的bind和unbind事件(绑定和解绑事件)
2017/03/02 Javascript
JS实现websocket长轮询实时消息提示的效果
2017/10/10 Javascript
vue2中的keep-alive使用总结及注意事项
2017/12/21 Javascript
vue项目中跳转到外部链接的实例讲解
2018/09/20 Javascript
javascript中的this作用域详解
2019/07/15 Javascript
JavaScript Array.flat()函数用法解析
2020/09/02 Javascript
Python元组拆包和具名元组解析实例详解
2018/03/26 Python
Python使用Flask-SQLAlchemy连接数据库操作示例
2018/08/31 Python
使用django的ORM框架按月统计近一年内的数据方法
2019/07/18 Python
Python字典底层实现原理详解
2019/12/18 Python
python读取与处理netcdf数据方式
2020/02/14 Python
python获取响应某个字段值的3种实现方法
2020/04/30 Python
基于django micro搭建网站实现加水印功能
2020/05/22 Python
解决keras模型保存h5文件提示无此目录问题
2020/07/01 Python
在 Python 中使用 7zip 备份文件的操作
2020/12/11 Python
销售所有的狗狗产品:Dog.com
2016/10/13 全球购物
公司部门司机岗位职责
2014/01/03 职场文书
四年级科学教学反思
2014/02/10 职场文书
逃课打麻将检讨书
2014/10/05 职场文书
先进基层党组织材料
2014/12/25 职场文书
退税申请报告怎么写
2015/05/18 职场文书
jquery插件实现图片悬浮
2021/04/16 jQuery
css3 文字断裂效果
2022/04/22 HTML / CSS
JavaScript中reduce()的用法
2022/05/11 Javascript