[Babel]Babel学习手记

2,163 阅读15分钟

一、Babel相关的概念

  • @babel/core: babel的核心,核心的api都在包含在这里。
  • @babel/cli: 命令行工具,通过命令对js文件进行转换的工具。
  • @babel/perset-env: 指定转换的工作环境。
  • @babel/polyfill: 相当于一个填充,因为babel本身只支持转换箭头函数、结构赋值这些语法糖类的语法,而一些新的API或者Promise函数等是无法转换的。@babel/polyfill就是解决这个问题的。
  • babel-loader: webpack的加载器,用于调用@babel/core的核心API来完成编译。

1、@babel/core与babel-core区别

@babel/core是babel 7过后的版本标识,babel-core是以前版本的标识。


2、.babelrc和babel.config.js

.babelrc和babel.config.js均是babel的配置文件,babel.config.js是bebel 7引入的新的方式。


3、@babel/polyfill和@babel/plugin-transform-runtime和@babel/runtime和@babel/runtime-corejs2(都是用来转换新Api的)

  • (1)@babel/polyfill:babel-polyfill则是通过改写全局prototype的方式实现,比较适合单独运行的项目。开启babel-polyfill的方式,可以直接在代码中require,或者在webpack的entry中添加,也可以在babel的env中设置useBuildins为true来开启,但是babel-polyfill会有近100K,打包后代码冗余量比较大,对于现代的浏览器,有些不需要polyfill,造成流量浪费污染了全局对象。
//使用方法一在entry中添加
module.exports = {
    entry: {
        main: ["@babel/polyfill", path.resolve(__dirname, "./src/index.js")]
    }
}
//使用方式二babel.config.js中设置
module.exports = {
    presets: [
        "@babel/preset-env", { "useBuiltIns": true }
    ]
}

  • (2)@babel/runtime和@babel/plugin-transform-runtime:babel-runtime和 babel-plugin-transform-runtime的区别是,相当一前者是手动挡而后者是自动挡,每当要转译一个api时都要手动加上require('babel-runtime'),而babel-plugin-transform-runtime会由工具自动添加,主要的功能是为api提供沙箱的垫片方案,不会污染全局的api,因此适合用在第三方的开发产品中。@babel/runtime和@babel/plugin-transform-runtime都要结合到使用

  • (3)@babel/runtime-corejs2:plugin-transform-runtime 可以设置成 false 或者 2,在babel.config.js的配置文件下有以下代码:
module.exports = {
    preset: ["@babel/preset-env"],
    plugins: [
        "@babel/plugin-transform-runtime", { corejs: 2 }
    ]
}

1、corejs 是一个给低版本的浏览器提供接口的库,如 Promise, map, set 等。在 babel 中你设置成 false 或者不设置,就是引入的是 corejs 中的库,而且在全局中引入,也就是说侵入了全局的变量。

2、如果你的全局有一个引入,不要让引入的库影响全局,那你就需要引把 corejs 设置成 2。 所以一旦你使用了2这个参数就必须引入@babel/runtime-corejs2

3、@babel/plugin-transform-runtime是必须装的,如果corejs设置为2的话安装@babel/runtime-corejs2来代替@babel/runtime,反正设置为false的话就需要@babel/runtime,根据你的设置来安装即可。

4、所以@babel/runtime和@babel/runtime-corejs2区别就是后面那个多了个corejs2的包。与@babel/runtime区别,官网是这样描述的Difference from @babel/runtime:This can be used instead of a polyfill for any non-instance methods. It will replace things like Promise or Symbol with the library functions in core-js


在babel 7下:

  • babel.config.js 是对整个项目(父子package) 都生效的配置,但要注意babel的执行工作目录。
  • .babelrc 是对 待编译文件 生效的配置,子package若想加载.babelrc是需要babel配置babelrcRoots才可以(父package自身的babelrc是默认可用的)。
  • 任何package中的babelrc寻找策略是: 只会向上寻找到本包的 package.json 那一级。
  • node_modules下面的模块一般都是编译好的,请剔除掉对他们的编译。如有需要,可以把个例加到 babelrcRoots 中。

