前言
最近在學(xué)習(xí)webpack, 發(fā)現(xiàn)了webpack中一個重要的功能點(diǎn)babel-loader
, 于是就想著學(xué)習(xí)了解一波Babel.
我們在做一件事, 學(xué)習(xí)一個知識點(diǎn)的時候, 都應(yīng)該是抱有一個目的去做的.
在你花了大把時間大把精力去學(xué)習(xí)這個知識的時候, 它能帶給你什么 ??? ? 能幫助到你什么??? ?
就像我學(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
例如, 我們使用env
preset:
cnpm i --save-dev @babel/preset-env
env
preset這個preset包括支持現(xiàn)代JavaScript(ES6+)的所有插件.
所以也就是說你安裝使用了env
preset之后, 就可以看到其它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
痛倚、plugins
、presets
, 相信你對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 };
加上這個配置的作用是:
- 使用了
env
preset這個preset -
env
preset只會為目標(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蝉稳。)
但是由于我們使用的是env
preset, 這里個配置中有一個叫做 "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 -
env
preset 只包含我們使用的功能的轉(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)推薦: