苦逼前端

babel-preset-env前端狗的又一个福音

Javascript2017-12-20 20:28

之前的文章提到我的小伙伴踩中了babel的坑,当时只是找到了问题的原因,然后给了一个比较潦草的解决方法:直接引入低版本浏览器不兼容的API。但是在实际开发中我们不可能熟知各个浏览器对API的兼容情况,导致只能报错之后再去补救,显然这不是个优秀的方案,下面我们就探讨一下如何优雅的避免此类问题的发生。

先尝试使用vue-cli构建项目,我们发现它的初始模版中同时配置了babel-preset-envbabel-plugin-transform-runtime

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": ["transform-vue-jsx", "transform-runtime"],
  "env": {
    "test": {
      "presets": ["env", "stage-2"],
      "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
    }
  }
}

由于babel-preset-env并没有开启useBuiltIns,且模版代码中也没有引入polyfill,导致不太熟悉babel环境的同学直接拿来用会踩到坑

我们尝试在main.js中加入以下代码:

const test = 'abcd';
alert(test.includes('a'));

编译后在IE9打开果然是报错了: SCRIPT438: 对象不支持“includes”属性或方法
修改为以下代码才能正常执行:

//手动添加polyfill
import 'core-js/modules/es6.string.includes'
const test = 'abcd';
alert(test.includes('a'));

但是这么做就会遇到上面所说的问题,我们不可能熟知每一个API的兼容情况,导致有遗漏或者多余。有没有什么插件会根据配置好的对浏览器的支持程度来自动做polyfill呢。

babel-preset-env就是干这件事情的,比如它会根据如下配置来对「大部分浏览器最新的两个版本以及safari 7+」进行polyfill,包括语法和API:

{
  "presets": [
    ["env", {
      "targets": {
        "browsers": ["last 2 versions", "safari >= 7"]
      }
      "useBuiltIns": true
    }]
  ]
}

这样就解决了上面提到的问题,我们不需要关心每个API在各个浏览器的兼容情况,我们只需要知道要兼容哪些浏览器就可以了。

既然如此,为什么vue-cli生成的模版还同时引入了babel-plugin-transform-runtime呢?恰巧我在segmentfault看到了相同的问题。

经过查证发现,原因是babel-preset-env@1.x没法很好地消除未使用的polyfill(就是说有未使用的代码被引入进来了)。如果希望避免这一点,就会禁用useBuiltIns: true,用更好的transform-runtime代替。

详情可见:
babel/babel-preset-env#84
babel/babel-preset-env#241

可以看到 vuejs-templates/webpack/ 引入的是 1.3babel-preset-env

我们还可以去 Bebel REPL 尝试一下:
1.开启左侧菜单下方Env Preset
2.输入浏览器版本
3.勾选Built-ins

然后左侧代码栏输入:

import babel-polyfill;

就会发现右侧会根据输入的浏览器版本生成与之对应的依赖注入代码,不管有没有用到,只要是当前浏览器环境不支持的,全部都会注入:

//我输入的浏览器版本是chrome 52
"use strict";
require("core-js/modules/es7.object.values");
require("core-js/modules/es7.object.entries");
require("core-js/modules/es7.object.get-own-property-descriptors");
require("core-js/modules/es7.string.pad-start");
require("core-js/modules/es7.string.pad-end");
require("core-js/modules/web.timers");
require("core-js/modules/web.immediate");
require("core-js/modules/web.dom.iterable");

而在babel-preset-env@2.x中已经完全可以用useBuiltIns: usage来达到按需引入的目的,也就是说不再需要transform-runtime了,并且也不再需要在代码中手动引入babel-polyfill了(但是还需要安装,因为编译后的代码依赖了它),显然这是一个很优秀的方案:

编译前:

var a = new Promise();
var b = new Map();

编译后(浏览器环境不支持Promise和Map):

import "core-js/modules/es6.promise";
import "core-js/modules/es6.map";
var a = new Promise();
var b = new Map();

编译后(浏览器环境不支持Map):

import "core-js/modules/es6.map";
var a = new Promise();
var b = new Map();

相关版本:
vue-cli: 2.9.3
nodejs: v6.1.0

评论(1)
  • luckymore: 完美!!!!2年9个月前
还可输入200个字