GVKun编程网logo

React+Webpack+ES6 兼容低版本浏览器(IE9)解决方案(react兼容的浏览器版本)

14

在本文中,我们将给您介绍关于React+Webpack+ES6兼容低版本浏览器的详细内容,并且为您解答IE9解决方案的相关问题,此外,我们还将为您提供关于ES5Object.assign低版本浏览器内

在本文中,我们将给您介绍关于React+Webpack+ES6 兼容低版本浏览器的详细内容,并且为您解答IE9解决方案的相关问题,此外,我们还将为您提供关于ES5 Object.assign 低版本浏览器内核兼容问题、ES6 + Webpack + React + Babel 如何在低版本浏览器上愉快的玩耍(上)、ES6 + Webpack + React + Babel 如何在低版本浏览器上愉快的玩耍(下)、ES6+React+Webpack初步构建项目流程的知识。

本文目录一览:

React+Webpack+ES6 兼容低版本浏览器(IE9)解决方案(react兼容的浏览器版本)

React+Webpack+ES6 兼容低版本浏览器(IE9)解决方案(react兼容的浏览器版本)

虽然过了兼容IE6的噩梦时代,IE依旧阴魂不散,因为你可能还要兼容IE9。在ES6已经普及的今天,用ES6写react已经成了标配。但是babel编译的js语法,由于某些不规范的写法,可能在IE9下不能正确解释,很容易导致白屏。本文记录如下

起因

在准备提测的那天,顺便打开IE9看一眼(注意,这里是原生IE9,不是用IE11模拟的IE9),OMG!

排查后发现,原来是因为构造函数中使用了this。简写如下

class Child extends React.Component {
 constructor(props) {
    super(props);
    this.state = {count:this.props.count}
  }

  render(){
    return (<p>child</p>)
  }
}
class Superer extends React.Component {
  state = {count:1}
  render() {
    return <Child count = {this.state.count}/>
  }
}

老司机们肯定能一眼发现问题:this.state = {count:this.props.count} 构造函数中不应该使用this,而是 super(props)传入的 porps,应该改为this.state = {count:props.count}. 改正之后,问题确实解决了。但是问题来了,虽然写法确实不规范,为什么其他浏览器都运行正常,包括IE11,用IE11模拟iE9也没有问题,偏偏就原版的IE9有问题。

怎么能就这么不明不白的算了,哼!

原因

既然浏览器运行的代码是经过babel编译的,那这个锅先甩给babel。查看一下babel编译后的源码。如下

"use strict";

var _createClass = function () { 
function defineProperties(target,props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target,descriptor.key,descriptor); } } return function (Constructor,protoProps,staticProps) { if (protoProps) defineProperties(Constructor.prototype,protoProps); if (staticProps) defineProperties(Constructor,staticProps); return Constructor; }; }();

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); } subClass.prototype = Object.create(superClass && superClass.prototype,{ constructor: { value: subClass,enumerable: false,writable: true,configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass,superClass) : subClass.__proto__ = superClass; }

var Child = function (_React$Component) {
  _inherits(Child,_React$Component);

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

    var _this = _possibleConstructorReturn(this,(Child.__proto__ || Object.getPrototypeOf(Child)).call(this,props));

    _this.state = { count: _this.props.count };
    return _this;
  }

  _createClass(Child,[{
    key: "render",value: function render() {
      return React.createElement(
        "p",null,"child"
      );
    }
  }]);

  return Child;
}(React.Component);

var Superer = function (_React$Component2) {
  _inherits(Superer,_React$Component2);

  function Superer() {
    var _ref;

    var _temp,_this2,_ret;

    _classCallCheck(this,Superer);

    for (var _len = arguments.length,args = Array(_len),_key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }

    return _ret = (_temp = (_this2 = _possibleConstructorReturn(this,(_ref = Superer.__proto__ || Object.getPrototypeOf(Superer)).call.apply(_ref,[this].concat(args))),_this2),_this2.state = { count: 1 },_temp),_possibleConstructorReturn(_this2,_ret);
  }

  _createClass(Superer,value: function render() {
      return React.createElement(Child,{ count: this.state.count });
    }
  }]);

  return Superer;
}(React.Component);

