什么是Babel降狠?
Babel 是一個(gè)工具鏈榜配,主要用于在舊的瀏覽器或環(huán)境中將 ECMAScript 2015+ 代碼轉(zhuǎn)換為向后兼容版本的 JavaScript 代碼。
主要做的事情:
- 語(yǔ)法轉(zhuǎn)換
- 實(shí)現(xiàn)目標(biāo)環(huán)境缺少的功能(es2015+)
- 源代碼轉(zhuǎn)換 (codemods)
- 還有更多5叭臁(點(diǎn)開(kāi)這些視頻看看)
用法
在這里會(huì)介紹如何將用es2015+寫(xiě)的JavaScript代碼轉(zhuǎn)換為能在當(dāng)前瀏覽器正常執(zhí)行的代碼临燃。包括兩方面:語(yǔ)法轉(zhuǎn)換、功能補(bǔ)充(這里暫時(shí)叫這個(gè)名字烙心,之后會(huì)相信介紹)膜廊。
- 安裝這些必要的包
npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill
- 在根目錄創(chuàng)建一個(gè)babel.config.js的配置文件:
const presets = [
[
"@babel/env",
{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},
useBuiltIns: "usage",
},
],
];
module.exports = { presets };
- targets:表示編譯出的代碼想要支持的瀏覽器版本
- useBuiltIns:之后詳細(xì)解釋
- 執(zhí)行命令
./node_modules/.bin/babel src --out-dir lib
然后你用es2015+編寫(xiě)的代碼就被轉(zhuǎn)化為能在目標(biāo)瀏覽器正常運(yùn)行的代碼了。
如何運(yùn)行的淫茵?
所有你用到的babel包都是被單獨(dú)發(fā)布在@babel作用域下(v7開(kāi)始)爪瓜,比如@babel/preset-env匙瘪、@babel/core铆铆、 @babel/cli,因?yàn)閎abel是插拔式的丹喻,所以用到什么安裝什么薄货,每個(gè)包各司其職。
@babel/core
其中最核心的包就是@babel/core碍论,它主要的作用就是編譯:
npm install --save-dev @babel/core
然后你可以在代碼里直接使用:
const babel = require("@babel/core");
babel.transform("code", optionsObject);
這里的optionsObject就和之前的babel.config.js是一樣的谅猾,如何編譯代碼,編譯成什么樣子什么標(biāo)準(zhǔn)用什么東西都在這里配置鳍悠。
@babel/cli
為什么我們能在命令行里直接使用:
./node_modules/.bin/babel src --out-dir lib
光有core是無(wú)法在命令行使用這些功能的税娜,@babel/cli支持你直接在命令行中編譯代碼。
這句話會(huì)編譯你src目錄下的所有js代碼藏研,并編譯成你想要的那樣(babel.config.js配置的)巧涧,并輸出到lib目錄下。
如果我們沒(méi)有配置babel.config.js遥倦,那么執(zhí)行這句話之后src會(huì)被原封不動(dòng)的搬到lib下(格式除外)谤绳。
--out-dir 代表輸出到哪個(gè)目錄下,你可試試--help看其他的用法袒哥,如果在這里我們沒(méi)有配置babel.config.js缩筛,我們可以通過(guò)--plugins 或者 --presets告訴 代碼應(yīng)該編譯成什么樣子。
Plugins & Presets
plugins顧名思義就是組件堡称,一個(gè)小型的js代碼程序告訴Babel
如何轉(zhuǎn)換你的源碼瞎抛,你可以自己寫(xiě)plugins也可以在github上使用別人寫(xiě)好的。來(lái)看如何使用一個(gè)插件:@babel/plugin-transform-arrow-functions
:
npm install --save-dev @babel/plugin-transform-arrow-functions
./node_modules/.bin/babel src --out-dir lib --plugins=@babel/plugin-transform-arrow-functions
@babel/plugin-transform-arrow-functions
組件的作用就是將es2015的箭頭函數(shù)轉(zhuǎn)換成普通函數(shù):
// src/foo.js:
const fn = () => 1;
// converted to
// lib/foo.js:
var fn = function fn() {
return 1;
};
當(dāng)然却紧,es2015有這么多新的語(yǔ)法桐臊,我們不可能一一的去引用每個(gè)plugins來(lái)編譯我們的代碼吧胎撤,于是就又了presets,顧名思義——預(yù)設(shè)断凶,它包含了一組我們需要的plugins伤提。就像plugin一樣,你也可以編寫(xiě)一組你最需要的plugins成為一個(gè)preset认烁。
目前這里有一個(gè)非常優(yōu)秀的preset叫env —— @babel/preset-env肿男。
npm install --save-dev @babel/preset-env
./node_modules/.bin/babel src --out-dir lib --presets=@babel/env
不需要任何配置,這個(gè)preset包含了所有現(xiàn)代js(es2015 es2016等)的所有新特性却嗡,你也可以傳遞一些配置給env舶沛,精準(zhǔn)實(shí)現(xiàn)你想要的編譯效果。
配置
更具你的需求窗价,配置肯定是不一樣的如庭,這里貼一個(gè)官方推薦配置:
const presets = [
[
"@babel/env",
{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},
},
],
];
module.exports = { presets };
這個(gè)配置只配置了prsets,其實(shí)還可以配置plugins撼港。
Polyfill
中文翻譯是墊片柱彻,之前沒(méi)有詳細(xì)了解babel之前,我也很迷茫這個(gè)polyfill是啥餐胀,因?yàn)檎Z(yǔ)法不都給你轉(zhuǎn)換好了哟楷,還需要這個(gè)東西干啥,后來(lái)仔細(xì)想了一下否灾,要適應(yīng)新特性應(yīng)該從兩方面入手:
- 語(yǔ)法轉(zhuǎn)換:
() => {};
for (let i of items) {};
比如箭頭函數(shù)卖擅、for...of,在不支持這些語(yǔ)法的環(huán)境下墨技,直接會(huì)報(bào)語(yǔ)法錯(cuò)誤惩阶,因?yàn)榫幾g器根本不知道 =>
這些是什么鬼符號(hào),要做到讓編譯器識(shí)別扣汪,那就要將這樣的語(yǔ)法轉(zhuǎn)換成瀏覽器能識(shí)別的代碼断楷,那么就需要語(yǔ)法轉(zhuǎn)換。
然后這里回到我們最開(kāi)始安裝包那里:
npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill
仔細(xì)看我們安裝core cli env都是 --save-dev崭别,這是因?yàn)槲覀儼l(fā)布的代碼都是編譯好的代碼冬筒,這些都只是開(kāi)發(fā)依賴,發(fā)布的代碼不需要依賴這些包茅主。
- 功能補(bǔ)充
'foo'.includes('f');
es2015里不僅只有新的語(yǔ)法舞痰,還有實(shí)例的擴(kuò)展,比如String诀姚,其實(shí)這里只是調(diào)用了String實(shí)例的一個(gè)方法响牛,我們無(wú)論怎么語(yǔ)法轉(zhuǎn)換也沒(méi)有什么用吧,如果我們?cè)诓恢С諷tring.prototype.includes的編譯器里跑這些代碼,會(huì)得到 'foo'.includes is not a function. 這樣的一個(gè)報(bào)錯(cuò)呀打,而不是語(yǔ)法報(bào)錯(cuò)矢赁。
Polyfill提供的就是一個(gè)這樣功能的補(bǔ)充,實(shí)現(xiàn)了Array贬丛、Object等上的新方法撩银,實(shí)現(xiàn)了Promise、Symbol這樣的新Class等瘫寝。到這里應(yīng)該能明白了蜒蕾,為什么安裝@babel/polyfill
沒(méi)有-dev稠炬,因?yàn)榫退愦a發(fā)布后焕阿,編譯后的代碼依然會(huì)依賴這些新特性來(lái)實(shí)現(xiàn)功能。
雖然@babel/polyfill提供了我們想要的所有新方法新類首启,但是這里依然存在一些問(wèn)題:
- 體積太大:比如我只用了String的新特性暮屡,但是我把整個(gè)包都引進(jìn)來(lái)了,毅桃,這不是徒增了很多無(wú)用的代碼褒纲。
- 污染全局環(huán)境:如果你引用了
@babel/polyfill
,那么像Promise這樣的新類就是掛載在全局上的钥飞,這樣就會(huì)污染了全局命名空間莺掠。可能在一個(gè)團(tuán)建建立的項(xiàng)目問(wèn)題不太大读宙,但是如果你是一個(gè)工具的開(kāi)發(fā)者彻秆,你把全局環(huán)境污染了,別人用你的工具结闸,就有可能把別人給坑了唇兑。
一個(gè)解決方案就是引入transform runtime 來(lái)替代 @babel/polyfill
。
幸運(yùn)的是桦锄,我們有env這個(gè)preset扎附,它又一個(gè)useBuiltIns選項(xiàng),如果設(shè)置成"usage"
结耀,那么將會(huì)自動(dòng)檢測(cè)語(yǔ)法幫你require你代碼中使用到的功能留夜。
const presets = [
[
"@babel/env",
{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},
useBuiltIns: "usage",
},
],
];
module.exports = { presets };
比如我在代碼中:
Promise.resolve().finally();
如果在edge17不支持這個(gè)特性的環(huán)境里運(yùn)行,將會(huì)幫你編譯成:
require("core-js/modules/es.promise.finally");
Promise.resolve().finally();
@babel/plugin-transform-runtime
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
主要功能:
- 避免多次編譯出helper函數(shù):
Babel轉(zhuǎn)移后的代碼想要實(shí)現(xiàn)和原來(lái)代碼一樣的功能需要借助一些幫助函數(shù)图甜,比如:
class Person {}
會(huì)被轉(zhuǎn)換為:
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Person = function Person() {
_classCallCheck(this, Person);
};
這里_classCallCheck
就是一個(gè)helper函數(shù)香伴,試想下如果我們很多文件里都聲明了類,那么就會(huì)產(chǎn)生很多個(gè)這樣的helper函數(shù)具则,積少成多增大了代碼體積即纲。
這里的@babel/runtime
包就聲明了所有需要用到的幫助函數(shù),而@babel/plugin-transform-runtime
的作用就是將所有需要helper函數(shù)的文件博肋,依賴@babel/runtime
包:
"use strict";
var _classCallCheck2 = require("@babel/runtime/helpers/classCallCheck");
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
var Person = function Person() {
(0, _classCallCheck3.default)(this, Person);
};
這里就沒(méi)有再編譯出helper函數(shù)classCallCheck了低斋,而是直接引用了@babel/runtime
中的helpers/classCallCheck蜂厅。
- 解決
@babel/polyfill
提供的類或者實(shí)例方法污染全局作用域的情況。
@babel/plugin-transform-runtime
會(huì)為代碼創(chuàng)建一個(gè)沙盒環(huán)境膊畴,為core-js
這里內(nèi)建的實(shí)例提供假名掘猿,你可以無(wú)縫的使用這些新特性,而不需要使用require polyfill唇跨。
"foobar".includes("foo")
稠通,這樣的實(shí)例方法仍然是不能正常執(zhí)行的,因?yàn)樗趻燧d在String.prototype上的买猖,如果需要使用這樣的實(shí)例方法改橘,還是不得不require('@babel/polyfill')
比如:
var sym = Symbol();
var promise = new Promise();
console.log(arr[Symbol.iterator]());
會(huì)被編譯成:
"use strict";
var _getIterator2 = require("@babel/runtime-corejs2/core-js/get-iterator");
var _getIterator3 = _interopRequireDefault(_getIterator2);
var _promise = require("@babel/runtime-corejs2/core-js/promise");
var _promise2 = _interopRequireDefault(_promise);
var _symbol = require("@babel/runtime-corejs2/core-js/symbol");
var _symbol2 = _interopRequireDefault(_symbol);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
var sym = (0, _symbol2.default)();
var promise = new _promise2.default();
console.log((0, _getIterator3.default)(arr));
這樣像Symbol、Promise這樣的新類也不會(huì)污染全局環(huán)境了玉控。
用法:
配置在配置文件里飞主,以.babelrc
舉例:
無(wú)選項(xiàng)配置:
{
"plugins": ["@babel/plugin-transform-runtime"]
}
有選項(xiàng)配置(默認(rèn)值):
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
這個(gè)插件的默認(rèn)配置默認(rèn)用戶已經(jīng)提供了所有polyfillable APIs,因此想要無(wú)縫使用不污染全局環(huán)境的內(nèi)建功能需要特別標(biāo)明corejs高诺。
可選項(xiàng):
corejs
:默認(rèn)false碌识,或者數(shù)字:{ corejs: 2 }
,代表需要使用corejs的版本虱而。helpers
:默認(rèn)是true筏餐,是否替換helpers。polyfill
:v7無(wú)該屬性regenerator
:默認(rèn)true牡拇,generator是否被轉(zhuǎn)譯成用regenerator runtime包裝不污染全局作用域的代碼魁瞪。useESModules
:默認(rèn)false,如果是true將不會(huì)用@babel/plugin-transform-modules-commonjs
進(jìn)行轉(zhuǎn)譯诅迷,這樣會(huì)減小打包體積佩番,因?yàn)椴恍枰3终Z(yǔ)義。false:
exports.__esModule = true;
exports.default = function(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
- true:
export default function(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
babel-polyfill vs babel-runtime
babel-polyfill
和 babel-runtime
是達(dá)成同一種功能(模擬ES2015環(huán)境罢杉,包括global keywords趟畏,prototype methods,都基于core-js提供的一組polyfill和一個(gè)generator runtime)的兩種方式:
babel-polyfill通過(guò)向全局對(duì)象和內(nèi)置對(duì)象的prototype上添加方法來(lái)達(dá)成目的滩租。這意味著你一旦引入babel-polyfill赋秀,像Map,Array.prototype.find這些就已經(jīng)存在了——全局空間被污染律想。
babel-runtime不會(huì)污染全局空間和內(nèi)置對(duì)象原型猎莲。事實(shí)上babel-runtime是一個(gè)模塊,你可以把它作為依賴來(lái)達(dá)成ES2015的支持技即。
比如當(dāng)前環(huán)境不支持Promise著洼,你可以通過(guò)require(‘babel-runtime/core-js/promise’)來(lái)獲取Promise。這很有用但不方便。幸運(yùn)的是身笤,babel-runtime并不是設(shè)計(jì)來(lái)直接使用的——它是和babel-plugin-transform-runtime一起使用的豹悬。babel-plugin-transform-runtime會(huì)自動(dòng)重寫(xiě)你使用Promise的代碼,轉(zhuǎn)換為使用babel-runtime導(dǎo)出(export)的Promise-like對(duì)象液荸。
注意: 所以plugin-transform-runtime一般用于開(kāi)發(fā)(devDependencies)瞻佛,而runtime自身用于部署的代碼(dependencies),兩者配合來(lái)一起工作娇钱。
那么我們什么時(shí)候用babel-polyfill伤柄,什么時(shí)候用babel-runtime?
babel-polyfill會(huì)污染全局空間文搂,并可能導(dǎo)致不同版本間的沖突适刀,而babel-runtime不會(huì)。從這點(diǎn)看應(yīng)該用babel-runtime细疚。
但記住蔗彤,babel-runtime有個(gè)缺點(diǎn)川梅,它不模擬實(shí)例方法疯兼,即內(nèi)置對(duì)象原型上的方法,所以類似Array.prototype.find贫途,你通過(guò)babel-runtime是無(wú)法使用的吧彪。
最后,請(qǐng)不要一次引入全部的polyfills(如require('babel-polyfill'))丢早,這會(huì)導(dǎo)致代碼量很大姨裸。請(qǐng)按需引用最好。
@babel/preset-env
以下出自:github -Babel 7 及新用法
preset-env
是 JS 中的 autoprefixer
根據(jù)環(huán)境來(lái)應(yīng)用不同的plugins怨酝。支持的plugins超過(guò)babel-preset-latest(2015-2017)傀缩。
用法:
{
"presets": [
[
"env",
{
"targets": { // 目標(biāo)環(huán)境
"browsers": [ // 瀏覽器
"last 2 versions",
"ie >= 10"
],
"node": "current" // node
},
"modules": true, // 是否轉(zhuǎn)譯module syntax,默認(rèn)是 commonjs
"debug": true, // 是否輸出啟用的plugins列表
"spec": false, // 是否允許more spec compliant农猬,但可能轉(zhuǎn)譯出的代碼更慢
"loose": false, // 是否允許生成更簡(jiǎn)單es5的代碼赡艰,但可能不那么完全符合ES6語(yǔ)義
"useBuiltIns": false, // 怎么運(yùn)用 polyfill
"include": [], // 總是啟用的 plugins
"exclude": [], // 強(qiáng)制不啟用的 plugins
"forceAllTransforms": false, // 強(qiáng)制使用所有的plugins,用于只能支持ES5的uglify可以正確壓縮代碼
}
]
],
}
這里主要需要注意的是useBuiltIns用于指定怎么處理polyfill斤葱,可以選值"usage" | "entry" | false
慷垮,默認(rèn)是false
。
-
useBuiltIns: 'usage'
:當(dāng)每個(gè)文件里用到(需要polyfill的特性)時(shí)揍堕,在文件中添加特定的import語(yǔ)句料身。這可以保證每個(gè)polyfill的特性僅load一次。
/// input
var a = new Promise(); // a.js
var b = new Map(); // b.js
/// output
// a.js
import "core-js/modules/es6.promise";
var a = new Promise();
// b.js
import "core-js/modules/es6.map";
var b = new Map();
-
useBuiltIns: 'entry'
:替換import "@babel/polyfill" / require("@babel/polyfill")
語(yǔ)句為獨(dú)立的(根據(jù)環(huán)境)需要引入的polyfill特性的import語(yǔ)句衩茸。
// input
import "@babel/polyfill";
// output
import "core-js/modules/es7.string.pad-start";
import "core-js/modules/es7.string.pad-end";
需要注意芹血,在整個(gè)項(xiàng)目中,"@babel/polyfill"只能require一次,否則報(bào)錯(cuò)幔烛。建議用獨(dú)立的entry文件引入隙畜。
-
useBuiltIns: false
:對(duì)@babel/polyfill
不做任何處理。
ReferenceError: regeneratorRuntime is not defined
需要注意说贝,當(dāng)你使用async/await
并被preset-env轉(zhuǎn)譯后议惰,運(yùn)行時(shí)可能會(huì)出現(xiàn)以上錯(cuò)誤,這是因?yàn)椋?br>
plugin-transform-regenerator
使用regenerator
來(lái)轉(zhuǎn)譯 async/generator
函數(shù)乡恕,但是它本身不包括regeneratorRuntime
言询,你需要使用babel-polyfill/regenerator runtime
來(lái)使regeneratorRuntime 存在。
通常情況下傲宜,加上transform-runtime plugin即可运杭。