本文详解在Webpack4和Babel7环境下,通过配置babel-loader、UglifyJSPlugin及处理ES3保留关键字、API填充和模块化加载等四个核心问题,实现ES6代码在IE8浏览器中的兼容运行。
项目背景与重构目标
近期对一款基于浏览器环境的数据采集SDK进行了重构。该SDK嵌入在公司各产品的前端页面中,负责采集用户行为数据并上传至公司大数据平台,为运营决策提供数据支持。
接手项目时,前任开发者已完成主要功能开发,仅需进行模块化拆分和代码规范整理以利于后续维护。模块化采用Webpack,代码规范使用ESLint。重构时决定使用ES6重写,废弃callback,全面采用Promise、async和await,以提升开发体验。
经与产品经理及前端负责人沟通,确认需要兼容公司各产品的最低浏览器版本,其中包括IE8。
针对Webpack、Babel与ES6兼容IE8的问题,网上现有方案多基于旧版本且难以直接运行。本文在分析现有方案关键问题并参考最新官方文档的基础上,总结出一套适用于Webpack4和Babel7的最新兼容方案。
ES6兼容IE8的四个核心问题
ES6代码在IE8中运行需解决四个主要问题。
语法支持与ES3保留关键字处理
语法支持
IE8及更早版本不支持ES6语法,仅IE10/11支持部分ES6 API。因此,需使用babel-loader将ES6代码编译为ES5。此步骤较为常规,具体浏览器支持情况可参考相关兼容性表格网站。
https://kangax.github.io/compat-table/es6/
ES3保留关键字
在IE8中,若通过object.propertyName方式访问ES3保留关键字(如default、class、catch)会引发错误。
SCRIPT1048: 缺少标识符
虽然存在es3ify-loader插件可将保留字转为字符串引用,但实际测试表明,Webpack默认的UglifyJS配置在手动启用ie8选项后,已能正确处理此类关键字,无需额外引入该插件。
// 编译前
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
}
})
]
}
}
执行环境与API填充策略
执行环境
仅解决语法问题仍不足以在IE8中运行ES6代码,因为IE8对ES5 API支持有限(如Object.defineProperty仅支持部分功能)。因此,需同时填充ES6和ES5的缺失API。
Babel提供了@babel/polyfill和@babel/runtime两种方案。官方文档已详细说明用法。值得注意的是,@babel/polyfill现已支持按需加载,通过配置browserlist和@babel/preset-env的useBuiltIns属性即可实现。
实际开发中需注意以下问题。
@babel/polyfill和@babel/runtime仅能识别业务代码中使用的缺失API,无法自动填充第三方库(如regenerator-runtime)使用的API(如Object.create、Array.prototype.forEach)。解决方法是在入口文件手动引入这些缺失的API。
模块化加载与Tree Shaking取舍
模块化加载
原计划使用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
希望本文内容对大家的学习有所帮助,欢迎支持。