建议改成: 读完这篇你还不懂Babel我给你寄口罩

前言

最近在学习webpack, 发现了webpack中一个重要的功能点babel-loader, 于是就想着学习了解一波Babel.

我们在做一件事, 学习一个知识点的时候, 都应该是抱有一个目的去做的.

在你花了大把时间大把精力去学习这个知识的时候, 它能带给你什么 ?️ ? 能帮助到你什么?️ ?

你到底有什么软用.jpg
你到底有什么软用.jpg

就像我学习Babel一样, 之前一直只知道它是一个JS编译器, 大概功能是能帮我们在旧的浏览器环境中将ES6+代码转换成向后兼容版本的JS代码, 但是其中重要的转换功能是靠什么实现, 以及里面到底有个什么学问是我没深入了解的, 它对我学习webpack有什么帮助?

在这一篇文章中我并没有介绍过于深入的内容, 但是如果把它当成一个入门Babel的教材来看那我相信它对你是有一定帮助的. 不信如果你读完了它之后再去看看官方的文档, 一定觉得都可以看懂了. 不然的话请评论区留下你的地址, 看我给不给你寄口罩...

不拐弯抹角了, 嘻嘻 ?, 让我们看看通过这一章节的阅读你能学习到什么:

  • @babel/cli
  • plugins
  • presets
  • 配置Babel
  • polyfill

前期准备

学习一个新的知识, 我还是偏向于用案例的的方式来打开讲解它.

所以在正式开始阅读之前, 让我们先来准备一个这样的案例项目:

mkdir babel-basic && cd babel-basic
npm init -y
mkdir src && cd src
touch index.js
复制代码

一顿操作之后, 我们新建的项目目录为:

/babel-basic
 |- /src
   |- index.js
 |- package.json
复制代码

现在package.json是最原始的配置, 而index.js暂时没有写内容.

package.json:

{
  "name": "babel-basic",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {}
}

复制代码

下面我都将围绕这个babel-basic项目来进行讲解, 我希望你也能在本地准备一个这样的项目案例, 以便你更好的理解我接下来要说的内容.

@babel/core

我们学习Babel, 首先要了解一个叫@babel/core 的东西, 它是Babel的核心模块.

当然要使用它, 我们得先安装:

$ npm i --save-dev @babel/core
复制代码

安装成功之后就可以在我们的代码中使用了, 你可以采用CommonJS的引用方式:

const babel = require('@babel/core');
babel.transform("code", options);
复制代码

这里的知识点有很多, 不过你不用急于的掌握它, 只需要知道它是Babel的核心, 让我们接着往下看.

@babel/cli

再然后就是@babel/cli, 它是一个终端运行工具, 内置的插件,运行你从终端使用babel的工具.

同样, 它也需要先安装:

$ npm i --save-dev @babel/cli @babel/core
复制代码

让我们安装@babel/cli的同时再来安装一下@babel/core,

现在, 让我先在src/index.js中写上一段简单的代码, 并来看看它的基本用法.

src/index.js:

const fn = () => 1; // 箭头函数, 返回值为1
console.log(fn());
复制代码

用法一: 命令行的形式(在项目根目录执行语句):

$ ./node_modules/.bin/babel src --out-dir lib
复制代码

这段语句的意思是: 它使用我们设置的解析方式来解析src目录下的所有JS文件, 并将转换后的每个文件都输出到lib目录下.

但是注意了, 由于我们现在没有设置任何的解析方式, 所以你在执行了这段语句之后, 能看到项目中多了一个lib目录, 而且里面的JS代码和src中的是一样的. 至于我说的解析方式, 就是后面我要介绍的plugins和presets.

另外, 如果你是npm@5.2.0附带的npm包运行器的话, 就可以用npx babel来代替./node_modules/.bin/babel:

$ npx babel src --out-dir lib
复制代码

用法二: 给package.json中配置一段脚本命令:

{
    "name": "babel-basic",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
+       "build": "babel src -d lib"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
+       "@babel/cli": "^7.8.4",
+       "@babel/core": "^7.8.4"
    }
}
复制代码

现在运行npm run build效果也是一样的, -d--out-dir的缩写...

(我们使用上面的 --out-dir 选项。你可以通过使用 --help 运行它来查看 cli 工具接受的其余选项。但对我们来说最重要的是 --plugins--presets。)

$ npx babel --help
复制代码

插件plugins

基本概念

知道了Babel的基本用法之后, 让我们来看看具体的代码转换.

现在要介绍的是插件plugins, 它的本质就是一个JS程序, 指示着Babel如何对代码进行转换.

所以你也可以编写自己的插件来应用你想要的任何代码转换.

插件案例(箭头函数插件)

但是首先让我们来学习一些基本的插件.

如果你是要将ES6+转成ES5, 可以依赖官方插件, 例如:

@babel/plugin-transform-arrow-functions:

$ cnpm i --save-dev @babel/plugin-transform-arrow-functions
$ npx babel src --out-dir lib --plugins=@babel/plugin-transform-arrow-functions
复制代码

这个插件的作用是将箭头函数转换为ES5兼容的函数:

还记得我们之前的src/index.js吗:

const fn = () => 1; // 箭头函数, 返回值为1
console.log(fn());
复制代码

现在编译之后, 你再打开lib/index.js来看看.

它是不是被转换为ES5的代码了呢? ?

const fn = function () {
  return 1;
}; // 箭头函数, 返回值为1


console.log(fn());
复制代码

捣鼓了这么久, 终于看到了一点实际的效果, 此时有点小兴奋啊?

表情包开心

虽然我们已经实现了箭头函数转换的功能, 但是ES6+其它的语法(比求幂运算符**)却并不能转换, 这是因为我们只使用了@babel/plugin-transform-arrow-functions这个功能插件, 没有使用其它的了.

Presets:

基本概念

如果想要转换ES6+的其它代码为ES5, 我们可以使用"preset"来代替预先设定的一组插件, 而不是逐一添加我们想要的所有插件.

这里可以理解为一个preset就是一组插件的集合.

presets和plugins一样, 也可以创建自己的preset, 分享你需要的任何插件组合.

@babel/preset-env

例如, 我们使用envpreset:

cnpm i --save-dev @babel/preset-env
复制代码

envpreset这个preset包括支持现代JavaScript(ES6+)的所有插件.

所以也就是说你安装使用了envpreset之后, 就可以看到其它ES6+语法的转换了.

现在让我们来用用ES7中的求幂运算符函数参数支持尾部逗号这两个功能吧:

src/index.js:

const fn = () => 1; // ES6箭头函数, 返回值为1
let num = 3 ** 2; // ES7求幂运算符
let foo = function(a, b, c, ) { // ES7参数支持尾部逗号
    console.log('a:', a)
    console.log('b:', b)
    console.log('c:', c)
}
foo(1, 3, 4)
console.log(fn());
console.log(num);
复制代码

然后在命令行里使用这个preset:

npx babel src --out-dir lib --presets=@babel/preset-env
复制代码

现在打开lib/src看看:

"use strict";

var fn = function fn() {
  return 1;
}; // 箭头函数, 返回值为1


var num = Math.pow(3, 2);

var foo = function foo(a, b, c) {
  console.log('a:', a);
  console.log('b:', b);
  console.log('c:', c);
};

foo(1, 3, 4);
console.log(fn());
console.log(num);
复制代码

求幂运算符被转换为成Math.pow()

函数参数的最后一个逗号也被去掉了.

截止到现在, 看完了@babel/core@babel/clipluginspresets, 相信你对Babel的功能有一定了解了吧, 但是真正使用起来我们不可能都是靠命令行的形式吧, 没错, 接下来我要将这些功能做成配置项.

配置

上面?介绍的都是一些终端传入CLI的方式, 在实际使用上, 我们更加偏向于配置文件.

例如我们在项目的根目录下创建一个babel.config.js文件:

const presets = [
	[
    "@babel/env",
    {
      targets: {
        edge: "17",
        chrome: "64",
        firefox: "60",
        safari: "11.1"
      }
    }
  ]	
]

module.exports = { presets };
复制代码

加上这个配置的作用是:

  • 使用了envpreset这个preset
  • envpreset只会为目标浏览器中没有的功能加载转换插件

现在你要使用这个配置就很简单了, 直接用我们前面package.json配置的命令行语句:

{
	"scripts": {
		"build": "babel src -d lib"
	}
}
复制代码

执行npm run build就可以了.

这个命令行语句看起来并没有修改, 那是因为它默认会去寻找跟根目录下的一个名为babel.config.js的文件(或者babelrc.js也可以, 这个在之后的使用babel的几种方式中会说到), 所以其实就相当于以下这个配置:

{
	"scripts": {
		"build": "babel src -d lib --config-file ./babel.config.js"
	}
}
复制代码

因此如果你的Babel配置文件是babel.config.js的话, 这两种效果是一样的.

(--config-file指令就类似于webpack中的--config, 用于指定以哪个配置文件构建)

这里我重点要说一下只会为目标浏览器中没有的功能加载转换插件这句话的意思.

例如我这里配置的其中一项是edge: "17", 那就表示它转换之后的代码支持到edge17.

所以你会发现, 如果你用了我上面babel.config.js的配置之后生成的lib文件夹下的代码好像并没有发生什么改变, 也就是它并没有被转换成ES5的代码:

src/index.js:

const fn = () => 1; // ES6箭头函数, 返回值为1
let num = 3 ** 2; // ES7求幂运算符
let foo = function(a, b, c, ) { // ES7参数支持尾部逗号
    console.log('a:', a)
    console.log('b:', b)
    console.log('c:', c)
}
foo(1, 3, 4)
console.log(fn());
console.log(num);
复制代码

使用babel.config.js配置之后构建的lib/index.js:

"use strict";

const fn = () => 1; // ES6箭头函数, 返回值为1


let num = 3 ** 2; // ES7求幂运算符

let foo = function foo(a, b, c) {
  // ES7参数支持尾部逗号
  console.log('a:', a);
  console.log('b:', b);
  console.log('c:', c);
};

foo(1, 3, 4);
console.log(fn());
console.log(num);
复制代码

箭头函数依旧是箭头函数, 求幂运算符依旧是求幂运算符.

这是因为在Edge17浏览器中支持ES7的这些功能, 所以它就没有必要将其转换了, 它只会为目标浏览器中没有的功能加载转换插件!!!

如果我们将edge17改成edge10看看 ?️?

babel.config.js:

const presets = [
    [
        "@babel/env",
        {
            targets: {
-               edge: "17",
+               edge: "10",
                firefox: "60",
                chrome: "67",
                safari: "11.1",
            },
        },
    ],
];

module.exports = { presets };
复制代码

保存重新运行npm run build, 你就会发现lib/index.js现在有所改变了:

"use strict";

var fn = function fn() {
  return 1;
}; // ES6箭头函数, 返回值为1


var num = Math.pow(3, 2); // ES7求幂运算符

var foo = function foo(a, b, c) {
  // ES7参数支持尾部逗号
  console.log('a:', a);
  console.log('b:', b);
  console.log('c:', c);
};

foo(1, 3, 4);
console.log(fn());
console.log(num);
复制代码

Polyfill

Plugins是提供的插件, 例如箭头函数转普通函数@babel/plugin-transform-arrow-functions

Presets是一组Plugins的集合.

而Polyfill是对执行环境或者其它功能的一个补充.

什么意思呢 ?️?

就像现在你想在edge10浏览器中使用ES7中的方法includes(), 但是我们知道这个版本的浏览器环境是不支持你使用这个方法的, 所以如果你强行使用并不能达到预期的效果.

polyfill的作用正是如此, 知道你的环境不允许, 那就帮你引用一个这个环境, 也就是说此时编译后的代码就会变成这样:

// 原来的代码
var hasTwo = [1, 2, 3].includes(2);

// 加了polyfill之后的代码
require("core-js/modules/es7.array.includes");
require("core-js/modules/es6.string.includes");
var hasTwo = [1, 2, 3].includes(2);
复制代码

这样说你应该就能看懂它的作用了吧 ?

表情包装逼

现在就让我们来学习一个重要的polyfill, 它就是@babel/polyfill.

@babel/polyfill用来模拟完成ES6+环境:

  • 可以使用像Promise或者WeakMap这样的新内置函数
  • 可以使用像Array.from或者Object.assign这样的静态方法
  • 可以使用像Array.prototype.includes这样的实例方法
  • 还有generator函数

为了实现这一点, Polyfill增加了全局范围以及像String这样的原生原型.

@babel/polyfill模块包括了core-js和自定义regenerator runtime

对于库/工具来说, 如果你不需要像Array.prototype.includes这样的实例方法, 可以使用transform runtime插件, 而不是使用污染全局的@babel/polyfill.

对于应用程序, 我们建议安装使用@babel/polyfill

cnpm i --save @babel/polyfill
复制代码

(注意 --save 选项而不是 --save-dev,因为这是一个需要在源代码之前运行的 polyfill。)

但是由于我们使用的是envpreset, 这里个配置中有一个叫做 "useBuiltIns"的选项

如果将这个选择设置为"usage", 就只包括你需要的polyfill

此时的babel.config.js调整为:

const presets = [
	[
		"@babel/env",
		{
			targets: {
				edge: "17",
				chrome: "64",
				firefox: "67",
				safari: '11.1'
			},
+			useBuiltIns: "usage"
		}
	]
]

module.exports = { presets }
复制代码

安装配置了@babel/polyfill, Babel将检查你的所有代码, 然后查找目标环境中缺少的功能, 并引入仅包含所需的polyfill

(如果我们没有将 env preset 的 "useBuiltIns" 选项的设置为 "usage" ,就必须在其他代码之前 require 一次完整的 polyfill。)

还是上面?的那个例子, 我们来改造一下, 使用Edge17中没有的Promise.prototype.finally:

src/index.js:

const fn = () => 1; // ES6箭头函数, 返回值为1
let num = 3 ** 2; // ES7求幂运算符
let hasTwo = [1, 2, 3].includes(2)
let foo = function(a, b, c, ) { // ES7参数支持尾部逗号
    console.log('a:', a)
    console.log('b:', b)
    console.log('c:', c)
}
foo(1, 3, 4)
Promise.resolve().finally();
console.log(fn());
console.log(num);
console.log(hasTwo);
复制代码

现在执行npm run build之后生成的lib/index.js变成了:

"use strict";

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

const fn = () => 1; // ES6箭头函数, 返回值为1


let num = 3 ** 2; // ES7求幂运算符

let hasTwo = [1, 2, 3].includes(2);

let foo = function foo(a, b, c) {
  // ES7参数支持尾部逗号
  console.log('a:', a);
  console.log('b:', b);
  console.log('c:', c);
};

foo(1, 3, 4);
Promise.resolve().finally();
console.log(fn());
console.log(num);
console.log(hasTwo);
复制代码

@babel/polyfill帮我们引入了Edge17 环境中没有的promise.finally()

被deprecated的@babel/polyfill

上面我介绍了一种名为@babel/polyfill的polypill, 其实它在Babel7.4.0以上已经不被推荐使用了.

而是推荐使用core-js@3+@babel/preset-env然后设置@babel/preset-envcorejs选项为3.

因此如果你按着我文章中讲方式使用@babel/polyfill, 是可以实现的, 不过控制台中会抛出一个警告⚠️:

WARNING: We noticed you're using the `useBuiltIns` option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the `corejs` option.

You should also be sure that the version you pass to the `corejs` option matches the version specified in your `package.json`'s `dependencies` section. If it doesn't, you need to run one of the following commands:

  npm install --save core-js@2    npm install --save core-js@3
  yarn add core-js@2              yarn add core-js@3
复制代码

解决办法是卸载掉@babel/polyfill, 然后重新安装core-js@版本号, 然后重新配置一些babel.config.js文件.

  1. 安装core-js@3:
cnpm i --save core-js@3
复制代码
  1. 添加corejs选项:
const presets = [
[
  "@babel/env",
      {
        targets: {
        edge: "17",
        chrome: "64",
        firefox: "67",
        safari: '11.1'
      },
      useBuiltIns: "usage",
+     corejs: 3
    }
  ]
]

module.exports = { presets }
复制代码

(useBuiltIns选项还是不能去掉)

现在重新npm run build之后就不会有这个警告了, 而且生成的lib也是正确的.

(感谢掘友 KsRyY 的细心提醒? )

小结

  • babel/cli 允许我们从终端运行Babel
  • envpreset 只包含我们使用的功能的转换,实现我们的目标浏览器中缺少的功能
  • @babel/polyfill实现所有新的JS功能, 为目标浏览器引入缺少的环境(但是Babel7.4.0以上不推荐使用)

后语

哈哈?, 不好意思开头骗了大家...寄口罩不存在的 ? 我自己也是被关在家里不敢出门...

看我为了能让大家老实呆家学习多费心啊 ? (不要脸了一波)

最后...

喜欢霖呆呆的小伙还希望可以关注霖呆呆的公众号 LinDaiDai 或者扫一扫下面的二维码???.

LinDaiDai公众号二维码.png
LinDaiDai公众号二维码.png

我会不定时的更新一些前端方面的知识内容以及自己的原创文章?

你的鼓励就是我持续创作的主要动力 ?.

相关推荐:

《JavaScript进阶-执行上下文(理解执行上下文一篇就够了)》

《全网最详bpmn.js教材》

《霖呆呆你来说说浏览器缓存吧》

《怎样让后台小哥哥快速对接你的前端页面》

《你的文章本可以这么炫(博客美化工具一波带走)》