polyfill 在babel7时代何去何从

1,895 阅读6分钟

一、前言

  • babel 的核心功能是转译代码(新语法,如箭头函数,类等)和polyfill能力(新内置对象,api等),本文主要讨论polyfill能力。

  • babel7.4之后,官方已经废弃@babel/polyfill 。@babel/runtime 在7.0.0后也去除了polyfill能力的core-js。polyfill作为前端垫片,铺平各浏览器差异的类库,前端开发者该何去何从,如何选择呢

二、babel-polyfill 、babel-runtime、 babel/preset-env

1、babel-polyfill

它的初衷是模拟(emulate)一整套 ES2015+ 运行时环境,所以它的确会以全局变量的形式 polyfill Map、Set、Promise 之类的类型,也的确会以类似 Array.prototype.includes() 的方式去注入污染原型

优点:

1、解决了Babel不转换新api的问题,能解决runtime无法转换内置对象新的实例方法。

缺点:

1、在代码中插入帮助函数,污染了全局环境。引入新的全局对象,修改了原型方法。

2、不同文件中存在重复代码,,编译后体积较大。

举个例子, 我在项目中定义了跟规范不一致的Array.from()函数,同时引入了一个库(依赖babel-polyfill),此时,这个库可能覆盖了自定义的Array.from()函数,导致出错。 这就是babel-runtime存在的原因。它将开发者依赖的全局内置对象等,抽取成单独的模块,并通过模块导入的方式引入,避免了对全局作用域的修改(污染)。

 const obj = {}
 Object.assign(obj, { age: 30 });
 
//转译后

'use strict';
// 使用了 core-js 提供的 assign
var _assign = require('babel-runtime/core-js/object/assign');   
var _assign2 = _interopRequireDefault(_assign);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var obj = {};
(0, _assign2.default)(obj, {
  age: 30
});

2、babel-runtime

此处首先说明的是babel-runtime(babel6) 而不是@babel/runtime(babel7) 在babel 6时代的runtime 包含 工具方法(helper)和polyfill功能core-js,可以通过配置babel-plugin-transform-runtime的属性来决定polyfill能力。

 plugins: [
        [
            "transform-runtime", //babel-plugin-transform-runtime简写
            {
                "absoluteRuntime": false,
                "corejs": 2,
                "helpers": true,
                "regenerator": true,
                "useESModules": false,
                "version": "7.0.0-beta.0"
            }
        ]
    ]

babel进入7.0.0后,@babel/runtime的core-js被移除,不在支持polyfill的作用,因此只能提供编译的一些工具方法。垫片能力被放到 @babel/preset-env的 useBuiltIns完成。下文讨论。

本节主要讨论6时代的polyfill的优缺点。

优点:

1)避免了babel-polyfill全局污染

2)结合babel-plugin-transform-runtime的ast分析,实现了内置对象,的静态方法的按需加载。

缺点:

只支持到static方法,全局内置对象等。

babel-polyfill与babel-runtime相比虽然有各种缺点,但在某些情况下仍然不能被babel-runtime替代, 例如, [1, 2, 3].includes(3),Array,Object以及其他”实例”下es6的方法,babel-runtime是无法支持的, 因为babel-runtime只支持到 static 的方法,对于应用项目还需要结合babel-prolyfill使用。 而一些工具库,可以用runtime处理,使用方统一polyfill。

基于此,@babel/preset-env的 useBuiltIns方式 在babel7应用中便诞生了。他继承了babel-runtime不污染全局的特征,并且完善了runtime对原型方法支持不足的缺点。因此是目前应用的主要配置方式。

3、 @babel/preset-env之 useBuiltIns

useBuiltIns影响 编译过程中的polyfill处理,不影响babel调用的插件

// false:
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": false,  //布尔类型,
                "corejs": 3,
                "debug": true,
                "modules": false 
            }
        ]
    ]

测试结果 babel 不做polyfill转化处理。(不管有没有引入core-js,都不会做polyfill处理) webpack打包依赖import引入,完全按import来

// entry
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "entry",
                "corejs": 3,
                "debug": true,
                "modules": false 
            }
        ]
    ]

