一. 介紹
1、Babel 是什么
官方:Babel 是一個 JavaScript 編譯器!
我:Babel 是一個源碼到目標(biāo)代碼的轉(zhuǎn)換器!

如圖可以看到, Babel 的作用就是將「源碼」轉(zhuǎn)換為「目標(biāo)代碼」,至于轉(zhuǎn)換中間的過程,下文討論。
2、Babel 的發(fā)展歷史
Babel 在 4.0 版本之前叫做 "6to5",由澳大利亞的 Sebastian (早已離開 Babel 團(tuán)隊,目前就職于 Facebook,依然致力于前端工具鏈開發(fā)中)在他的 2014 年十一月(那是他的高中時期)開始發(fā)布的,通過 6to5 這個名字我們就能知道,最初的 Babel 就是將 ES6 的代碼轉(zhuǎn)換成 ES5 的代碼,2015 年與 ESNext 合并并改名成 Babel。Babel 的本意是 "巴別塔",取自神話小說中。由 6to5 重命名為 Babel 前,6to5 已經(jīng)走過了三個版本,所以改名后的 Babel 從版本 4.0 開始,此時已經(jīng)能夠轉(zhuǎn)譯 ES7 與 JSX,2015 年發(fā)布 5.0,引入了 stage,創(chuàng)建了 plugin system 來支持自定義轉(zhuǎn)譯,同年又發(fā)布 6.0 ,拆分了幾個核心包,引入了 presets 和 plugin/presets options 的概念,18 年,發(fā)布了 7.0,除了性能方面的提升以外,增加了對 typescript 的支持,對安裝包使用「@babel」的命名空間。
3、Babel 的作用
主要用于將采用 ECMAScript 2015+ 語法編寫的代碼轉(zhuǎn)換為 es5 語法,讓開發(fā)者無視用戶瀏覽器的差異性,并且能夠用新的 JS 語法及特性進(jìn)行開發(fā)。除此之外,Babel 能夠轉(zhuǎn)換 JSX 語法,并且能夠支持 TypeScript 轉(zhuǎn)換為 JavaScript。 總結(jié)一下:Babel 的作用如下
- 語法轉(zhuǎn)換
- 通過 Polyfill 方式在目標(biāo)環(huán)境中添加缺失的特性
- 源碼轉(zhuǎn)換
二. Babel7 的使用
1、配置文件
Babel 支持多種形式的配置文件,根據(jù)使用場景不同可以選擇不同的配置文件。如果配置中需要書寫 js 邏輯,可以選擇「babel.config.js」或者 「.babelrc.js」;如果只是需要一個簡單的 key-value 配置,那么可以選擇「.babelrc」,甚至可以直接在 「package.json」 中配置。
這里給出在各種配置文件中配置 Babel 的書寫形式,以 plugins 和 presets 配置為例:
// babel.config.js
module.exports = function(api) {
api.cache(true);
const plugins = ["@babel/plugin-transform-arrow-functions"];
const presets = ["@babel/preset-env"];
return {
plugins,
presets
};
}
// .babelrc.js
const plugins = ["@babel/plugin-transform-arrow-functions"];
const presets = ["@babel/preset-env"];
module.exports = { plugins, presets };
// .babelrc
{
plugins: ["@babel/plugin-transform-arrow-functions"],
presets: ["@babel/preset-env"]
}
// package.json
{
"name": "my-package",
"version": "1.0.0",
// ...省略其他配置
"babel": {
"plugins": ["@babel/plugin-transform-arrow-functions"],
"presets": ["@babel/preset-env"]
}
}
2、小試個牛刀
所有 Babel 的包都發(fā)布在 npm 上,并且名稱以 @babel 為前綴(自從版本 7.0 之后),接下來,我們一起看下 @babel/core 和 @babel/cli 這兩個 npm 包。
- @babel/core - 核心庫,封裝了 Babel 的核心能力
- @babel/cli - 命令行工具, 提供了「babel」 這個命令
接下來我們試著通過 Babel 將 es6 中的「箭頭函數(shù)」語法轉(zhuǎn)換為 es5 中「function 函數(shù)申明」語法:
// 安裝這兩個依賴包
npm install --save-dev @babel/core @babel/cli
/*
package.json 文件中配置 babel 執(zhí)行命令
以下命令含義為:將 src 目錄中的文件經(jīng)過 babel 轉(zhuǎn)換,并將轉(zhuǎn)換后的文件輸出到 lib 目錄中
*/
"script": {
"compile": "babel src --out-dir lib --watch"
}
// src/index.js
const fn = () => {
console.log('xuemingli');
};
// lib/index.js
const fn = () => {
console.log('xuemingli');
};
此時,我們執(zhí)行 npm run compile 后,發(fā)現(xiàn) lib/index.js 中的依然是箭頭函數(shù),說明 src/index.js 中的代碼并沒有沒 babel 轉(zhuǎn)換,為什么?請大家記住一句話:Babel 構(gòu)建在插件之上的。默認(rèn)情況下,Babel 不做任何處理,需要借助插件來完成語法的解析,轉(zhuǎn)換,輸出。
3、插件
接下來我們一起嘗試使用 @babel/plugin-transform-arrow-functions 插件來將箭頭函數(shù)轉(zhuǎn)換成函數(shù)聲明:
//.babelrc
{
"plugins": ["@babel/plugin-transform-arrow-functions"]
}
// lib/index.js
const fn = function () {
console.log('xuemingli');
};
再次執(zhí)行 npm run compile 后發(fā)現(xiàn) lib/index.js 中已經(jīng)是函數(shù)聲明了,說明 src/index.js 中的代碼被 babel 轉(zhuǎn)換了。到此為止,大家可能想迫切的知道插件究竟做了什么事,別急,請大家慢慢往后看。
插件的配置形式常見有兩種,分別是字符串格式與數(shù)組格式(見下面代碼),并且可以傳遞參數(shù),如果插件名稱為 @babel/plugin-XXX,可以使用簡寫成@babel/XXX,例如 @babel/plugin-transform-arrow-functions 便可以簡寫成 @babel/transform-arrow-functions。請大家再記住一句話: 插件的執(zhí)行順序是從前往后。
// .babelrc
/*
* 以下三個插件的執(zhí)行順序是:
@babel/proposal-class-properties ->
@babel/syntax-dynamic-import ->
@babel/plugin-transform-arrow-functions
*/
{
"plugins": [
// 同 "@babel/plugin-proposal-class-properties"
"@babel/proposal-class-properties",
// 同 ["@babel/plugin-syntax-dynamic-import"]
["@babel/syntax-dynamic-import"],
[
"@babel/plugin-transform-arrow-functions",
{
"loose": true
}
]
]
}
到此為止,我們了解了插件的使用,對于特定的 ES6+ 語法,我們需要使用特定的插件將其轉(zhuǎn)換為 ES5 中的代碼,設(shè)想一下,如果我們的項(xiàng)目中使用了大量的 ES6+ 語法,我們是不是需要一個個的配置相應(yīng)的插件呢?當(dāng)然不需要,那如何解決呢?有請 「預(yù)設(shè)」出場。
4、預(yù)設(shè)
預(yù)設(shè)是一組插件的集合。與插件類似,預(yù)設(shè)的配置形式也是字符串和數(shù)組兩種(見下面代碼),預(yù)設(shè)也可以將 @babel/preset-XXX 簡寫為 @babel/XXX 。預(yù)設(shè)的執(zhí)行順序是從后往前,并且插件在預(yù)設(shè)之前執(zhí)行。
我們常見的預(yù)設(shè)有以下幾種:
- @babel/preset-env: 可以無視瀏覽器環(huán)境的差異而盡情地使用 ES6+ 新語法和新特性;
- 注:語法和特性不是一回事,語法上的迭代是讓我們書寫代碼更加簡單和方便,如展開運(yùn)算符、類,結(jié)構(gòu)等,因此這些語法稱為語法糖;特性上的迭代是為了擴(kuò)展語言的能力,如 Map、Promise 等,事實(shí)上,Babel 對新語法和新特性的處理也是不一樣的,對于新語法,Babel 通過插件直接轉(zhuǎn)換,而對于新特性,Babel 還需要借助 polyfill 來處理和轉(zhuǎn)換。
- @babe/preset-react: 可以書寫 JSX 語法,將 JSX 語法轉(zhuǎn)換為 JS 語法;
- @babel/preset-typescript:可以使用 TypeScript 編寫程序,將 TS 轉(zhuǎn)換為 JS;
- 注:該預(yù)設(shè)只是將 TS 轉(zhuǎn)為 JS,不做任何類型檢查
- @babel/preset-flow:可以使用 Flow 來控制類型,將 Flow 轉(zhuǎn)換為 JS;
預(yù)設(shè)使用示例如下:
// .babelrc
/*
* 以下配置中,插件比預(yù)設(shè)先執(zhí)行
* 預(yù)設(shè)的執(zhí)行順序?yàn)椋? @babel/preset-react ->
@babel/preset-typescript ->
@babel/preset-env
*/
{
"plugins": [
// 同 "@babel/plugin-proposal-class-properties"
"@babel/proposal-class-properties",
// 同 ["@babel/plugin-syntax-dynamic-import"]
["@babel/syntax-dynamic-import"],
[
"@babel/plugin-transform-arrow-functions",
{
"loose": true
}
]
],
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": {
"version": 3,
"proposals": true // 使用尚在提議階段特性的 polyfill
}
}
],
"@babel/preset-typescript",
// 同 @babel/preset-react
"@babel/react"
]
}
對于 @babel/preset-env ,我們通常需要設(shè)置目標(biāo)瀏覽器環(huán)境,可以在根目錄下的 .browserslistrc 文件中設(shè)置,也可以在該預(yù)設(shè)的參數(shù)選項(xiàng)中通過 targets(優(yōu)先級最高) 或者在 package.json 中通過 browserslist 設(shè)置。如果我們不設(shè)置的話,該預(yù)設(shè)默認(rèn)會將所有的 ES6+ 的新語法(注意:這里我說的只是新語法,不包含新特性)全部做轉(zhuǎn)換,反之,該預(yù)設(shè)只會對目標(biāo)瀏覽器環(huán)境不兼容的新語法做轉(zhuǎn)換。我推薦設(shè)置目標(biāo)瀏覽器環(huán)境,這樣在中大型項(xiàng)目中可以明顯縮小編譯后的代碼體積,因?yàn)橛行┬抡Z法的轉(zhuǎn)換需要引入一些額外定義的 helper 函數(shù)的,比如 class。
目標(biāo)瀏覽器配置示例如下:
// .browserslistrc
> 0.25%
not dead
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 0.25%, not dead"
}
]
]
}
// package.json
{
"name": "my-package",
"version": "1.0.0",
// ...省略其他配置
"browserslist": "> 0.25%, not dead"
}
設(shè)置不同目標(biāo)瀏覽器環(huán)境,編譯情況示例如下:
src/index.js 中的是源代碼,lib/index.js 是轉(zhuǎn)換后的目標(biāo)代碼:
// src/index.js
// 新語法
const fn = () => {
console.log('xuemingli');
};
const arr = ["name", "age"];
const [a, b] = arr;
const arr2 = [...arr, "school"];
const arr3 = [["key1", "value1"], ["key2", "value2"]]
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName() {
console.log(this.name);
}
}
const person = new Person('xuemingli', '26');
person.sayName();
// 新特性
const isHas = [1,2,3].includes(2);
const resArr = [1,[2,3]].flat();
const p = new Promise((resolve, reject) => {
resolve(100);
});
const map = new Map(arr3);
const value1 = map.get('key1');
const arr4 = Object.keys({ name: 'xuemingli', age: 26 })
結(jié)論 1:通過如下轉(zhuǎn)換后的目標(biāo)代碼可以驗(yàn)證:如果不設(shè)置瀏覽器環(huán)境的話,@babel/preset-env 會將新語法全部轉(zhuǎn)換,并且對于一些特殊的新語法,如 class,還額外定義了一些 helper 函數(shù),而新特性并沒有做任何轉(zhuǎn)換。
/*
lib/index.js
不設(shè)置目標(biāo)瀏覽器環(huán)境
*/
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
// 新語法
var fn = function fn() {
console.log('xuemingli');
};
var arr = ["name", "age"];
var a = arr[0],
b = arr[1];
var arr2 = [].concat(arr, ["school"]);
var arr3 = [["key1", "value1"], ["key2", "value2"]];
var Person = /*#__PURE__*/function () {
function Person(name, age) {
_classCallCheck(this, Person);
this.name = name;
this.age = age;
}
_createClass(Person, [{
key: "sayName",
value: function sayName() {
console.log(this.name);
}
}]);
return Person;
}();
var person = new Person('xuemingli', '26');
person.sayName();
// 新特性
var isHas = [1, 2, 3].includes(2);
var resArr = [1, [2, 3]].flat();
var p = new Promise(function (resolve, reject) {
resolve(100);
});
var map = new Map(arr3);
var value1 = map.get('key1');
var arr4 = Object.keys({
name: 'xuemingli',
age: 26
});
結(jié)論 2:通過如下轉(zhuǎn)換后的目標(biāo)代碼可以驗(yàn)證:如果設(shè)置了目標(biāo)瀏覽器環(huán)境,@babel/preset-env 只會對目標(biāo)瀏覽器環(huán)境不兼容的新語法做轉(zhuǎn)換。如代碼所示,當(dāng)前目標(biāo)瀏覽器環(huán)境下,該預(yù)設(shè)只轉(zhuǎn)換了「結(jié)構(gòu)賦值」語法,而其他新語法在已經(jīng)被瀏覽器實(shí)現(xiàn)了,便不需要轉(zhuǎn)換了。
/*
lib/index.js
目標(biāo)瀏覽器環(huán)境設(shè)置為 last 53 Chrome versions
*/
"use strict";
// 新語法
const fn = () => {
console.log('xuemingli');
};
const arr = ["name", "age"];
const a = arr[0],
b = arr[1];
const arr2 = [...arr, "school"];
const arr3 = [["key1", "value1"], ["key2", "value2"]];
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName() {
console.log(this.name);
}
}
const person = new Person('xuemingli', '26');
person.sayName();
// 新特性
const isHas = [1, 2, 3].includes(2);
const resArr = [1, [2, 3]].flat();
const p = new Promise((resolve, reject) => {
resolve(100);
});
const map = new Map(arr3);
const value1 = map.get('key1');
const arr4 = Object.keys({
name: 'xuemingli',
age: 26
});
對于新特性,@babel/preset-env 能否轉(zhuǎn)換呢?答案當(dāng)然是能的。但是需要通過 useBuiltIns 這個參數(shù)選項(xiàng)實(shí)現(xiàn),值需要設(shè)置為 usage,這樣的話,只會轉(zhuǎn)換我們使用到的新語法和新特性(注意:這里既包括新語法也包括新特性),能夠有效減小編譯后的包體積,并且還要設(shè)置 corejs: { version: 3, proposals } 選項(xiàng),因?yàn)檗D(zhuǎn)換新特性需要用到 polyfill,而 corejs 就是一個 polyfill 包。如果不顯示指定 corejs 的版本的話,默認(rèn)使用的是 version 2 ,而 version 2 已經(jīng)停更,諸如一些更新的特性的 polyfill 只會更行與 version 3 里,如 Array.prototype.flat()。
// .babelrc
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": {
"version": 3,
"proposals": true // 使用尚在提議階段特性的 polyfill
}
}
]
]
// lib/index.js
// 從 corejs 這個包里引入了 polyfill 并對新特性做了轉(zhuǎn)換
"use strict";
require("core-js/modules/es.object.define-property.js");
require("core-js/modules/es.array.concat.js");
require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.array.flat.js");
require("core-js/modules/es.array.unscopables.flat.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.map.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/esnext.map.delete-all.js");
require("core-js/modules/esnext.map.every.js");
require("core-js/modules/esnext.map.filter.js");
require("core-js/modules/esnext.map.find.js");
require("core-js/modules/esnext.map.find-key.js");
require("core-js/modules/esnext.map.includes.js");
require("core-js/modules/esnext.map.key-of.js");
require("core-js/modules/esnext.map.map-keys.js");
require("core-js/modules/esnext.map.map-values.js");
require("core-js/modules/esnext.map.merge.js");
require("core-js/modules/esnext.map.reduce.js");
require("core-js/modules/esnext.map.some.js");
require("core-js/modules/esnext.map.update.js");
require("core-js/modules/web.dom-collections.iterator.js");
require("core-js/modules/es.object.keys.js");
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
// 新語法
var fn = function fn() {
console.log('xuemingli');
};
var arr = ["name", "age"];
var a = arr[0],
b = arr[1];
var arr2 = [].concat(arr, ["school"]);
var arr3 = [["key1", "value1"], ["key2", "value2"]];
var Person = /*#__PURE__*/function () {
function Person(name, age) {
_classCallCheck(this, Person);
this.name = name;
this.age = age;
}
_createClass(Person, [{
key: "sayName",
value: function sayName() {
console.log(this.name);
}
}]);
return Person;
}();
var person = new Person('xuemingli', '26');
person.sayName();
// 新特性
var isHas = [1, 2, 3].includes(2);
var resArr = [1, [2, 3]].flat();
var p = new Promise(function (resolve, reject) {
resolve(100);
});
var map = new Map(arr3);
var value1 = map.get('key1');
var arr4 = Object.keys({
name: 'xuemingli',
age: 26
});
以下示例演示了使用 @babel/preset-react 、@babel/preset-typescript 以及 @babel/preset-env 來轉(zhuǎn)換 src/index.tsx 中的源代碼,lib/index.js 中的是轉(zhuǎn)換后的目標(biāo)代碼:
// .babelrc
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": {
"version": 3,
"proposals": true // 使用尚在提議階段特性的 polyfill
}
}
],
"@babel/preset-typescript", "@babel/react"
]
// src/index.tsx
import React, { useState } from 'react';
const App: React.FC = () => {
const [value, setValue] = useState(0);
const fn = (): void => {
setValue(value => value++);
};
const arr: string[] = ["name", "age"];
const [a, b] = arr;
const arr2: string[] = [...arr, "school"];
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayName() {
console.log(this.name);
}
}
const person = new Person('xuemingli', 26);
person.sayName();
// 新特性
const isHas: boolean = [1,2,3].includes(2);
const resArr: number[] = [1,[2,3]].flat();
const p: Promise<number> = new Promise((resolve, reject) => {
resolve(100);
});
const map: Map<string, string> = new Map();
map.set('key1', 'value1');
const arr4: string[] = Object.keys({ name: 'xuemingli', age: 26 })
return (
<div>
<div>{value}</div>
<button onClick={fn}>+</button>
</div>
)
};
export default App;
// lib/index.js
"use strict";
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
require("core-js/modules/es.symbol.js");
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.symbol.iterator.js");
require("core-js/modules/es.array.slice.js");
require("core-js/modules/es.array.from.js");
require("core-js/modules/es.regexp.exec.js");
require("core-js/modules/es.object.define-property.js");
require("core-js/modules/es.weak-map.js");
require("core-js/modules/esnext.weak-map.delete-all.js");
require("core-js/modules/es.object.get-own-property-descriptor.js");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
require("core-js/modules/es.array.concat.js");
require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.array.flat.js");
require("core-js/modules/es.array.unscopables.flat.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.map.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/esnext.map.delete-all.js");
require("core-js/modules/esnext.map.every.js");
require("core-js/modules/esnext.map.filter.js");
require("core-js/modules/esnext.map.find.js");
require("core-js/modules/esnext.map.find-key.js");
require("core-js/modules/esnext.map.includes.js");
require("core-js/modules/esnext.map.key-of.js");
require("core-js/modules/esnext.map.map-keys.js");
require("core-js/modules/esnext.map.map-values.js");
require("core-js/modules/esnext.map.merge.js");
require("core-js/modules/esnext.map.reduce.js");
require("core-js/modules/esnext.map.some.js");
require("core-js/modules/esnext.map.update.js");
require("core-js/modules/web.dom-collections.iterator.js");
require("core-js/modules/es.object.keys.js");
var _react = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]( "Symbol.iterator") method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
var App = function App() {
var _useState = (0, _react.useState)(0),
_useState2 = _slicedToArray(_useState, 2),
value = _useState2[0],
setValue = _useState2[1];
var fn = function fn() {
setValue(function (value) {
return value++;
});
};
var arr = ["name", "age"];
var a = arr[0],
b = arr[1];
var arr2 = [].concat(arr, ["school"]);
var Person = /*#__PURE__*/function () {
function Person(name, age) {
_classCallCheck(this, Person);
this.name = name;
this.age = age;
}
_createClass(Person, [{
key: "sayName",
value: function sayName() {
console.log(this.name);
}
}]);
return Person;
}();
var person = new Person('xuemingli', 26);
person.sayName();
// 新特性
var isHas = [1, 2, 3].includes(2);
var resArr = [1, [2, 3]].flat();
var p = new Promise(function (resolve, reject) {
resolve(100);
});
var map = new Map();
map.set('key1', 'value1');
var arr4 = Object.keys({
name: 'xuemingli',
age: 26
});
return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("div", null, value), /*#__PURE__*/_react.default.createElement("button", {
onClick: fn
}, "+"));
};
var _default = App;
exports.default = _default;
到此為止,我們了解了預(yù)設(shè)的概念和使用方式。雖然 @babel/env 可以幫我們做新語法和新特性的按需轉(zhuǎn)換,但是依然存在 2 個問題:
- 從 corejs 引入的 polyfill 是全局范圍的,不是模塊作用域返回的,可能存在污染全局變量的風(fēng)險,如上例中的 require("core-js/modules/es.promise.js");
- 對于某些新語法,如 class,會在編譯后的文件中注入很多 helper 函數(shù)聲明,而不是從某個地方 require 進(jìn)來的函數(shù)引用,從而增大編譯后的包體積;
能不能解決如上兩個問題呢?當(dāng)然能,有請 runtime 出場。
5、runtime
runtime 是 babel7 提出來的概念,旨在解決如上提出的性能問題的。接下來我們實(shí)踐一下 @babel/plugin-transform-runtime 插件配合 @babel/preset-env 使用,示例如下:
npm install --save-dev @babel/plugin-transform-runtime
// @babel/runtime 是要安裝到生產(chǎn)依賴的,因?yàn)樾绿匦缘木幾g需要從這個包里引用 polyfill
// 不錯,它就是一個封裝了 corejs 的 polyfill 包
npm install --save @babel/runtime
// .babelrc
{
"presets": [
"@babel/env"
],
"plugins": [
[
"@babel/plugin-transform-runtime",{
"corejs": 3
}
]
],
}
// src/index.js
// 新語法
const fn = () => {
console.log('xuemingli');
};
const arr = ["name", "age"];
const [a, b] = arr;
const arr2 = [...arr, "school"];
const arr3 = [["key1", "value1"], ["key2", "value2"]]
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName() {
console.log(this.name);
}
}
const person = new Person('xuemingli', '26');
person.sayName();
// 新特性
const isHas = [1,2,3].includes(2);
const resArr = [1,[2,3]].flat();
const p = new Promise((resolve, reject) => {
resolve(100);
});
const map = new Map(arr3);
const value1 = map.get('key1');
const arr4 = Object.keys({ name: 'xuemingli', age: 26 })
npm run compile 編譯后,可以明顯看到,引入的 polyfill 不再是全局范圍內(nèi)的了,而是模塊作用域范圍內(nèi)的;并且不再是往編譯文件中直接注入 helper 函數(shù)了,而是通過引用的方式,既解決了全局變量污染的問題,又減小了編譯后包的體積。
// lib/index.js
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _flat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/flat"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/map"));
var _keys = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/keys"));
var _context, _context2, _context3;
// 新語法
var fn = function fn() {
console.log('xuemingli');
};
var arr = ["name", "age"];
var a = arr[0],
b = arr[1];
var arr2 = (0, _concat.default)(_context = []).call(_context, arr, ["school"]);
var arr3 = [["key1", "value1"], ["key2", "value2"]];
var Person = /*#__PURE__*/function () {
function Person(name, age) {
(0, _classCallCheck2.default)(this, Person);
this.name = name;
this.age = age;
}
(0, _createClass2.default)(Person, [{
key: "sayName",
value: function sayName() {
console.log(this.name);
}
}]);
return Person;
}();
var person = new Person('xuemingli', '26');
person.sayName();
// 新特性
var isHas = (0, _includes.default)(_context2 = [1, 2, 3]).call(_context2, 2);
var resArr = (0, _flat.default)(_context3 = [1, [2, 3]]).call(_context3);
var p = new _promise.default(function (resolve, reject) {
resolve(100);
});
var map = new _map.default(arr3);
var value1 = map.get('key1');
var arr4 = (0, _keys.default)({
name: 'xuemingli',
age: 26
});
6、結(jié)合 webpack
在 webpack 中,通過 babel-loader 的方式來接入 babel 的能力,babel 配置文件與單獨(dú)使用 babel 時相同。在 webpack 中使用 babel 時建議開啟 babel 的緩存能力,即 cacheDirectory,統(tǒng)計顯示,在大型項(xiàng)目中,構(gòu)建速度可以提升 1 倍,webpack 的配置文件示例如下:
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [
{
test: /\.[j,t]sx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader?cacheDirectory'
}
}
]
},
};