默认情况下.babelrc不作用于子包,那么在babel.config.js下加入一下babelrcRoots来指定即可。

    module.exports = {
      babelrcRoots: ['.', './frontend', './backend'] // 允许这两个子 package 加载 babelrc 相对配置
    }

一文读懂 babel7 的配置文件加载逻辑
对babel-transform-runtime,babel-polyfill的一些理解
babel7中 corejs 和 corejs2 的区别
babel preset env配置
babel学习笔记

二、Babel配置入门指南

首先需要本机安装node.js,使用npm包管理工具来初始化目录,本次操作学习都是在Babel 7上进行的,相较于Babel 6还是有一定区别,单独使用Babel 7需要CLI来完成,所以先安装脚手架和核心@babel/core。

npm install @babel/cli @babel/core

安装完成后,我们就可以使用cli提供的命令来转换我们的JS代码了,比如建立一个test.js的JS文件,里面包含ES6的内容:

let a = [1, 2, 3];

let b = () => {
	console.log('这是箭头函数!');
}

let c = [...a];

然后来一波命令:

npx babel test.js --watch --out-file test-transform.js
  • 注意:npx是npm在5.2.0后附赠的东西,使用这个我们可以避免全局安装这些命令工具,具体大家可以百度。
  • 我的test.js是直接建在跟package.json同级目录下的。 之后我们可以发现输出的test-transform.js没改变,没有转化,这是因为babel是基于插件来实现的,没配置插件肯定什么也不会干,所以需要配置,这里就不介绍单独手动引入插件的方式了。
// 转换前
let a = [1, 2, 3];

let b = () => {
	console.log('这是箭头函数!');
}

let c = [...a];

// 转换后
let a = [1, 2, 3];

let b = () => {
	console.log('这是箭头函数!');
}

let c = [...a];

不想每次需要什么插件都去手动引入插件,所以我们就需要祭出@babel/preset-env,先安装这个工具。

npm intasll @babel/preset-env

然后在根目录建一个babel.config.js来配置babel,在文件中加入以下内容:

module.exports = {
	presets: [
		["@babel/preset-env"]
	]
}

之后我们再运行一次上面的转换命令,可以得到以下代码:

"use strict";

var a = [1, 2, 3];

var b = function b() {
  console.log('这是箭头函数!');
};

var c = [].concat(a);

这样ES6新的语法糖就转换成ES5了。 当然,babel.config.js里面还可以指定目标,当满足什么样的条件才去转换语法,不指定targets的情况下,默认是把所有的ES6+都转换成ES5,比如下面的示例:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8"
    }]
  ]
}

这种只有当在大于ie 8以上的浏览器不支持的语法才会转换。

@babel/polyfill

babel只能转换一般的语法糖,不能转换新的API,所以就只能祭出polyfill来弥补。首先安装下polyfill,然后引入就可以了。

npm install @babel/polyfill

然后在JS文件里面引入

import "@babel/polyfill";

对于使用webpack的同学,可以直接在main入口直接引入,打包过后直接可以使用,如下:

entry: {
  main: ["@babel/polyfill", path.resolve(__dirname, "../main.js")]
}
  • 注意:这个地方我只是举个栗子,实际上你这样单独操作出来的JS再直接引入某个HTML文件里面其实是没有用的,要结合到打包工具一起使用才行,比如Webpack。

@babel/preset-env 加强

上面的引入方法是完全引入,导致包非常大,我们可以按需引入,这里又要配置@babel/preset-env,修改babel.config.js代码如下:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
      "useBulitIns": "entry"
    }]
  ]
}

然后运行一下会看到这些代码:

"use strict";

require("core-js/modules/es6.array.copy-within");

require("core-js/modules/es6.array.every");

require("core-js/modules/es6.array.fill");
..........
require("core-js/modules/web.timers");

require("core-js/modules/web.immediate");

require("core-js/modules/web.dom.iterable");

require("regenerator-runtime/runtime");

