苦逼前端

理解babel

Javascript2017-11-16 20:21

最近有小伙伴发现某页面在iOS8微信环境下显示空白页,跑过来问我可能导致此问题的原因,因为这是个前端模板渲染的页面,所以首先想到的是js抛错了,找个iOS8来试试吧。

手里有了iOS8,也打开了这个不正常的页面,却发现有点手足无措,曾经各种远程调试真机的招数居然一个都想不起来了,于是翻到了这篇文章温习了下,mac插上数据线连接到手机,然后在mac的safari中就能调试到iOS里的页面了,如果是首次调试还需要在iOS设备上打开:设置→Safari→高级→web检查器。

发现确实是报错了:
TypeError:undefined is not a function(evaluating 'v.includes(":8080")')
应该是用来检查路由中的端口的,这里为什么会有一个字符串的高级API呢,经过和小伙伴确认,他确实是在代码里使用了includes方法,但它不是应该被编译环境转成浏览器识别的代码吗?我们先来了解下这个方法吧:includes

文档上写着:

This method has been added to the ECMAScript 2015 specification and may not be available in all JavaScript implementations yet.

iOS端仅支持到safari9,很不幸iOS8上运行的是safari8,所以就报了上面的错。那出现这个问题是谁的锅呢?为什么编译工具没有去转换这个方法呢,编译工具对方法的转化是依据着什么样的规则呢?带着这几个问题,我们去测试下以下两大主流框架的主流构建环境。

我们发现create-react-appvue-cli生成的项目模版默认都没有对该方法进行polyfill。并且前者的官方github还有个关于此事的讨论,最终结果是官方不予默认支持。

要解决这个问题也不难,在自己的代码中引入官方提供的polyfill就可以了:

if (!String.prototype.includes) {
    String.prototype.includes = function(search, start) {
        'use strict';
        if (typeof start !== 'number') {
            start = 0;
        }
        if (start + search.length > this.length) {
            return false;
        } else {
            return this.indexOf(search, start) !== -1;
        }
    };
}

但我们怎么才能通过webpack/babel实现编译支持,而非自己去管理这部分代码呢。看了babel的官方文档,才发现原来自己一直对babel的理解也是有偏差的,以为配置了类似es2015这种,就会支持它的所有特性,包括语法和API,但人家明确说了只支持语法转换,如果想要支持所有的API还需要在代码中引入babel-polyfill:

Since Babel only transforms syntax (like arrow functions), you can use babel-polyfill in order to support new globals such as Promise or new native methods like String.padStart (left-pad).

但这个babel-polyfill好像口碑没那么好,污染全局变量、压缩后80k、不能按依赖引入,所以大家一致推荐使用babel-runtime,但请注意它并不能模拟实例方法,即内置对象原型上的方法,如String.prototype.includesArray.prototype.find等,只能模拟类似Object.assign等非内置对象原型上的方法。

虽然babel-runtime使用起来比较麻烦,但它能按模块注入:

//比如要使用Promise
import Promise from 'babel-runtime/core-js/promise';

//注入山寨includes
import includes from 'babel-runtime/core-js/string/includes';
const abc = 'abc';
includes(abc, 'a');//true

但这个babel-runtime还有更高级的用法,结合webpack,就能在编译阶段搞定这些API了,与之对应的编译插件叫做: babel-plugin-transform-runtime
我们在.babelrc中开启:

{
    "plugins": ["transform-runtime"]
}

这样除了上文提到的内置对象原型上的方法以外的所有语法和API,我们就都能愉快的使用了。如果一定要使用这些方法那只能在开发代码中引入babel-polyfill:

//此方法会引入整个babel-polyfill,不推荐这么使用
import 'babel-polyfill';
const abc = 'abc';
abc.includes('a');//true

//此方法是按需引入babel-polyfill,但依赖的模块是core-js
import 'core-js/modules/es6.string.includes';
const bcd = 'bcd';
bcd.includes('b');//true

所以,综上所述,
babel-runtime和babel-polyfill都是需要直接在开发代码中当作模块来引入使用的。 babel-plugin-transform-runtime是个编译工具,经过它编译的代码可以认为是直接注入了babel-runtime的API(前提是开启了helper和polyfill,这两者都是默认开启的),但是想要兼容类似String.prototype.includes的方法还需要手动在代码中引入babel-polyfill。

我的小伙伴就是使用了babel-plugin-transform-runtime这个编译插件,但是并没有在代码中引入babel-polyfill,导致了低版本浏览器不兼容String.prototype.includes而报错。

评论(3)
  • 111.202.148.*: 先插一句,我就是那个小伙伴😂2年10个月前
  • 61.140.94.*: 学习了~感谢分享1年3个月前
  • 111.200.23.*: 牛批1年2个月前
还可输入200个字