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

banner

前言

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

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

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

你到底有什么软用.jpg

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

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

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

  • @babel/cli
  • plugins
  • presets
  • 配置Bable
  • 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()

小结

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

后语

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

看我为了能让大家老实呆家学习多费心啊 😂

最后...

喜欢霖呆呆的小伙还希望可以关注霖呆呆的公众号 LinDaiDai

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

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

相关推荐:

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

《全网最详bpmn.js教材》

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容