
前言
最近在學(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í)候, 它能帶給你什么 ??? ? 能幫助到你什么??? ?
就像我學(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/cli、plugins、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í)行上下文一篇就夠了)》