需要配合引入

import "core-js";  // 引入es,esnext,以及一些web处理的polyfill
import  "core-js/stable"/ /稳定的polyfill
import   "core-js/proposals"  //提案阶段的polyfill
import  "core-js/stage/4.js"  // 指定提案级别高的的polyfill
import "regenerator-runtime/runtime"

若不引入core,则不会polyfill 测试结果 Replaced core-js entries with the following polyfills babel会根据import 进行引入polyfill。 优点:在不考虑包大小情况下,引入简单。 缺点: 1)若引入core-js 会引入所有的polyfill,大部分的polyfill都是无用的,包的体积也大大增加了 2)若指定引入细分,则要求开发则对调用的函数,core-js本身要够熟悉,学习成本大,引入难度大。

    // usage
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "usage",
                "corejs": 3,
                "debug": true,
                "modules": false 
            }
        ]
    ]

不引入core.js

测试结果: 1、有条件的按需加载(es),esnext不会自动加载,因此使用时一定要注意是不是esnext级别的提案。,

import "core-js/modules/esnext.math.radians.js"; import "core-js/modules/esnext.string.replace-all"; 可以通过node_modules/core-js/modules中查看是不是esnext

2、引不引入corejs,babel结果是一样。

3、esnext手动引入会被webpack进来

三、core2js 、core3js、 es 、esnext

在不传corejs 版本时,corejs默认按core2js获取文件,若安装3.0 ,则会报错,因为core2js、和core3js的文件结构已经发生变化。

    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "usage",
                 // "corejs": 2,
                "debug": true,
                "modules": false 
            }
        ]
    ]

core2js 文件结构

aa

core3js 文件结构

aa

1、 corejs2 es6后提案功能被统一放到core-js/es7中。 目前corejs2,开发分支已经锁死,新特征不再更新完善,所有新提案在core3js上提交。 corejs3 新提案在 core-js/modules/esnext中实现。当 "useBuiltIns": "usage"时,esnext中的方法不会被polyfill,只能手动import。支持ECMAScript稳定功能,引入core-js@3冻结期间的新功能,比如flat

2、加入到ES2016-ES2019中的提案,现在已经被标记为稳定功能

3、更新了提案的实现,增加了proposals配置项,由于提案阶段不稳定,需要谨慎使用

4、增加了对一些web标准的支持,比如URL 和 URLSearchParams

5、现在支持原型方法,同时不污染原型

6、删除了过时的特性

四、总结

@babel/polyfill 由于其污染性,引入不灵活等弊端,babel7.4后已经被官方deprecated。babel-runtime由于其不支持原型方法,还要结合polyfill使用, babel-runtime 在7.0.0后也移除了其polyfill的core-js 。babel7通过预制env 的useBuiltIns配置来实现polyfill。继承了runtime的有点。弥补了原型方法支持不足的问题。

presets: [
  ["@babel/preset-env", {
    useBuiltIns: "entry",
    corejs: 3,
  }]
]

import "core-js/stable";
import "regenerator-runtime/runtime";

通过corejs 引入后,实现了无污染的polyfill,使用 useBuiltIns: "usage" 可以实现按需加载。

1、脚本

  "scripts": {
    "clean": "rimraf dist",
    "build": "rimraf dist && npx webpack",
    "babel": "rimraf babel_result && npx babel src -d babel_result",
    "node": "node dist/index.js",
    "start": "webpack-dev-server --inline --progress --config webpack.config.js"
  },

npm run babel 查看babel 的结果,
"debug": true可以看到babel 构建过程。

  • 2、@bebel/*** 类库说明
 @babel/runtime  //单独的包babel-runtime用以提供编译模块的工具函数,新版本功能被削弱
 @babel/plugin-transform-runtime //合并runtime的调用
 @babel/preset-env  //bebel7 新预制env
 @babel/core   //babel内核
 core-js  //polyfill api的差异
 regenerator-runtime/runtime  // async await  yeild iterator等
 @babel/polyfill  //core-js 和     regenerator-runtime/runtime