前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ES6 + Babel + React低版本浏览器采坑记录

ES6 + Babel + React低版本浏览器采坑记录

作者头像
IMWeb前端团队
发布2018-01-08 16:18:52
1.7K0
发布2018-01-08 16:18:52
举报
文章被收录于专栏:IMWeb前端团队IMWeb前端团队

本文作者:IMWeb 何璇 原文出处:IMWeb社区 未经同意,禁止转载

有个项目要兼容IE8-10

某天,胆大的某前端开发由于业务需要升级了项目依赖IMUI,升级了项目构建(babel 5.x => babel 6.x),于是...这个页面在IE下就白屏了。忙乎了一天加班到深夜,觉得实在是坑多,这里记录一下。

坑越来越深

经过分析,主要有这么几个兼容性问题:

react/react-dom依赖版本问题

这点比较好解决,将react的版本降至0.14.x即可,然后将imui中用到新特性的组件代码给删除(比如PureComponent)。

对象不支持 xxx 属性或方法

这种情况一般是使用了es6,es7的高级语法,解决方案有很多种:

  • 局部引入额外的库import assign from 'object-assign'
  • 全局引入polyfill(会污染全局,例如babel-polyfill
  • 使用babel-plugin-transform-runtime

这里就不详细说了,大家可以使用corejs方案,支持局部使用和全局实现。

代码语言:javascript
复制
import assign from 'core-js/library/fn/object/assign'; // 局部使用
import 'core-js/fn/object/assign'; // 全局实现

至于第三种方案,下面会详细说...

类继承问题

关于这个问题,网上也已有不少文章做了阐述,主要是因为babel-plugin-transform-es2015-classes对类继承的编译存在兼容性问题:

  • 对一些内置的类(Date, Array)继承会存在问题
  • 对一些不支持__proto__(IE <= 10)的浏览器,不会被正确继承

第一个问题简单,可以使用babel-plugin-transform-builtin-extend解决。

第二个问题,让我们来看一个例子:

代码语言:javascript
复制
// class App extends React.component {
//   constructor(props) {
//     super(props);
//   }
// }
// 被编译为
"use strict";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) { 
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 
  }
  // 这里使用了Object.create来创建以superClass的原型为原型的对象,重写了子类原型来实现继承,并将constructor指回subClass
  // 在es3中可以借助寄生式继承的方式,以避免经典原型链继承的缺点(多执行一遍父类的构造函数以及子类原型上冗余父类的实例属性)
  subClass.prototype = Object.create(superClass && superClass.prototype, { 
    constructor: { value: subClass, enumerable: false, writable: true, configurable: true } 
  }); 
  // 这里为什么要使用setPrototypeOf或__proto__呢?结合下面的$0
  // 为了子类能够继承父类的静态属性和方法
  // 由于IE9,10会执行__proto__,因此下面的$0根本无法调用到父类构造函数,因此无法继承父类的实例属性
  if (superClass) Object.setPrototypeOf ? 
    Object.setPrototypeOf(subClass, superClass) :
    subClass.__proto__ = superClass;
}

var App = function (_React$component) {
  _inherits(App, _React$component);

  function App(props) {
    _classCallCheck(this, App);

    return _possibleConstructorReturn(this, (App.__proto__ || Object.getPrototypeOf(App)).call(this, props)); // Mark: $0
  }

  return App;
}(React.component);

