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

banner

前言

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

我們?cè)谧鲆患? 學(xué)習(xí)一個(gè)知識(shí)點(diǎn)的時(shí)候, 都應(yīng)該是抱有一個(gè)目的去做的.

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

你到底有什么軟用.jpg

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

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

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

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

前期準(zhǔn)備

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

所以在正式開(kāi)始閱讀之前, 讓我們先來(lái)準(zhǔn)備一個(gè)這樣的案例項(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暫時(shí)沒(méi)有寫(xiě)內(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": {}
}

下面我都將圍繞這個(gè)babel-basic項(xiàng)目來(lái)進(jìn)行講解, 我希望你也能在本地準(zhǔn)備一個(gè)這樣的項(xiàng)目案例, 以便你更好的理解我接下來(lái)要說(shuō)的內(nèi)容.

@babel/core

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

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

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

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

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

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

@babel/cli

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

同樣, 它也需要先安裝:

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

讓我們安裝@babel/cli的同時(shí)再來(lái)安裝一下@babel/core,

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

src/index.js:

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

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

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

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

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

另外, 如果你是npm@5.2.0附帶的npm包運(yùn)行器的話, 就可以用npx babel來(lái)代替./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的縮寫(xiě)...

(我們使用上面的 --out-dir 選項(xiàng)。你可以通過(guò)使用 --help 運(yùn)行它來(lái)查看 cli 工具接受的其余選項(xiàng)。但對(duì)我們來(lái)說(shuō)最重要的是 --plugins--presets。)

$ npx babel --help

插件plugins

基本概念

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

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

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

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

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

如果你是要將ES6+轉(zhuǎn)成ES5, 可以依賴(lài)官方插件, 例如:

@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

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

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

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

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

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

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


console.log(fn());

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

表情包開(kāi)心

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

Presets:

基本概念

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

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

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

@babel/preset-env

例如, 我們使用envpreset:

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

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

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

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

src/index.js:

const fn = () => 1; // ES6箭頭函數(shù), 返回值為1
let num = 3 ** 2; // ES7求冪運(yùn)算符
let foo = function(a, b, c, ) { // ES7參數(shù)支持尾部逗號(hào)
    console.log('a:', a)
    console.log('b:', b)
    console.log('c:', c)
}
foo(1, 3, 4)
console.log(fn());
console.log(num);

然后在命令行里使用這個(gè)preset:

npx babel src --out-dir lib --presets=@babel/preset-env

現(xiàn)在打開(kāi)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ù)的最后一個(gè)逗號(hào)也被去掉了.

截止到現(xiàn)在, 看完了@babel/core、@babel/cliplugins、presets, 相信你對(duì)Babel的功能有一定了解了吧, 但是真正使用起來(lái)我們不可能都是靠命令行的形式吧, 沒(méi)錯(cuò), 接下來(lái)我要將這些功能做成配置項(xiàng).

配置

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

例如我們?cè)陧?xiàng)目的根目錄下創(chuàng)建一個(gè)babel.config.js文件:

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

module.exports = { presets };

加上這個(gè)配置的作用是:

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

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

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

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

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

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

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

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

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

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

所以你會(huì)發(fā)現(xiàn), 如果你用了我上面babel.config.js的配置之后生成的lib文件夾下的代碼好像并沒(méi)有發(fā)生什么改變, 也就是它并沒(méi)有被轉(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ù)支持尾部逗號(hào)
    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ù)支持尾部逗號(hào)
  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的這些功能, 所以它就沒(méi)有必要將其轉(zhuǎn)換了, 它只會(huì)為目標(biāo)瀏覽器中沒(méi)有的功能加載轉(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, 你就會(huì)發(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ù)支持尾部逗號(hào)
  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是對(duì)執(zhí)行環(huán)境或者其它功能的一個(gè)補(bǔ)充.

什么意思呢 ????

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

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

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

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

表情包裝逼

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

babel/polyfill用來(lái)模擬完成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

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

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

cnpm i --save @babel/polyfill

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

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

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

此時(shí)的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

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

還是上面??的那個(gè)例子, 我們來(lái)改造一下, 使用Edge17中沒(méi)有的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ù)支持尾部逗號(hào)
    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ù)支持尾部逗號(hào)
  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)境中沒(méi)有的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)境

后語(yǔ)

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

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

最后...

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

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

你的鼓勵(lì)就是我持續(xù)創(chuàng)作的主要?jiǎng)恿???.

相關(guān)推薦:

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

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

《霖呆呆你來(lái)說(shuō)說(shuō)瀏覽器緩存吧》

《怎樣讓后臺(tái)小哥哥快速對(duì)接你的前端頁(yè)面》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容