一文读懂babel-loader、babel-polyfill、babel-transform-runtime的区别和联系

4,529 阅读3分钟

在我们的项目中,如果使用了es6的语法和API时就要用到babel对这些语法进行转化,使代码可以在低版本的浏览器上正常运行。

假如有一段es6代码(main.js):

async function f() {
  return await 123;
}
f().then(v => console.log(v))

console.log(Object.values({ 1: 2 }));
console.log(Array.isArray([]));
console.log([1, 2, 3].includes(3));

package.json

{
  "name": "babel-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack --config webpack.config.js",
    "dev": "webpack-dev-server --open"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.4.5",
    "@babel/plugin-transform-runtime": "^7.4.4",
    "@babel/preset-env": "^7.4.5",
    "babel-core": "^7.0.0-bridge.0",
    "babel-jest": "^24.8.0",
    "babel-loader": "^7.1.4",
    "css-loader": "^1.0.1",
    "regenerator-runtime": "^0.13.2",
    "webpack": "^4.34.0",
    "webpack-cli": "^3.3.4"
  },
  "dependencies": {
    "@babel/polyfill": "^7.4.4",
    "@babel/runtime": "^7.4.5",
    "@babel/runtime-corejs2": "^7.4.5"
  }
}

webpack4的配置:

const path = require('path');
module.exports = {
  mode: "development",
  devtool: "inline-cheap-module-source-map",
  entry: {
    app: ['./main.js']
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './dist'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader']
      }
    ]
  }
};

注释掉rules,打包后在IE浏览器运行报错

释放掉rules,增加.babelrc文件,配置如下:

{
	"presets": [["@babel/preset-env"]],
}

因为单独使用babel-loader是不能对es6的语法进行解析的,需要配合babel-preset-env。 继续打包运行

依然报错,但是报错信息发生了变化,因为babel-loader只会对es6的语法进行解析,例如箭头函数。如果要解析es6的API方法就要引入babel-polyfill或者babel-runtime。

babel-polyfill

模拟一个完整的es2015+环境,用于应用程序的开发而不是库文件,可以使用Promise之类的新的内置组件和Array.from和Object.assign之类的静态方法和 Array.prototype.includes等实例方法,polyfill将添加到全局范围和本地原型中,因此会污染全局变量。

引入babel-polyfill的四种方式:

  1. 直接在main.js顶部使用import "@babel/polyfill"
  2. 设置.babelrc
{
	"presets": [["@babel/preset-env", {"useBuiltIns": "entry", "corejs": 2}]],
}

并在main.js顶部使用import "@babel/polyfill"

  1. 设置.babelrc
{
	"presets": [["@babel/preset-env", {"useBuiltIns": false}]],
}

在webpack.config.js中入口配置:

entry: {
    app: ['@babel/polyfill', './main.js']
},

4.设置.babelrc,无需引用@babel/polyfill

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

该配置会自动加载项目中需要的polyfill,而不是全部引入。

打包之后bundle.js大小的比较:

打包之前:

使用前三种引用方式:

使用第四种引用方式:

可见前三种属于全部引用,第四种属于按需引用。当然也可以从安装包里找到所需的垫片手动导入。

babel-runtime

项目中安装依赖包:

npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime

babel-runtime为生产依赖,transform-runtime为开发依赖,从这里即可看出,babel-runtime中包含了所有的核心帮助函数。使用babel-runtime的两种方式

  1. 在项目文件中手动引入所需要的帮助函数,例如
import Promise from "babel-runtime/core-js/promise";
let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

项目中只使用了Promise,所以只需要引入Promise的帮助函数即可。查看打包之后的bundle.js,可以看到Promise被重写,而不是和babel-polyfill一样放在global上。但是当项目文件比较多,使用的es6 API也比较多时,手动引入就变的比较繁琐。这时就需要transform-runtime插件了,该插件会分析项目代码,自动的引入所需的垫片APIs。使用方法:在.babelrc中配置:

{
    "plugins": [["@babel/plugin-transform-runtime", {"corejs": 2}]]
}

并安装依赖:

npm install --save @babel/runtime-corejs2

去掉手动引入的core-js/promise文件,打包。可以看到打包完之后的文件和手动引入的文件大小相同,并且都可以正常在IE中运行。transform-runtime的配置参数corejs默认值为false,假定用户将引入所有需要使用的帮助文件,所以为了达到自动引入的效果需要手动的改为2.否则不会解析es6的代码。

babel-runtime为你的代码提供了一个沙盒环境,所以不会像babel-polyfill一样污染全局变量,因此适用于开发组件库。但是babel-runtime不能模拟实例方法,即内置对象原型上的方法,例如Array.prototype.concat。

但是如果babel版本>=7.4.0,设置corejs: 3。同样安装依赖

npm install --save @babel/runtime-corejs3

在IE中[1, 2, 3].includes(3)方法可以正常执行。可见core: 3在core: 2的基础上解决了babel-runtime不能解析实例方法的弱点。