var a = [1, 2, 3];

var b = function b() {
  console.log('这是箭头函数!');
};

var c = [].concat(a);
b();
var d = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("ok");
  }, 2000);
});
d.then(function (res) {
  console.log(res);
});

它会一股脑的把所有的包全部引进来,这样嘿不科学,所以usage参数可以做到按需引入,它只会引入相关的包,没使用的ES6+API不会引入相关的包,修改babel.config.js代码:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
      "useBuiltIns": "usage"
    }]
  ]
}

转换过后的代码如下:

"use strict";

require("core-js/modules/es6.promise");

require("core-js/modules/es6.object.to-string");

var a = [1, 2, 3];

var b = function b() {
  console.log('这是箭头函数!');
};

var c = [].concat(a);
b();
var d = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("ok");
  }, 2000);
});
d.then(function (res) {
  console.log(res);
});

这样,我们可以看到,我的代码使用了promise,它就只引入promise相关的代码。

  • 注意:这儿使用usage参数后,JS文件里面就不要单独写import "@babel/polyfill"这句话了,多此一举。

到这儿,问题来了,在使用usage参数后使用babel的cli命令工具(也就是这个:npx babel test.js --watch --out-file test-done.js)会给一段提示,告诉我们要指定core-js@2或者core-js@3,从babel 7.4过后官方就建议这么做了,就是让我们放弃@babel/polyfill,我先卸载掉@babel/polyfill试一试,发现还是能转换,不科学,然后查资料和看preset-env文件夹下才发现,人家自带了polyfill,所以这儿如果有了@babel/preset-env不需要单独安装@babel/polyfill了,前提应该是使用了@babel/preset-env配置了babel才行(像前面的完全引入polyfill还是需要单独安装@babel/polyfill),(注意:这儿我试了使用webpack结合babel-loader在不单独安装@babel/polyfill或者core-js这些打包会出问题,"useBuiltIns": "usage"时提示找不到包,使用entry参数打包不报错,直接不会导入包,所以环境不一样不能一概而论)前面有个要我们指定corejs的提示,也只需要在babel.config.js里面指定一下就可以了,这儿我的@babel/perset-env版本是7.6.3,不知道其它版本有没有集成polyfill,babel.config.js代码如下:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
      "useBuiltIns": "usage",
      "corejs": 2
    }]
  ]
}

这儿我测试了下指定corejs为2和3时引入的代码不一样,暂时没了解到原因-_-。

@babel/runtime、 @babel/plugin-transform-runtime

接下来走一波不污染全局的配置方式,前面的配置都是要污染全局,还是不怎么科学,关于这种方式的优点和缺点大家上网查一下就可以了,接下来先安装好包:

npm install @babel/plugin-transform-runtime -D

上面这个安装到dev依赖就可以了,不用于生产环境

npm install @babel/runtime

接下来我们配置一下babel.config.js,如下:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
    }]
  ],
  plugins: [
    ["@babel/plugin-transform-runtime"]
  ]
}

然后输出一下

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var a = [1, 2, 3];

var b = function b() {
  console.log('这是箭头函数!');
};

var c = [].concat(a);
b();
var d = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("ok");
  }, 2000);
});
d.then(function (res) {
  console.log(res);
});

var MM = function MM() {  // 这个是我转换前用class定义的类 class MM {constructor(){}} 这种
  (0, _classCallCheck2["default"])(this, MM);
};

发现输出的代码里面没得polyfill了。如果需要polyfill的话就要单独安装以下内容:

npm install @babel/runtime-corejs2

这里安装这个包之前把我@babel/runtime给卸载了,安装完成后再重新配置一下babel.config.js,如下:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
    }]
  ],
  plugins: [
    ["@babel/plugin-transform-runtime", {
      "corejs": 2
    }]
  ]
}

然后运行一下,看结果。相比前面的@babel/runtime,这里的polyfill回来了,多了一句这个

var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));

参考资料

babel-runtime VS babel-polyfill
结合Babel 7.4.0 谈一下Babel-runtime 和 Babel-polyfill

20191107更新

三、结合到webpack来使用

结合webpack的话,还需要以下几个东西:

npm install webpack -D
npm install webpack-cli -D
npm install babel-loader -D

babel-loader是加载器,要结合webpack来使用的话,这个必须要,同时,在项目根目录建立一个webpack.config.js配置文件(这里webpack的知识就不介绍了,详情参考官网)。

还是借助@babel/preset-env

首先我还是先测试了参数为entry的情况,配置文件内容如下(注意:这儿我还安装了@babel/polyfill):

// index.js,是我要转换的文件
import '@babel/polyfill';

let a = `hello, can you hear me!`;

let func = () => {
    console.log('这是箭头函数');
}

let b = [1, 2, 3];
let c = [...b];

let array = [1];
 
 
console.log(array);

let promise_t = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(233);
    }, 2000)
});

promise_t.then((res) => {
    console.log(res);
})

class Test {
    constructor(name) {
        this.name = name;
    }

    print() {
        console.log(this.name);
    }
}

let t_t = new Test('xiaohong');
t_t.print();

然后是webpack.config.js文件

const path = require('path');

module.exports = {
    // 指定模式(这个指定可以用来区分开发模式和生产模式,可选值有:'devvlopment','production','none')
    mode: 'none',
    // 入口
    entry: {
        main: path.resolve(__dirname, './src/index.js')
    },

    // 出口
    output: {
        // 出口文件
        path: path.resolve(__dirname, './dist'),
        // 文件名
        filename: './[name].js',
    },

    // 加载器
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'babel-loader'
                    }
                ]
            }
        ]
    }
}

之后再是babel.config.js文件

module.exports = {
    presets: [
        ['@babel/preset-env', {
            'useBuiltIns': 'entry',
        }]
    ]
}

这儿我发现,即使我的babel.config.js不配置'useBuiltIns': 'entry',这句,打包出来的main.js虽然内容有差别,但是还是能运行成功,IE11亲测可用上面js的promise语法。暂时我还没把useBuiltIns这个属性搞的特别清楚,只是知道在使用usage属性值时可以按需加载polyfill。 接下来使用'useBuiltIns': 'usage'来试一试,我发现在保持其他文件不变的情况下,(这儿有个问题是,使用usage时在index.js中还是要写这句import '@babel/polyfill',不然找不到包,这与我上面单独使用babel来转换时说不在单独引入polyfill有点出入,还没怎么明白,后期我会补充)。然后我比较了两种方式生成的main.js的代码,使用entry属性值时,main.js代码八千多行,大小大概250kb,使用usage参数时,代码一千多行,大小大概37kb,所以按需引入效果还是明显。而且亲测在IE11上两种方式生成的代码均可正常运行。

使用core-js代替@babel/polyfill

官方网站上面说从babel 7.4.0后开始放弃babel/polyfill,转而使用core-js。这儿我们又来试一试core-js 首先卸载@babel/polyfill,然后执行以下命令安装core-js@2:

npm install core-js@2 -S

这儿还有个core-js@3的版本,暂时没有用过,不知道区别。安装完成后修改一下babel.config.js

module.exports = {
    presets: [
        ['@babel/preset-env', {
            'useBuiltIns': 'usage',
            'corejs': 2
        }]
    ]
}

然后你的index.js的上面去掉import '@babel/polyfill',这时执行过后的main.js能完美运行在IE11上,而且这儿也不需要手动引入core-js@2,使用usage属性值时自动按需引入,比较稳。如果我不写'corejs': 2这个参数,转换也可以成功,但是会报warning,叫我去安装core-js并制定版本,问题不大,还是写上,毕竟官方要求。 然后如果我直接把useBuiltIns的参数值改为entry,发现class这些es6语法会转换,但是Promise的polyfill没有引入,看来没usage参数好使,需要手动引入才行,我在index.js手动引入import 'core-js', 然后执行webpack但是却报错,提示Module not found: Error: Can't resolve 'regenerator-runtime/runtime',这个就尴尬了,还要安装regenerator-runtime/runtime,但是使用usage却没有出现这个问题,暂时无解,这儿我单独安装一次:

npm install regenerator-runtime -D

这儿我单独安装一次regenerator-runtime就不会报错了,暂时没找到原因,试着在安装了regenerator-runtime的情况下使用usage参数值,同时去除index.js里面的import 'core-js'这句话,不然会报警告。内容像这样:When setting useBuiltIns: 'usage', polyfills are automatically imported when needed. Please remove the import '@babel/polyfill' call or use useBuiltIns: 'entry' instead.。我试了下,使用webpack打包没得问题,可以运行。这儿建议把regenerator-runtime安装一次,避免不必要的麻烦。

  • 这里我总结下吧(这儿总结的地方我都没有显示的指定corejs参数,也就是上面代码的‘corejs’: 2这句话) 1、不安装core-js和regenerator-runtime,不论你的@babel/preset-env的useBuiltIns设置为哪个参数值都不能打包成功,usage参数值提示找不到core-js的包,entry直接不会引入相关的东西,因为包都没有,会提示你去下载corejs。false效果跟entry一样。 2、只安装了core-js(这儿安装的core-js@2版本),usage参数可以打包成功,但是提示指定core-js版本,这个正常,因为我没写那句'corejs: 2'了,测试在IE11上正常运行。entry参数需要手动在index.js中引入core-js,打包提示Can't resolve 'regenerator-runtime/runtime',打包失败,false参数打包成功,但是文件巨大,代码九千多行-_-,IE11运行成功。 3、安装core-js和regenerator-runtime,usage参数可以打包成功,但是提示指定core-js版本,这个正常,因为我没写那句'corejs: 2'了,测试在IE11上正常运行。entry参数可以打包成功,但是提示指定core-js版本,这个正常,因为我没写那句'corejs: 2'了,测试在IE11上正常运行,打包过后文件巨大,我安装了regenerator-runtime,没有在index.js里面手动引入,只引入了core-js,这次打包没报上面找不到包的错误。false参数可以打包成功,但是提示指定core-js版本,这个正常,因为我没写那句'corejs: 2'了,测试在IE11上正常运行,打包过后文件巨大。

所以我得出一个配置结果 在babel.config.js里面这样写:

module.exports = {
   presets: [
       ['@babel/preset-env', {
           'useBuiltIns': 'usage',
           'corejs': 2
       }]
   ]
}

package.json里面包含这些包:

{
  "name": "webpack-babel",
  "version": "1.0.0",
  "description": "practice",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.config.js"
  },
  "author": "lee",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.7.0",
    "@babel/preset-env": "^7.7.1",
    "babel-loader": "^8.0.6",
    "regenerator-runtime": "^0.13.3",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.9"
  },
  "dependencies": {
    "core-js": "^2.6.10"
  }
}

core-js和regenerator-runtime(这个为了避免entry参数时包错找不到包)都安装起。这样就可以了。

使用@babel/plugin-transform-runtime和@babel/runtime-corejs2来实现API的polyfill

前面使用了@babel/preset-env的useBuiltIns来实现API填充,这儿试一试另一种方案,首先卸载掉core-js和regenerator-runtime,然后安装一下两个包

npm install @babel/plugin-transform-runtime -D
npm install @babel/runtime-corejs2 -S

安装完成后配置一下babel.config.js

module.exports = {
    presets: [
        ['@babel/preset-env']
    ],
    plugins: [
        ['@babel/plugin-transform-runtime', {
            'corejs': 2
        }]
    ]
}

注意:这儿我是安装的@babel/runtime-corejs2,所以“'corejs': 2”这个参数不能少,否者会报 Can't resolve '@babel/runtime/helpers/createClass'这种错误,上面这种方式不会污染全局,适合第三方开发。

四、总结

暂时babel就总结到这儿了,顺便熟悉了一下webpack,如有不准确的地方还请各位多多指正,文章里面还有几个没理解清楚的地方,后期再琢磨琢磨!这里附上github的地址供大家参考:地址