重点看_inherits()和Child构造函数,

subClass.prototype = Object.create(superClass && superClass.prototype,configurable: true } }); 
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass,superClass) : subClass.__proto__ = superClass;
 
function Child(props) {
  _classCallCheck(this,Child);
  var _this = _possibleConstructorReturn(this,props));

  _this.state = { count: _this.props.count };
  return _this;
}

找不到的就是 _this.props.cout,显然,_this指向错误了。查阅(谷)资料(歌)后发现,
getPrototypeOf() 是 ES5 的方法,IE9+ 都能得到很好的支持,而 setPrototypeOf(),subClass.__proto__ = superClass 是 ES6 的方法,需要到 IE11 才支持,所以_this其实指向的是Function.prototype,而不是react.Component。所以props没有成功赋给Child类,当然就找不到了。

解决方法

果然这个锅是babel的。

那要怎么解决呢?如果是自己写的逻辑,直接修改写法就可以了。但是,如果你用了开源组件,看了源码,找到问题,提了issue,开发者还跟你互动,就说没问题,他还说他亲测没问题,就是不改,你该怎么办?(手动微笑脸)
当然是原(huan)谅(zu)他(jian)啊~~ ,既然锅是babel的,那就肯定还有一种解决方法。

使用babel插件babel-preset-es2015-ie
该插件,在检测到setPrototypeOf(),subClass.__proto__不支持时,自己包装了一个方法

function _inherits(subClass,superClass) { 
  ...; 
  if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass,superClass) : _defaults(subClass,superClass);
}
function _defaults(obj,defaults) {
 var keys = Object.getownPropertyNames(defaults);
  for (var i = 0; i < keys.length; i++) {
   var key = keys[i]; 
   var value = Object.getownPropertyDescriptor(defaults,key);
    if (value && value.configurable && obj[key] === undefined) {
     Object.defineProperty(obj,key,value); 
     } 
   }
  return obj;
}

至此,IE9下总是报错的问题就解决了,希望能给同样掉进此坑的小伙伴一点帮助,早点摆脱IE的魔爪。

参考文章:

ES6 + Webpack + React + Babel 如何在低版本浏览器上愉快的玩耍(上)
BABEL6 编译 ES6 继承代码的一个兼容问题(IE <= 10)

ES5 Object.assign 低版本浏览器内核兼容问题

ES5 Object.assign 低版本浏览器内核兼容问题

var _extends = Object.assign || function (target) {
    for (var i = 1; i < arguments.length; i++) {
        var source = arguments[i];
        for (var key in source) {
            if (Object.prototype.hasOwnProperty.call(source, key)) {
                target[key] = source[key];
            }
        }
    }
    return target;
};

  

 
 
 

ES6 + Webpack + React + Babel 如何在低版本浏览器上愉快的玩耍(上)

ES6 + Webpack + React + Babel 如何在低版本浏览器上愉快的玩耍(上)

起因

某天,某测试说:“这个页面在 IE8 下白屏,9也白。。”

某前端开发: 吭哧吭哧。。。一上午的时间就过去了,搞定了。

第二天,某测试说:“IE 又白了。。”

某前端开发: 吭哧吭哧。。。谁用的 Object.assign,出来我保证削不屎你。

原谅我不禁又黑了一把 IE。

有人可能会想,都要淘汰了,还有什么好讲的?

也许几年后,确实没用了,但目前我们的系统还是要对 ie8+ 做兼容,因为确实还有个别用户,尽管他没朋友。。。

记录下本次在 IE 下踩得坑,让后面的同学能够不再在这上面浪费时间了。

经过

测试

首先,看下面代码(以下测试在 IE9)

class Test extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <div>{this.props.content}</div>;
  }
}

module.exports = Test;

