建議改成: 讀完這篇你還不懂Babel我給你寄口罩

banner

前言

最近在學(xué)習(xí)webpack, 發(fā)現(xiàn)了webpack中一個重要的功能點(diǎn)babel-loader, 于是就想著學(xué)習(xí)了解一波Babel.

我們在做一件事, 學(xué)習(xí)一個知識點(diǎn)的時候, 都應(yīng)該是抱有一個目的去做的.

在你花了大把時間大把精力去學(xué)習(xí)這個知識的時候, 它能帶給你什么 ??? ? 能幫助到你什么??? ?

你到底有什么軟用.jpg

就像我學(xué)習(xí)Babel一樣, 之前一直只知道它是一個JS編譯器, 大概功能是能幫我們在舊的瀏覽器環(huán)境中將ES6+代碼轉(zhuǎn)換成向后兼容版本的JS代碼, 但是其中重要的轉(zhuǎn)換功能是靠什么實(shí)現(xiàn), 以及里面到底有個什么學(xué)問是我沒深入了解的, 它對我學(xué)習(xí)webpack有什么幫助?

在這一篇文章中我并沒有介紹過于深入的內(nèi)容, 但是如果把它當(dāng)成一個入門Babel的教材來看那我相信它對你是有一定幫助的. 不信如果你讀完了它之后再去看看官方的文檔, 一定覺得都可以看懂了. 不然的話請?jiān)u論區(qū)留下你的地址, 看我給不給你寄口罩...

不拐彎抹角了, 嘻嘻 ??, 讓我們看看通過這一章節(jié)的閱讀你能學(xué)習(xí)到什么:

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

前期準(zhǔn)備

學(xué)習(xí)一個新的知識, 我還是偏向于用案例的的方式來打開講解它.

所以在正式開始閱讀之前, 讓我們先來準(zhǔn)備一個這樣的案例項(xiàng)目:

mkdir babel-basic && cd babel-basic
npm init -y
mkdir src && cd src
touch index.js

一頓操作之后, 我們新建的項(xiàng)目目錄為:

/babel-basic
    |- /src
        |- index.js
    |- package.json

現(xiàn)在package.json是最原始的配置, 而index.js暫時沒有寫內(nèi)容.

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項(xiàng)目來進(jìn)行講解, 我希望你也能在本地準(zhǔn)備一個這樣的項(xiàng)目案例, 以便你更好的理解我接下來要說的內(nèi)容.

@babel/core

我們學(xué)習(xí)Babel, 首先要了解一個叫@babel/core 的東西, 它是Babel的核心模塊.

當(dāng)然要使用它, 我們得先安裝:

$ npm i --save-dev @babel/core

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

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

這里的知識點(diǎn)有很多, 不過你不用急于的掌握它, 只需要知道它是Babel的核心, 讓我們接著往下看.

@babel/cli

再然后就是@babel/cli, 它是一個終端運(yùn)行工具, 內(nèi)置的插件,運(yùn)行你從終端使用babel的工具.

同樣, 它也需要先安裝:

$ npm i --save-dev @babel/cli @babel/core

讓我們安裝@babel/cli的同時再來安裝一下@babel/core,

現(xiàn)在, 讓我先在src/index.js中寫上一段簡單的代碼, 并來看看它的基本用法.

src/index.js:

const fn = () => 1; // 箭頭函數(shù), 返回值為1
console.log(fn());

用法一: 命令行的形式(在項(xiàng)目根目錄執(zhí)行語句):

$ ./node_modules/.bin/babel src --out-dir lib

這段語句的意思是: 它使用我們設(shè)置的解析方式來解析src目錄下的所有JS文件, 并將轉(zhuǎn)換后的每個文件都輸出到lib目錄下.

但是注意了, 由于我們現(xiàn)在沒有設(shè)置任何的解析方式, 所以你在執(zhí)行了這段語句之后, 能看到項(xiàng)目中多了一個lib目錄, 而且里面的JS代碼和src中的是一樣的. 至于我說的解析方式, 就是后面我要介紹的plugins和presets.

另外, 如果你是npm@5.2.0附帶的npm包運(yùn)行器的話, 就可以用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"
    }
}

現(xiàn)在運(yùn)行npm run build效果也是一樣的, -d--out-dir的縮寫...

(我們使用上面的 --out-dir 選項(xiàng)浪耘。你可以通過使用 --help 運(yùn)行它來查看 cli 工具接受的其余選項(xiàng)乱灵。但對我們來說最重要的是 --plugins--presets。)

$ npx babel --help

插件plugins

基本概念

知道了Babel的基本用法之后, 讓我們來看看具體的代碼轉(zhuǎn)換.

現(xiàn)在要介紹的是插件plugins, 它的本質(zhì)就是一個JS程序, 指示著Babel如何對代碼進(jìn)行轉(zhuǎn)換.

所以你也可以編寫自己的插件來應(yīng)用你想要的任何代碼轉(zhuǎn)換.

插件案例(箭頭函數(shù)插件)

但是首先讓我們來學(xué)習(xí)一些基本的插件.

如果你是要將ES6+轉(zhuǎn)成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

這個插件的作用是將箭頭函數(shù)轉(zhuǎn)換為ES5兼容的函數(shù):

還記得我們之前的src/index.js嗎:

const fn = () => 1; // 箭頭函數(shù), 返回值為1
console.log(fn());

現(xiàn)在編譯之后, 你再打開lib/index.js來看看.

它是不是被轉(zhuǎn)換為ES5的代碼了呢? ??

const fn = function () {
  return 1;
}; // 箭頭函數(shù), 返回值為1


console.log(fn());

搗鼓了這么久, 終于看到了一點(diǎn)實(shí)際的效果, 此時有點(diǎn)小興奮啊??

表情包開心

雖然我們已經(jīng)實(shí)現(xiàn)了箭頭函數(shù)轉(zhuǎn)換的功能, 但是ES6+其它的語法(比求冪運(yùn)算符**)卻并不能轉(zhuǎn)換, 這是因?yàn)槲覀冎皇褂昧?code>@babel/plugin-transform-arrow-functions這個功能插件, 沒有使用其它的了.

Presets:

基本概念

如果想要轉(zhuǎn)換ES6+的其它代碼為ES5, 我們可以使用"preset"來代替預(yù)先設(shè)定的一組插件, 而不是逐一添加我們想要的所有插件.

這里可以理解為一個preset就是一組插件的集合.

presets和plugins一樣, 也可以創(chuàng)建自己的preset, 分享你需要的任何插件組合.

@babel/preset-env

例如, 我們使用envpreset:

cnpm i --save-dev @babel/preset-env

envpreset這個preset包括支持現(xiàn)代JavaScript(ES6+)的所有插件.

所以也就是說你安裝使用了envpreset之后, 就可以看到其它ES6+語法的轉(zhuǎn)換了.

現(xiàn)在讓我們來用用ES7中的求冪運(yùn)算符函數(shù)參數(shù)支持尾部逗號這兩個功能吧:

src/index.js:

const fn = () => 1; // ES6箭頭函數(shù), 返回值為1
let num = 3 ** 2; // ES7求冪運(yùn)算符
let foo = function(a, b, c, ) { // ES7參數(shù)支持尾部逗號
    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

現(xiàn)在打開lib/src看看:

"use strict";

var fn = function fn() {
  return 1;
}; // 箭頭函數(shù), 返回值為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);

求冪運(yùn)算符被轉(zhuǎn)換為成Math.pow()

函數(shù)參數(shù)的最后一個逗號也被去掉了.

截止到現(xiàn)在, 看完了@babel/core七冲、@babel/cli痛倚、pluginspresets, 相信你對Babel的功能有一定了解了吧, 但是真正使用起來我們不可能都是靠命令行的形式吧, 沒錯, 接下來我要將這些功能做成配置項(xiàng).

配置

上面??介紹的都是一些終端傳入CLI的方式, 在實(shí)際使用上, 我們更加偏向于配置文件.

例如我們在項(xiàng)目的根目錄下創(chuàng)建一個babel.config.js文件:

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

module.exports = { presets };

加上這個配置的作用是:

  • 使用了envpreset這個preset
  • envpreset只會為目標(biāo)瀏覽器中沒有的功能加載轉(zhuǎn)換插件

現(xiàn)在你要使用這個配置就很簡單了, 直接用我們前面package.json配置的命令行語句:

{
    "scripts": {
        "build": "babel src -d lib"
    }
}

執(zhí)行npm run build就可以了.

這個命令行語句看起來并沒有修改, 那是因?yàn)樗J(rèn)會去尋找跟根目錄下的一個名為babel.config.js的文件(或者babelrc.js也可以, 這個在之后的使用babel的幾種方式中會說到), 所以其實(shí)就相當(dāng)于以下這個配置:

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

因此如果你的Babel配置文件是babel.config.js的話, 這兩種效果是一樣的.

(--config-file指令就類似于webpack中的--config, 用于指定以哪個配置文件構(gòu)建)

這里我重點(diǎn)要說一下只會為目標(biāo)瀏覽器中沒有的功能加載轉(zhuǎn)換插件這句話的意思.

例如我這里配置的其中一項(xiàng)是edge: "17", 那就表示它轉(zhuǎn)換之后的代碼支持到edge17.

所以你會發(fā)現(xiàn), 如果你用了我上面babel.config.js的配置之后生成的lib文件夾下的代碼好像并沒有發(fā)生什么改變, 也就是它并沒有被轉(zhuǎn)換成ES5的代碼:

src/index.js:

const fn = () => 1; // ES6箭頭函數(shù), 返回值為1
let num = 3 ** 2; // ES7求冪運(yùn)算符
let foo = function(a, b, c, ) { // ES7參數(shù)支持尾部逗號
    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配置之后構(gòu)建的lib/index.js:

"use strict";

const fn = () => 1; // ES6箭頭函數(shù), 返回值為1


let num = 3 ** 2; // ES7求冪運(yùn)算符

let foo = function foo(a, b, c) {
  // ES7參數(shù)支持尾部逗號
  console.log('a:', a);
  console.log('b:', b);
  console.log('c:', c);
};

foo(1, 3, 4);
console.log(fn());
console.log(num);

箭頭函數(shù)依舊是箭頭函數(shù), 求冪運(yùn)算符依舊是求冪運(yùn)算符.

這是因?yàn)樵贓dge17瀏覽器中支持ES7的這些功能, 所以它就沒有必要將其轉(zhuǎn)換了, 它只會為目標(biāo)瀏覽器中沒有的功能加載轉(zhuǎn)換插件!!!

如果我們將edge17改成edge10看看 ????

babel.config.js:

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

module.exports = { presets };

保存重新運(yùn)行npm run build, 你就會發(fā)現(xiàn)lib/index.js現(xiàn)在有所改變了:

"use strict";

var fn = function fn() {
  return 1;
}; // ES6箭頭函數(shù), 返回值為1


var num = Math.pow(3, 2); // ES7求冪運(yùn)算符

var foo = function foo(a, b, c) {
  // ES7參數(shù)支持尾部逗號
  console.log('a:', a);
  console.log('b:', b);
  console.log('c:', c);
};

foo(1, 3, 4);
console.log(fn());
console.log(num);

Polyfill

Plugins是提供的插件, 例如箭頭函數(shù)轉(zhuǎn)普通函數(shù)@babel/plugin-transform-arrow-functions

Presets是一組Plugins的集合.

而Polyfill是對執(zhí)行環(huán)境或者其它功能的一個補(bǔ)充.

什么意思呢 ????

就像現(xiàn)在你想在edge10瀏覽器中使用ES7中的方法includes(), 但是我們知道這個版本的瀏覽器環(huán)境是不支持你使用這個方法的, 所以如果你強(qiáng)行使用并不能達(dá)到預(yù)期的效果.

polyfill的作用正是如此, 知道你的環(huán)境不允許, 那就幫你引用一個這個環(huán)境, 也就是說此時編譯后的代碼就會變成這樣:

// 原來的代碼
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);

這樣說你應(yīng)該就能看懂它的作用了吧 ??

表情包裝逼

現(xiàn)在就讓我們來學(xué)習(xí)一個重要的polyfill, 它就是babel/polyfill.

babel/polyfill用來模擬完成ES6+環(huán)境:

  • 可以使用像Promise或者WeakMap這樣的新內(nèi)置函數(shù)
  • 可以使用像Array.from或者Object.assign這樣的靜態(tài)方法
  • 可以使用像Array.prototype.includes這樣的實(shí)例方法
  • 還有generator函數(shù)

為了實(shí)現(xiàn)這一點(diǎn), Polyfill增加了全局范圍以及像String這樣的原生原型.

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

對于庫/工具來說, 如果你不需要像Array.prototype.includes這樣的實(shí)例方法, 可以使用transform runtime插件, 而不是使用污染全局的@babel/polyfill.

對于應(yīng)用程序, 我們建議安裝使用@babel/polyfill

cnpm i --save @babel/polyfill

(注意 --save 選項(xiàng)而不是 --save-dev澜躺,因?yàn)檫@是一個需要在源代碼之前運(yùn)行的 polyfill蝉稳。)

但是由于我們使用的是envpreset, 這里個配置中有一個叫做 "useBuiltIns"的選項(xiàng)

如果將這個選擇設(shè)置為"usage", 就只包括你需要的polyfill

此時的babel.config.js調(diào)整為:

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

module.exports = { presets }

安裝配置了@babel/polyfill, Babel將檢查你的所有代碼, 然后查找目標(biāo)環(huán)境中缺少的功能, 并引入僅包含所需的polyfill

(如果我們沒有將 env preset 的 "useBuiltIns" 選項(xiàng)的設(shè)置為 "usage" ,就必須在其他代碼之前 require 一次完整的 polyfill掘鄙。)

還是上面??的那個例子, 我們來改造一下, 使用Edge17中沒有的Promise.prototype.finally:

src/index.js:

const fn = () => 1; // ES6箭頭函數(shù), 返回值為1
let num = 3 ** 2; // ES7求冪運(yùn)算符
let hasTwo = [1, 2, 3].includes(2)
let foo = function(a, b, c, ) { // ES7參數(shù)支持尾部逗號
    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);

現(xiàn)在執(zhí)行npm run build之后生成的lib/index.js變成了:

"use strict";

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

const fn = () => 1; // ES6箭頭函數(shù), 返回值為1


let num = 3 ** 2; // ES7求冪運(yùn)算符

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

let foo = function foo(a, b, c) {
  // ES7參數(shù)支持尾部逗號
  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 環(huán)境中沒有的promise.finally()

小結(jié)

  • babel/cli 允許我們從終端運(yùn)行Babel
  • envpreset 只包含我們使用的功能的轉(zhuǎn)換,實(shí)現(xiàn)我們的目標(biāo)瀏覽器中缺少的功能
  • @babel/polyfill實(shí)現(xiàn)所有新的JS功能, 為目標(biāo)瀏覽器引入缺少的環(huán)境

后語

哈哈??, 不好意思開頭騙了大家...寄口罩不存在的 ?? 我自己也是被關(guān)在家里不敢出門...

看我為了能讓大家老實(shí)呆家學(xué)習(xí)多費(fèi)心啊 ??

最后...

喜歡霖呆呆的小伙還希望可以關(guān)注霖呆呆的公眾號 LinDaiDai

我會不定時的更新一些前端方面的知識內(nèi)容以及自己的原創(chuàng)文章??

你的鼓勵就是我持續(xù)創(chuàng)作的主要動力 ??.

相關(guān)推薦:

《JavaScript進(jìn)階-執(zhí)行上下文(理解執(zhí)行上下文一篇就夠了)》

《全網(wǎng)最詳bpmn.js教材》

《霖呆呆你來說說瀏覽器緩存吧》

《怎樣讓后臺小哥哥快速對接你的前端頁面》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耘戚,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子操漠,更是在濱河造成了極大的恐慌收津,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颅夺,死亡現(xiàn)場離奇詭異朋截,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吧黄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唆姐,“玉大人拗慨,你說我怎么就攤上這事》盥” “怎么了赵抢?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長声功。 經(jīng)常有香客問我烦却,道長,這世上最難降的妖魔是什么先巴? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任其爵,我火速辦了婚禮,結(jié)果婚禮上伸蚯,老公的妹妹穿的比我還像新娘摩渺。我一直安慰自己,他們只是感情好剂邮,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布摇幻。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绰姻。 梳的紋絲不亂的頭發(fā)上枉侧,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機(jī)與錄音狂芋,去河邊找鬼棵逊。 笑死,一個胖子當(dāng)著我的面吹牛银酗,可吹牛的內(nèi)容都是我干的辆影。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼黍特,長吁一口氣:“原來是場噩夢啊……” “哼蛙讥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灭衷,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤次慢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后翔曲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迫像,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年瞳遍,在試婚紗的時候發(fā)現(xiàn)自己被綠了闻妓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡掠械,死狀恐怖由缆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情猾蒂,我是刑警寧澤均唉,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站肚菠,受9級特大地震影響舔箭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蚊逢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一层扶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧时捌,春花似錦怒医、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽焰薄。三九已至,卻和暖如春扒袖,著一層夾襖步出監(jiān)牢的瞬間塞茅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工季率, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留野瘦,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓飒泻,卻偏偏與公主長得像鞭光,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子泞遗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容