怎么解决,可以添加一个polyfill来解决(请查看下面参考链接中的从babel编译es6类继承的一个坑说起

或者使用babel提供的loose模式,编译结果如下:

代码语言:javascript
复制
// ...
// 省略
// ...
var App = function (_React$component) {
  _inherits(App, _React$component);

  function App(props) {
    _classCallCheck(this, App);
    // 注意这里是直接调用了父类的构造函数
    return _possibleConstructorReturn(this, _React$component.call(this, props));
  }

  return App;
}(React.component);

缺少标识符

大家想必都知道IE8中,保留字是不允许被当做键值的,比如var obj = { default: 1 }

而es6的模块体系中,大家都喜欢使用export default xxx来输出模块的默认值,这就尴尬了...babel编译后的代码在IE8上会直接报错,运行不了:

代码语言:javascript
复制
// import util from 'util';
// export default xxx;
// 会被编译成...
'use strict';

// IE8也不支持defineProperty,开启loose模式即可
Object.defineProperty(exports, "__esModule", {
  value: true
});

var _util = require('util');

var _util2 = _interopRequireDefault(_util);

// 注意就是这行!!!
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

exports.default = {};

而babel本身也提供了两个插件解决这个问题

本来直接打算在项目中的.babelrc文件中加上插件配置即可,但是加上了在某些情形下依然会报这个错误:

代码语言:javascript
复制
{
  "presets": [
    ["es2015", { "loose": true }],
    "react",
    "stage-0"
  ],
  "plugins": [
    [
      "transform-builtin-extend", {
          "globals": ["Error", "Array"],
          "approximate": true
      }
    ],
    "es3-member-expression-literals",
    "es3-property-literals"
  ]
}

天真的你以为这样就完了么,其实babel在升级到6.x版本后,将一些编译工作都分拆出去做成plugin,但是这两个plugin的实现是不太稳定的,项目代码的编译结果是部分模块的default加上了引号,部分模块没有(拿了一个比较复杂的模块试验了是稳定重现的),具体想了解的同学可以去看看issues或者源码:

最终的解决方案应该是用稳定的es3ify,由于项目中用的构建工具是fis3,这里给出fis3的示例(Webpack的同学用es3ify-loader即可):

代码语言:javascript
复制
fis.match('src/**.{js,jsx}', {
    rExt: 'js',
    parser: [
      fis.plugin('babel-imweb'),
      fis.plugin('es3ify')
    ]
})

缺少函数

前面说道,可以使用babel-plugin-transform-runtime来引入polyfill来解决高级用法的问题。但其实这个插件存在的原因是因为babel编译结果需要借助一下helpers函数(比如_extend),会放在模块编译结果的开始部分,造成冗余。而IMUI作为一个UI组件库供别人使用,正需要使用这个插件,避免污染全局的polyfill。让我们来看看官方是怎么说的:

The runtime transformer plugin does three things:

  • Automatically requires babel-runtime/regenerator when you use generators/async functions.
  • Automatically requires babel-runtime/core-js and maps ES6 static methods and built-ins.
  • Removes the inline Babel helpers and uses the module babel-runtime/helpers instead.

其实这个插件本身也没什么问题,但结合了我们的模块化工具modjs,就成了深坑。babel-runtime的编译结果依赖corejs里会带有这样的代码:

代码语言:javascript
复制
// babel-runtime/helpers/inherits
var _setPrototypeOf = require("../core-js/object/set-prototype-of");
...
exports.default = function (subClass, superClass) {
  ...
  // 注意这里的对于Object.setPrototypeOf改为了_setPrototypeOf2.default
  if (superClass) _setPrototypeOf2.default ? (0, _setPrototypeOf2.default)(subClass, superClass) : subClass.__proto__ = superClass;
};

modjs有这样的代码:

代码语言:javascript
复制
require = function(id) {
  ...
  var mod = modulesMap[id] = {
    exports: {}
  };
  ...
    ...
  var factoryConf = factoryMap[id];

  ...

  // 直接返回值
  if (typeof factoryConf.factory !== 'function') {
    mod.exports = factoryConf.factory;
    return mod.exports;
  }
  ...
    ...
  // 注意这里导致core-js/object/set-prototype-of的exports为{}
  // 所以上面的Babel编译结果代码运行就报错了
  mod.exports = factoryConf.factory.apply(global, args) || mod.exports || {};
  return mod.exports;
};

所以导致运行时出现缺少函数的报错。

总结

总之,IE真的是毒瘤...

参考链接

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 有个项目要兼容IE8-10
  • 坑越来越深
    • react/react-dom依赖版本问题
      • 对象不支持 xxx 属性或方法
        • 类继承问题
          • 缺少标识符
            • 缺少函数
            • 总结
            • 参考链接
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档