这段代码跑的妥妥的,没什么问题。

一般来说,babel 在转换继承时,可能会出现兼容问题,那么,再看这一段

class Test extends React.Component {
  constructor(props) {
    super(props);
  }
  test() {
      console.log(''test'');
  }
  render() {
    return <div>{this.props.content}</div>;
  }
}

Test.defaultProps = {
  content: "测试"
};

class Test2 extends Test {
  constructor(props) {
    super(props);
    this.test();
  }
}

Test2.displayName = ''Test2'';

module.exports = Test2;

这段代码同样也可以正常运行

也就是说在上述这两种情况下,不做任何处理(前提是已经加载了 es5-shim/es5-sham),在 IE9 下都可以正常运行。

然后我们再看下会跑挂的代码

class Test extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      test: 1,
    };
  }
  test() {
      console.log(this.state.value);
  }
  render() {
    return <div>{this.props.content}</div>;
  }
}

Test.defaultProps = {
  content: "测试"
};

class Test2 extends Test {
  constructor(props) {
    super(props);
    // SCRIPT5007: 无法获取属性 "value" 的值,对象为 null 或未定义
    this.test();
    
    // SCRIPT5007: 无法获取属性 "b" 的值,对象为 null 或未定义
    this.a = this.props.b;
  }
}
// undefined
console.log(Test2.defaultProps);

Test2.displayName = ''Test2'';

module.exports = Test2;

这段代码在高级浏览器中是没问题的,在 IE9 中会出现注释所描述的问题

从这些问题分析,可得出3个结论

  1. 在构造函数里的定义的属性无法被继承

  2. 在构造函数里不能使用 this.props.xx

  3. 类属性或方法是无法被继承的

也就是说,只要规避了这三个条件的话,不做任何处理(前提是已经加载了 es5-shim/es5-sham),在 IE9 下都可以正常运行。

第二点,是完全可以避免的,切记在 constructor 直接使用 props.xxx, 不要再用 this.props.xxx

第三点,也是可以完全避免的,因为从理论上来说,类属性就不该被继承,如果想使用父类的类属性可以直接Test2.defaultProps = Test.defaultProps;

第一点,可避免,但无法完全避免

原因

第一点,有时是无法完全避免的,那么就要查询原因,才能找到解决方案

我们把 babel 转义后的代码放出来就能查出原因了

''use strict'';

var _createClass = function () {
  ...
}();

function _classCallCheck(instance, Constructor) { 
  ...
}

function _possibleConstructorReturn(self, call) { 
  ...
  // 这个方法只是做了下判断,返回第一个或第二参数
  return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { 
  ...; 
  // 这里的 _inherits 是通过将子类的原型[[prototype]]指向了父类,所以如果在高级浏览器下,子类的可以继承到类属性
  // 根本问题也是出在这里,IE9 下既没有 `setPrototypeOf` 也没有 `__proto__`
  if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}

var Test = function (_React$Component) {
  ...
  return Test;
}(React.Component);

Test.defaultProps = {
  content: "测试"
};

var Test2 = function (_Test) {
  _inherits(Test2, _Test);

  function Test2(props) {
    _classCallCheck(this, Test2);
     // 这里的 this 会通过 _possibleConstructorReturn,来获取父类构造函数里定义的属性
     // _possibleConstructorReturn 只是做了下判断,如果第二个参数得到了正确执行,则返回执行结果,否则返回第一个参数,也就是子类的 this
     // 也就是说问题出在 Object.getPrototypeOf 
     // 在 _inherits 中将子类的原型指向了父类, 这里通过 getPrototypeOf 来获取父类,其实就是 _Test
     // Object.getPrototypeOf 不能正确的执行,导致了子类无法继承到在构造函数里定义的属性或方法,也无法继承到类属性或方法
    var _this2 = _possibleConstructorReturn(this, Object.getPrototypeOf(Test2).call(this, props));

    _this2.test();
    console.log(_this2.props.children);
    return _this2;
  }

  return Test2;
}(Test);

console.log(Test2.defaultProps);

Test2.displayName = ''Test'';

module.exports = Test2;

通过上述的代码注释,可以得出有两处问题需要解决

  1. 正确的获取父类(解决无法继承到在构造函数里定义的属性或方法)

  2. 正确的将子类的原型指向了父类(解决无法继承到类属性或方法)

解决方案

通过文档的查询,发现只要开启 es2015-classes 的 loose 模式即可解决第一个问题

loose 模式

Babel have two modes:

  • A normal mode follows the semantics of ECMAScript 6 as closely as possible.

  • A loose mode produces simpler ES5 code.

Babel 有两种模式:

  • 尽可能符合 ES6 语义的 normal 模式。

  • 提供更简单 ES5 代码的 loose 模式。

尽管官方是更推荐使用 normal 模式,但为了兼容 IE,我们目前也只能开启 loose 模式。

在 babel6 中,主要是通过 babel-preset-2015 这个插件,来进行转义的
我们看下 babel-preset-2015

 plugins: [
    require("babel-plugin-transform-es2015-template-literals"),
    require("babel-plugin-transform-es2015-literals"),
    require("babel-plugin-transform-es2015-function-name"),
    ...
    require("babel-plugin-transform-es2015-classes"),
    ...
    require("babel-plugin-transform-es2015-typeof-symbol"),
    require("babel-plugin-transform-es2015-modules-commonjs"),
    [require("babel-plugin-transform-regenerator"), { async: false, asyncGenerators: false }],
  ]

是一堆对应转义的插件,从命名上也可看出了大概,比如 babel-plugin-transform-es2015-classes 就是做类的转义的,也就是我们只需把它开启 loose 模式,即可解决我们的一个问题

[require(''babel-plugin-transform-es2015-classes''), {loose: true}],

看下开启了 loose 模式的代码,你会发现它的确更接近 ES5

var Test = function (_React$Component) {
  ...
  // 这里是 ES5 的写法
  Test.prototype.test = function test() {
    console.log(this.state.value);
  };
  /* normal 模式是这样的
  {
    key: ''test'',
    value: function test() {
      console.log(this.state.value);
    }
  }
  */
  return Test;
}(React.Component);

var Test2 = function (_Test) {
  _inherits(Test2, _Test);

  function Test2(props) {
    _classCallCheck(this, Test2);
    // 这里直接拿到了父类 _Test, 即解决了无法继承到在构造函数里定义的属性或方法
    var _this2 = _possibleConstructorReturn(this, _Test.call(this, props));

    _this2.test();
    return _this2;
  }

  return Test2;
}(Test);

我们可以通过去安装 babel-preset-es2015-loose, 这个插件来开启 loose 模式。

但从我们团队的 老司机 口中

image

得到了一个更好插件babel-preset-es2015-ie,看下这个插件的代码,发现它和原来的 babel-preset-2015 只有两行区别

[
  [require(''babel-plugin-transform-es2015-classes''), {loose: true}],
  require(''babel-plugin-transform-proto-to-assign''),
]

刚好解决我们上述碰到的两个问题

这个 babel-plugin-transform-proto-to-assign 插件会生成一个 _defaults 方法来处理原型

function _inherits(subClass, superClass) { 
  ...; 
  if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : _defaults(subClass, superClass);
}
function _defaults(obj, defaults) {
 var keys = Object.getOwnPropertyNames(defaults);
  for (var i = 0; i < keys.length; i++) {
   var key = keys[i]; 
   var value = Object.getOwnPropertyDescriptor(defaults, key);
    if (value && value.configurable && obj[key] === undefined) {
     Object.defineProperty(obj, key, value); 
     } 
   }
  return obj;
}

这个插件正确的将子类的原型指向了父类(解决无法继承到类属性或方法)

总结

本文讲述低版本浏览器报错的原因和解决方案

  • 一方面是提示下在构造函数里不要使用 this.props.xx

  • 另一方面也对继承的机制有了更好的理解

在这次项目中发现在低版本浏览器跑不起来的两点主要原因:

  1. SCRIPT5007: 无法获取属性 xxx 的值,对象为 null 或未定义,这种情况一般是组件继承后,无法继承到在构造函数里定义的属性或方法,同样类属性或方法也同样无法继承

  2. SCRIPT438: 对象不支持 xxx 属性或方法,这种情况一般是使用了 es6、es7 的高级语法,Object.assgin Object.keys 等,这种情况在移动端的一些 ‘神机’ 也一样会挂。

第一点本文已经分析,预知第二点讲解请见下篇。

备注:下篇会主要介绍下如何让 用了 Object.assign 的那位同学可以继续用,又不会被削。

ES6 + Webpack + React + Babel 如何在低版本浏览器上愉快的玩耍(下)

ES6 + Webpack + React + Babel 如何在低版本浏览器上愉快的玩耍(下)

回顾

起因:

某天,某测试说:“这个页面在 IE8 下白屏,9也白。。”
某前端开发: 吭哧吭哧。。。一上午的时间就过去了,搞定了。
第二天,某测试说:“IE 又白了。。”
某前端开发: 嘿咻嘿咻。。。谁用的 Object.assign,出来我保证削不屎你。

在上篇,我们主要抛出了两个问题,并给出了第一个问题的解决方案。

  1. SCRIPT5007: 无法获取属性 xxx 的值,对象为 null 或未定义,这种情况一般是组件继承后,无法继承到在构造函数里定义的属性或方法,同样类属性或方法也同样无法继承

  2. SCRIPT438: 对象不支持 xxx 属性或方法,这种情况一般是使用了 es6、es7 的高级语法,Object.assign Object.values 等,这种情况在移动端的一些 ‘神机’ 也一样会挂。

本篇将给出第二个问题的解决方案, 并对第一个问题的解决方案有了更新的进展。

文章略长,请耐心看~嘿嘿嘿~

image

正文开始

想要不支持该方法的浏览器支持,无非两种办法

  1. 局部引用,引入一个相同的方法代替,其缺点则是使用起来比较麻烦,每个用到的文件都要去引入。

  2. 全局实现,与之相反的方法是使用 polyfill ,其优点便是使用方便,缺点则是会全局污染,特别是实例方法,涉及到修改其 prototype ,不是你的类,你去修改它原型是不推荐的。

针对这两种办法,提供出以下几种方案,供大家参考

方案一:引入额外的库

拿最常用的 assign 来说,可以这样

import assign from ''object-assign'';
assign({}, {});

其实这种也是我们之前的使用方式,缺点就是需要去找到对应的库,比如 Promise 我们可以使用 lie

另一方面一旦有人没有按照这个规则,而直接使用了 Object.assign,那这个人就可能被削。

方案二:全局引入 babel-polyfill

在项目的程序入口

import ''babel-polyfill'';

babel 提供了这个 polyfill,有了它,你就可以尽情使用高级方法,包括 Object.values [].includes Set generator Promise 等等。其底层依赖的是 core-js 。

但是这种方案显然有些暴力, polyfill 构建并 uglify 后的大小为 98k,gzip 后为32.6k,32k 对与移动端还是有点大的。

性能与使用是否方便自己权衡,比如离线包后或也可以接受。

方案三:手动引入 core-js

这个方案也稍微有些麻烦, core-js 里实现了大部分 e6、es7 的高级语法,具体列表可以去这里查看 https://github.com/babel/babe...

我先截取一部分做下参考

  Object: {
      assign: "object/assign",
      create: "object/create",
      defineProperties: "object/define-properties",
      defineProperty: "object/define-property",
      entries: "object/entries",
      freeze: "object/freeze",
      ...
  }

具体怎么使用呢?找到要使用的方法的值,如:assign 是 "object/assign",将其拼接至一个固定路径。

import assign from ''core-js/library/fn/object/assign''

import ''core-js/fn/object/assign''

这里包含上述所说的局部使用和全局实现的两种

直接引入 ''core-js/fn/'' 下的即为全局实现,你可以在程序入口引入你想使用的,这样相对于方案二避免了多余的库的引入

引入 ''core-js/library/fn/'' 下的即为局部使用,和方案一一样,只是省去了自己去寻找类库。

但是,实际使用,import 要写辣么长的路径,还是感觉有些麻烦。

方案四:使用 babel-plugin-transform-runtime

本文会重点介绍下这个插件

先看下如何使用

// without options
{
  "plugins": ["transform-runtime"]
}

// with options
{
  "plugins": [
    ["transform-runtime", {
     "helpers": false, // defaults to true; v6.12.0 (2016-07-27) 新增;
      "polyfill": true, // defaults to true
      "regenerator": true, // defaults to true
      // v6.15.0 (2016-08-31) 新增
      // defaults to "babel-runtime"
      // 可以这样配置
      // moduleName: path.dirname(require.resolve(''babel-runtime/package''))
      "moduleName": "babel-runtime"
    }]
  ]
}

该插件会做三件事情

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 (Object.assign) and built-ins (Promise).

  • Removes the inline babel helpers and uses the module babel-runtime/helpers instead.

  • 第一件,如果你想使用 generator , 有两个办法,一个就是引入 bable-polyfill 这个大家伙儿,另一个就是使用这个插件,否则你会看到这个错误

    Uncaught ReferenceError: regeneratorRuntime is not defined
  • 第二件,就是能帮助我们解决一些高级语法的问题,它会在构建时帮你自动引入,用到什么引什么。

但是它的缺陷是它只能帮我们引入静态方法和一些内建模块,如 Object.assign Promise 等。实例方法是不会做转换的,如 "foobar".includes("foo") ,官方提示在这里:

NOTE: Instance methods such as "foobar".includes("foo") will not work since that would require modification of existing builtins (Use babel-polyfill for that).

翻译一下就是,不要越俎代庖,不是你的东西你别乱碰,欠儿欠儿的。

image

所以这个方案不会像方案二那样随心所欲的使用,但其实也基本够用了。

没有的实例方法可以采用方案三委屈下。

个人还是比较推荐这两种合体的方案。

需要注意的一点是:

开启 polyfill 后,会与 export * from ''xx'' 有冲突

请看构建后的代码:

...
/***/ },
/* 106 */
/***/ function(module, exports, __webpack_require__) {

    ''use strict'';
    // 这是什么鬼。
    import _Object$defineProperty from ''babel-runtime/core-js/object/define-property'';
    import _Object$keys from ''babel-runtime/core-js/object/keys'';
    Object.defineProperty(exports, "__esModule", {
      value: true
    });
    ...

截止 2016-09-10,官方尚未解决此 issue, 只有先避开 export * from ''xx'' 这种写法。或在这里找答案。

  • 第三件,是会引入一些 helper 来代替每次都生成的通用函数,看个例子就明白了

原来构建好的代码每个模块都有类似这种代码:

function _classCallCheck(instance, Constructor)...

function _possibleConstructorReturn(self, call)...

function _inherits(subClass, superClass)...

开启 helper 后:

var _classCallCheck2 = require(''babel-runtime/helpers/classCallCheck'');

var _possibleConstructorReturn2 = require(''babel-runtime/helpers/possibleConstructorReturn'');

var _inherits2 = require(''babel-runtime/helpers/inherits'');

这样统一引用了 helper,去处了冗余,看起来也更优雅了。

在 v6.12.0 之前 helper 也是默认开启的,没有配置可改,其他的 ployfill regenerator 都是有配置可以设置的。也许是推荐你使用 helper 。

但是 v6.12.0 (2016-07-27) 增加了 helper 的配置。为什么呢?

我最开始用这个插件的时候也很诧异,按道理来说,去除了冗余代码,代码的体积应该变小才对,但实际测试却变大了,我测试时是未经 uglify 的代码从 18k 增加到了 78k,查看构建模块增加了将近 100 个 详情。

原因是从 babel-runtime 里引入的 helper 依赖很多,全部都是兼容最底层的。比如 Object.create typeof 这种方法全部被重写了。

后来 gaearon 大神都忍不了了,他测试的结果是增加了 5kB min+gzip 详情。

于是有了 helper 这个配置项。

另外还有一点,如果开启了 helper 的话,你会发现之前引用的 babel-plugin-transform-proto-to-assign 就失效了,虽然他本来就不该被使用,后面会讲到。

所以目前看来这个 helper 不用也罢。

再说下 moduleName 这个参数是干什么的?

还记得开启 helper 后的代码吗

var _classCallCheck2 = require(''babel-runtime/helpers/classCallCheck'');

看下这个路径,如果是本地项目安装了 babel-runtime 是没问题的,但如果你是用的通用构建工具,比如 nowa,所有的构建依赖库都是在公共的地方,毕竟 babel 太太了。这里就会报错了。

Cannot resolve module babel-runtime/regenerator

gaearon 大神在写 create-react-app 时也发现了这个问题, 详情

虽然这个问题可以通过 webpack 的 resolve.root 来解决,但是 gaearon 大神看其不爽,觉得依赖 webpack 不够优雅,#3612 于是乎就有了 moduleName 这个参数,已发布 v6.15.0 (2016-08-31)。

放弃 loose 模式, 放弃 ie8

上篇中提到了开启了 loose 模式来解决低版本浏览器无法继承到在构造函数里定义的属性或方法。

我们是通过 babel-preset-es2015-ie 这个插件,主要是改写了 babel-plugin-transform-es2015-classes: {loose: true} 和添加了插件 babel-plugin-transform-proto-to-assign(解决类方法继承的问题)

babel-preset-es2015 v6.13.0 (2016-08-04) 时,presets 已经支持了参数配置,可以直接开启 loose 模式。

它内部会把开启一些插件的 loose 模式,不只是babel-plugin-transform-es2015-classes

{
  presets: [
    ["es2015", { "loose": true }]
  ]
}

这样我们就可以直接使用 babel-preset-es2015,至于 babel-plugin-transform-proto-to-assign 可以单独配置,也可不使用,因为类方法本来就不该被继承,要使用就直接 Parent.defaultProps 就可以了。

在上文中并没有提到开启 loose 模式的另一个原因是解决 ie8 下的两个 es3 属性名关键字的问题,因为上文测试均在 ie9 上,所以上述的方案也是停留在必须支持 ie8。

那么如果我们放弃了 ie8 ,看一看是不是会海阔天空。

babel-plugin-transform-es2015-classes v6.14.0 (2016-08-23) 一个 ‘大胡子哥’(原谅我不认识他) 修复了 __proto__ 这个问题 #3527 Fix class inheritance in IE <=10 without loose mode.
这样我们就可以在 ie9+ 上使用正常的 es6 模式了。

毕竟我们该向前看,loose 模式有点后退的赶脚。

这篇文章也表达了不推荐使用 loose 模式

Con: You risk getting problems later on, when you switch from transpiled ES6 to native ES6. That is rarely a risk worth taking.

当然,如果真的离不开 ie8,就针对 es3 关键字的问题引用两个插件即可

require(''babel-plugin-transform-es3-member-expression-literals''),
require(''babel-plugin-transform-es3-property-literals''),

我们再稍微看下‘大胡子哥’的修改,其实很简单,也很巧妙,看一行关键代码

// 修改后生成的代码多了一个 先取 `xxx.__proto__` 再使用 `Object.getPrototypeOf`
  var _this = _possibleConstructorReturn(this, (Test.__proto__ || Object.getPrototypeOf(Test)).call(this, props));

回顾下 inherits 方法的实现

function _inherits(subClass, superClass) {
    ...
    // 虽然 ie9/10 不支持 `__proto__`,这里只是作为了普通对象给予赋值,`Object.getPrototypeOf` 获取不到但可以直接 `.__proto__` 获取
  Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  ...

如果你看懂了实现方式,不知道你有没有发现 babel-plugin-transform-proto-to-assign(解决类方法继承的问题)这个家伙真的不能用了

function _inherits(subClass, superClass) { 
  ...
  // 因为它会将 `__proto__` 转为 `_default` 
  Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : _defaults(subClass, superClass);
}

这样上述的修复就无效了。切记不能使用,还是那句话,类方法本来就不该被继承。

最后看下终极方案的通用配置

{
  plugins: [
    ["transform-runtime", {
      "helpers": false,
      "polyfill": true,
      "regenerator": true
    }],
    ''add-module-exports'',
    ''transform-es3-member-expression-literals'',
    ''transform-es3-property-literals'',
  ],
  "presets": [
    ''react'',
    ''es2015'',
    ''stage-1''
  ],
}

更简单、完整的解决方案,请查看 nowa

感谢阅读。

参考链接

  • https://babeljs.io/docs/plugi...

  • https://github.com/babel/babel

广告时间: 请献出你的小星星

  • https://github.com/saltjs/sal...

  • https://github.com/uxcore/uxcore

ES6+React+Webpack初步构建项目流程

ES6+React+Webpack初步构建项目流程

当我们打算使用Webpack构建工具,React和ES6来开发项目的时候,构建这么一套自动化的项目的流程见下:

第一步:webpack是一个基于node的项目,我们使用命令行对webpack进行全局的安装

npm install webpack –g。可以通过webpack –h来查看安装的版本信息。然后我们新建一个文件夹用来存放整个项目文件。为了可以在项目中使用webpack,我们需要将webpack安装到当前的项目依赖中,在新建的文件夹下输入:npm init(安装webpack依赖,会在对应文件夹下生成package.json),然后使用npm install webpack –save-dev将webpack的模块添加到本地项目中。此时会生成一个本地的模块目录node_modules。如下图所示:


第二步:在新建的文件下目录下创建你的工程目录结构。为了演示react和es6,我这里需要展示一个背景色为橙色,只显示一段文字:hello!的工程。首先创建一个index.html,入口文件app.js,被引用的UI模块文件hello.js以及对应的样式文件style.css。目录结构如下所示:

第三步:这里定义的入口js文件app.js中可以使用es6中的import引用hello.js中的UI组件。同时也可以使用require来引入定义的style.css文件用来改变元素的样式属性。对应的在hello.js中可以使用react创建对应的功能模块的组件,并通过export导出接口给其他文件使用。



第四步:创建并使用webpack最重要的配置文件webpack.config.js。这个文件主要定义webpack构建工具识别的入口文件entry,出口文件output(这里的出口文件主要是用来被index.html中的script标签进行引用)以及webpack为了打包对应css,img,es6所需要的加载器loader。这里的webpack.config.js是webpack默认运行时加载的配置文件。


TIPS:这里为了加载使用各个loader,比如说css-loader,babel-loader,style-loader等等加载器,需要我们在开发项目之前进行安装。有两个安装方式:

A.可以直接在命令行输入对应安装命令:npm install style-loader –save-dev之类

B.直接在package.json中的devDependencies定义所需要使用的loader加载器名字,然后在命令行直接输入npm install进行对应loader的安装。
 
 
这里为了可以使用webpack的更具文件的改动直接自动刷新浏览器的功能,安装并使用了webpack-dev-server;注意在使用webpack-devserver的时候一定要在后面加上参数—hot和—inline才能达到自动刷新浏览器的目的。

关于React+Webpack+ES6 兼容低版本浏览器IE9解决方案的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于ES5 Object.assign 低版本浏览器内核兼容问题、ES6 + Webpack + React + Babel 如何在低版本浏览器上愉快的玩耍(上)、ES6 + Webpack + React + Babel 如何在低版本浏览器上愉快的玩耍(下)、ES6+React+Webpack初步构建项目流程的相关知识,请在本站寻找。

本文标签: