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

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

前言

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

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

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

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

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

在这一篇文章中我并无介绍过于深刻的内容, 可是若是把它当成一个入门Babel的教材来看那我相信它对你是有必定帮助的. 不信若是你读完了它以后再去看看官方的文档, 必定以为均可以看懂了. 否则的话请评论区留下你的地址, 看我给不给你寄口罩...web

不拐弯抹角了, 嘻嘻 😁, 让咱们看看经过这一章节的阅读你能学习到什么:chrome

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

前期准备

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

因此在正式开始阅读以前, 让咱们先来准备一个这样的案例项目:json

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

一顿操做以后, 咱们新建的项目目录为:promise

/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()

小结

  • babel/cli 容许咱们从终端运行Babel
  • envpreset 只包含咱们使用的功能的转换,实现咱们的目标浏览器中缺乏的功能
  • @babel/polyfill实现全部新的 JS功能, 为目标浏览器引入缺乏的环境

后语

哈哈😄, 很差意思开头骗了你们...寄口罩不存在的 😂 我本身也是被关在家里不敢出门...

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

最后...

喜欢霖呆呆的小伙还但愿能够关注霖呆呆的公众号 LinDaiDai 或者扫一扫下面的二维码👇👇👇.

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

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

你的鼓励就是我持续创做的主要动力 😊.

相关推荐:

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

《全网最详bpmn.js教材》

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

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