2020年,你不得不知道的 babel 那些事兒
前言
隨著 Nodejs 的崛起,編譯這個(gè)昔日在 Java、C++ 等語(yǔ)言中流行的詞,在前端也逐漸火了起來(lái),現(xiàn)在一個(gè)前端項(xiàng)目在開(kāi)發(fā)過(guò)程中沒(méi)有編譯環(huán)節(jié),總感覺(jué)這個(gè)項(xiàng)目是沒(méi)有靈魂的。說(shuō)起前端編譯就不得不提前端編譯界的扛把子 Babel ,大部分前端攻城獅對(duì) Babel 并不陌生,但是在這個(gè) Ctrl+C ?和 Ctrl+V 的年代,大多數(shù)人對(duì)它也只是知道、了解或者聽(tīng)過(guò),少數(shù)可能配置過(guò) Babel,但也僅此而已。作為一個(gè)有想法和靈魂的前端攻城獅僅僅知道這些是不夠的,你需要對(duì) Babel 有一個(gè)系統(tǒng)的了解,今天就來(lái)聊聊 Babel 那些事兒。
什么是 Babel?
官方的解釋 Babel 是一個(gè) JavaScript 編譯器,用于將 ECMAScript 2015+ 版本的代碼轉(zhuǎn)換為向后兼容的 JavaScript 語(yǔ)法,以便能夠運(yùn)行在當(dāng)前版本和舊版本的瀏覽器或其他環(huán)境中。簡(jiǎn)單來(lái)說(shuō) Babel 的工作就是:
- 語(yǔ)法轉(zhuǎn)換
- 通過(guò) Polyfill 的方式在目標(biāo)環(huán)境中添加缺失的特性
- JS 源碼轉(zhuǎn)換
Babel 的基本原理
原理很簡(jiǎn)單,核心就是 AST (抽象語(yǔ)法樹(shù)) (https://segmentfault.com/a/1190000016231512?utm_source=tag-newest)。首先將源碼轉(zhuǎn)成抽象語(yǔ)法樹(shù),然后對(duì)語(yǔ)法樹(shù)進(jìn)行處理生成新的語(yǔ)法樹(shù),最后將新語(yǔ)法樹(shù)生成新的 JS 代碼,整個(gè)編譯過(guò)程可以分為 3 個(gè)階段 parsing (解析)、transforming (轉(zhuǎn)換)、generating (生成),都是在圍繞著 AST 去做文章,話不多說(shuō)上圖:
圖片整個(gè)過(guò)程很清晰,但是,好多東西都是看著簡(jiǎn)單,但是實(shí)現(xiàn)起來(lái)賊復(fù)雜,比如這里說(shuō)到的 AST,要是你覺(jué)得你對(duì) AST 已經(jīng)信手拈來(lái)了,老哥麻煩在下面留下聯(lián)系方式,我要來(lái)找你要簡(jiǎn)歷。言歸正傳,這里提一下,Babel 只負(fù)責(zé)編譯新標(biāo)準(zhǔn)引入的新語(yǔ)法,比如 Arrow function、Class、ES Modul 等,它不會(huì)編譯原生對(duì)象新引入的方法和 API,比如 Array.includes,Map,Set 等,這些需要通過(guò) Polyfill 來(lái)解決,文章后面會(huì)提到。
Babel 的使用
運(yùn)行 babel 所需的基本環(huán)境
babel/cli
npm install i -S @babel/cli@babel/cli 是 Babel 提供的內(nèi)建命令行工具。提到 @babel/cli 這里就不得不提一下 @babel/node ,這哥倆雖然都是命令行工具,但是使用場(chǎng)景不同,babel/cli 是安裝在項(xiàng)目中,而 @babel/node 是全局安裝。
@babel/core
npm install i -S @babel/core安裝完 @babel/cli 后就在項(xiàng)目目錄下執(zhí)行
babel test.js會(huì)報(bào)找不到 @babel/core 的錯(cuò)誤,因?yàn)?@babel/cli 在執(zhí)行的時(shí)候會(huì)依賴(lài) @babel/core 提供的生成 AST 相關(guān)的方法,所以安裝完 @babel/cli 后還需要安裝 @babel/core。安裝完這兩個(gè)插件后,如果在 Mac 環(huán)境下執(zhí)行會(huì)出現(xiàn)
command not found: babel,這是因?yàn)?@babel/cli是安裝在項(xiàng)目下,而不是全局安裝,所以無(wú)法直接使用 Babel 命令,需要在 package.json 文件中加上下面這個(gè)配置項(xiàng):
然后執(zhí)行"scripts": {
"babel":"babel"
}npm run babel ./test.js,順利生成代碼,此時(shí)生成的代碼并沒(méi)有被編譯,因?yàn)?Babel 將原來(lái)集成一體的各種編譯功能分離出去,獨(dú)立成插件,要編譯文件需要安裝對(duì)應(yīng)的插件或者預(yù)設(shè),我們經(jīng)常看見(jiàn)的什么 @babel/preset-stage-0、@babel/preset-stage-1,@babel/preset-env 等就是干這些活的。那這些插件和預(yù)設(shè)怎么用呢?下面就要說(shuō)到 Babel 的配置文件了,這些插件需要在配置文件中交代清楚,不然 Babel 也不知道你要用哪些插件和預(yù)設(shè)。
安裝完基本的包后,就是配置 Babel 配置文件,Babel 的配置文件有四種形式:
babel.config.js
在項(xiàng)目的根目錄(package.json文件所在目錄)下創(chuàng)建一個(gè)名為 babel.config.js 的文件,并輸入如下內(nèi)容。
具體 babel.config.js 配置 (https://www.babeljs.cn/docs/config-files#project-wide-configuration)module.exports = function (api) {
api.cache(true);
const presets = [ ... ];
const plugins = [ ... ];
return {
presets,
plugins
};
}.babelrc
在你的項(xiàng)目中創(chuàng)建名為.babelrc的文件
.babelrc 文檔 (https://www.babeljs.cn/docs/config-files#file-relative-configuration){
"presets": [...],
"plugins": [...]
}.babelrc.js
與 .babelrc 的配置相同,你可以使用 JavaScript 語(yǔ)法編寫(xiě)。const presets = [ ... ];
const plugins = [ ... ];
module.exports = { presets, plugins };- package.json還可以選擇將 .babelrc 中的配置信息寫(xiě)到
package.json文件中{
...
"babel": {
"presets": [ ... ],
"plugins": [ ... ],
}
}
插件(Plugins)
插件是用來(lái)定義如何轉(zhuǎn)換你的代碼的。在 Babel 的配置項(xiàng)中填寫(xiě)需要使用的插件名稱(chēng),Babel 在編譯的時(shí)候就會(huì)去加載 node_modules 中對(duì)應(yīng)的 npm 包,然后編譯插件對(duì)應(yīng)的語(yǔ)法。
.babelrc{
"plugins": ["transform-decorators-legacy", "transform-class-properties"]
}
插件執(zhí)行順序
插件在預(yù)設(shè)(Presets) 前運(yùn)行。
插件的執(zhí)行順序是從左往右執(zhí)行。也就是說(shuō)在上面的示例中,Babel 在進(jìn)行 AST 遍歷的時(shí)候會(huì)先調(diào)用 transform-decorators-legacy 插件中定義的轉(zhuǎn)換方法,然后再調(diào)用 transform-class-properties 中的方法。
插件傳參
參數(shù)是由插件名稱(chēng)和參數(shù)對(duì)象組成的一個(gè)數(shù)組。{
"plugins": [
[
"@babel/plugin-proposal-class-properties",
{ "loose": true }
]
]
}
插件名稱(chēng)
插件名稱(chēng)如果為 @babel/plugin-XX,可以使用短名稱(chēng)@babel/XX ,如果為 babel-plugin-xx,可以直接使用 xx。
自定義插件
大部分時(shí)間我們都是在用別人的寫(xiě)的插件,但是有時(shí)候我們總是想秀一下,自己寫(xiě)一個(gè) Babel 插件,那應(yīng)該怎么操作呢?
插件加載
要致富先修路,要用自己寫(xiě)的插件首先得知道怎么使用自定義的插件。一種方式是將自己寫(xiě)的插件發(fā)布到 npm 倉(cāng)庫(kù)中去,然后本地安裝,然后在 Babel 配置文件中配置插件名稱(chēng)就好了:
npm install @babel/plugin-myPlugin
{
"plugins": ["@babel/plugin-myPlugin"]
}
另外一種方式就是不發(fā)布,直接將寫(xiě)好的插件放在項(xiàng)目中,然后在 babel 配置文件中通過(guò)訪問(wèn)相對(duì)路徑的方式來(lái)加載插件:.babelrc{
"plugins": ["./plugins/plugin-myPlugin"]
}
第一種通過(guò) npm 包的方式一般是插件功能已經(jīng)完善和穩(wěn)定后使用,第二種方式一般在開(kāi)發(fā)階段,本地調(diào)試時(shí)使用。編寫(xiě)插件
插件實(shí)際上就是在處理 AST 抽象語(yǔ)法樹(shù),所以編寫(xiě)插件只需要做到下面三點(diǎn):
確認(rèn)我們要修改的節(jié)點(diǎn)類(lèi)型
找到 AST 中需要修改的屬性
將 AST 中需要修改的屬性用新生成的屬性對(duì)象替換
const result = 1 + 2;
轉(zhuǎn)換成:
const result = 3;
在寫(xiě)插件前你需要明確轉(zhuǎn)換前后的 AST 長(zhǎng)什么樣子,就好像整容一樣,你總得選個(gè)參考吧。AST explorer (https://astexplorer.net/) 你值得擁有。轉(zhuǎn)換前:
圖片轉(zhuǎn)換后:
圖片找到差別,然后就到了用代碼來(lái)解決問(wèn)題的時(shí)候了let babel = require('@babel/core');
let t = require('babel-types');
let preCalculator={
visitor: {
BinaryExpression(path) {
let node = path.node;
let left = node.left;
let operator = node.operator;
let right = node.right;
if (!isNaN(left.value) && !isNaN(right.value)) {
let result = eval(left.value + operator + right.value);
//生成新節(jié)點(diǎn),然后替換原先的節(jié)點(diǎn)
path.replaceWith(t.numericLiteral(result));
//遞歸處理 如果當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)配型還是表達(dá)式
if (path.parent && path.parent.type == 'BinaryExpression') {
preCalculator.visitor.BinaryExpression.call(null,path.parentPath);
}
}
}
}
}
const result = babel.transform('const sum = 1+2+3',{
plugins:[
preCalculator
]
});
上面這段代碼,Babel 在編譯的時(shí)候會(huì)深度遍歷 AST 對(duì)象的每一個(gè)節(jié)點(diǎn),采用訪問(wèn)者的模式,每個(gè)節(jié)點(diǎn)都會(huì)去訪問(wèn)插件定義的方法,如果類(lèi)型和方法中定義的類(lèi)型匹配上了,就進(jìn)入該方法修改節(jié)點(diǎn)中對(duì)應(yīng)屬性。在節(jié)點(diǎn)遍歷完成后,新的 AST 對(duì)象也就生成了。babel-types (https://www.npmjs.com/package/babel-types) 提供 AST 樹(shù)節(jié)點(diǎn)類(lèi)型對(duì)象。上面這樣寫(xiě)只是為了我們開(kāi)發(fā)測(cè)試方便,其實(shí)最終的完整體是下面這樣的:const types = require('babel-types');
const visitor = {
BinaryExpression(path) {//需要處理的節(jié)點(diǎn)路徑
let node=path.node;
let left=node.left;
let operator=node.operator;
let right=node.right;
if (!isNaN(left.value) && !isNaN(right.value)) {
let result=eval(left.value+operator+right.value);
path.replaceWith(t.numericLiteral(result));
if (path.parent&& path.parent.type == 'BinaryExpression') {
preCalculator.visitor.BinaryExpression.call(null,path.parentPath);
}
}
}
}
module.exports = function(babel){
return {
visitor
}
}
我們?cè)诓寮兄恍枰薷钠ヅ渖系?AST 屬性,不需要關(guān)注源碼到 AST 以及新 AST 到源碼的過(guò)程,這些都是 Babel 去干的事,我們干好自己的活就好了,其他的交給 babel。這也就解釋了我上面的步驟中為嘛沒(méi)有 AST 的生成和源碼的生成,那就不是我們?cè)诓寮懈傻氖聝骸?h2 style="font-weight:bold;font-size:24px;color:rgb(89,89,89);">預(yù)設(shè)(Presets)預(yù)設(shè)就是一堆插件(Plugin)的組合,從而達(dá)到某種轉(zhuǎn)譯的能力,就比如 react 中使用到的 @babel/preset-react ,它就是下面幾種插件的組合。
- @babel/plugin-syntax-jsx
- @babel/plugin-transform-react-jsx
- @babel/plugin-transform-react-display-name
{
"plugins":["@babel/plugin-syntax-jsx","@babel/plugin-transform-react-jsx","@babel/plugin-transform-react-display-name"]
}
但是這樣一方面顯得不那么優(yōu)雅,另一方面增加了使用者的使用難度。如果直接使用預(yù)設(shè)就清新脫俗多了~
{
"presets":["@babel/preset-react"]
}
預(yù)設(shè)(Presets)的執(zhí)行順序
前面提到插件的執(zhí)行順序是從左往右,而預(yù)設(shè)的執(zhí)行順序恰好反其道行之,它是從右往左{
"presets": [
"a",
"b",
"c"
]
}
它的執(zhí)行順序是 c、b、a,是不是有點(diǎn)奇怪,這主要是為了確保向后兼容,因?yàn)榇蠖鄶?shù)用戶將 "es2015" 放在 "stage-0" 之前。自定義預(yù)設(shè)(Presets)
這種場(chǎng)景一般很少,在這個(gè)拿來(lái)主義的時(shí)代,插件我們都很少寫(xiě),就更別說(shuō)自定義預(yù)設(shè)了。不過(guò)前面插件我們都說(shuō)了怎么寫(xiě)了,預(yù)設(shè)咱也不能冷落她呀。
前面提到預(yù)設(shè)就是已有插件的組合,主要就是為了避免使用者配置過(guò)多的插件,通過(guò)預(yù)設(shè)把插件收斂起來(lái),其實(shí)寫(xiě)起來(lái)特別簡(jiǎn)單,前提是你已經(jīng)確定好要用哪些插件了。import { declare } from "@babel/helper-plugin-utils";
import pluginA from "myPluginA";
import pluginB from "myPluginB"
export default declare((api, opts) => {
const pragma = opts.pragma;
return {
plugins: [
[
pluginA,
{pragma}//插件傳參
],
pluginB
]
};
});
其實(shí)就是把 Babel 配置中的 plugins 配置放到 presets 中了,實(shí)質(zhì)上還是在配置 Plugins,只是寫(xiě) Presets 的人幫我們配置好了。那些她認(rèn)識(shí)你而你不認(rèn)識(shí)她的預(yù)設(shè)
@babel/preset-stage-xxx
@babel/preset-stage-xxx 是 ES 在不同階段語(yǔ)法提案的轉(zhuǎn)碼規(guī)則而產(chǎn)生的預(yù)設(shè),隨著被批準(zhǔn)為 ES 新版本的組成部分而進(jìn)行相應(yīng)的改變(例如 ES6/ES2015)。
提案分為以下幾個(gè)階段:
- stage-0 - 設(shè)想(Strawman):只是一個(gè)想法,可能有 Babel 插件,stage-0 的功能范圍最廣大,包含 stage-1 , stage-2 以及 stage-3 的所有功能
- stage-1 - 建議(Proposal):這是值得跟進(jìn)的
- stage-2 - 草案(Draft):初始規(guī)范
- stage-3 - 候選(Candidate):完成規(guī)范并在瀏覽器上初步實(shí)現(xiàn)
- stage-4 - 完成(Finished):將添加到下一個(gè)年度版本發(fā)布中
@babel/preset-es2015
preset-es2015 是僅包含 ES6 功能的 Babel 預(yù)設(shè)。
實(shí)際上在 Babel7 出來(lái)后上面提到的這些預(yù)設(shè) stage-x,preset-es2015 都可以廢棄了,因?yàn)?@babel/preset-env 出來(lái)一統(tǒng)江湖了。
@babel/preset-env
前面兩個(gè)預(yù)設(shè)是從 ES 標(biāo)準(zhǔn)的維度來(lái)確定轉(zhuǎn)碼規(guī)則的,而 @babel/preset-env 是根據(jù)瀏覽器的不同版本中缺失的功能確定代碼轉(zhuǎn)換規(guī)則的,在配置的時(shí)候我們只需要配置需要支持的瀏覽器版本就好了,@babel/preset-env 會(huì)根據(jù)目標(biāo)瀏覽器生成對(duì)應(yīng)的插件列表然后進(jìn)行編譯:{
"presets": [
["env", {
"targets": {
"browsers": ["last 10 versions", "ie >= 9"]
}
}],
],
...
}在默認(rèn)情況下 @babel/preset-env 支持將 JS 目前最新的語(yǔ)法轉(zhuǎn)成 ES5,但需要注意的是,如果你代碼中用到了還沒(méi)有成為 JS 標(biāo)準(zhǔn)的語(yǔ)法,該語(yǔ)法暫時(shí)還處于 stage 階段,這個(gè)時(shí)候還是需要安裝對(duì)應(yīng)的 stage 預(yù)設(shè),不然編譯會(huì)報(bào)錯(cuò)。
{
"presets": [
["env", {
"targets": {
"browsers": ["last 10 versions", "ie >= 9"]
}
}],
],
"stage-0"
}雖然可以采用默認(rèn)配置,但如果不需要照顧所有的瀏覽器,還是建議你配置目標(biāo)瀏覽器和環(huán)境,這樣可以保證編譯后的代碼體積足夠小,因?yàn)樵谟械陌姹緸g覽器中,新語(yǔ)法本身就能執(zhí)行,不需要編譯。@babel/preset-env 在默認(rèn)情況下和 preset-stage-x 一樣只編譯語(yǔ)法,不會(huì)對(duì)新方法和新的原生對(duì)象進(jìn)行轉(zhuǎn)譯,例如:
const arrFun = ()=>{}
const arr = [1,2,3]
console.log(arr.includes(1))轉(zhuǎn)換后:
箭頭函數(shù)被轉(zhuǎn)換了,但是 Array.includes 方法,并沒(méi)有被處理,這個(gè)時(shí)候要是程序跑在低版本的瀏覽器上,就會(huì)出現(xiàn)?"use strict";
var arrFun = function arrFun() {};
var arr = [1, 2, 3];
console.log(arr.includes(1));includes is not function?的錯(cuò)誤。這個(gè)時(shí)候就需要 polyfill 閃亮登場(chǎng)了。
Polyfill
polyfill 的翻譯過(guò)來(lái)就是墊片,墊片就是墊平不同瀏覽器環(huán)境的差異,讓大家都一樣。
@babel/polyfill
@babel/polyfill 模塊可以模擬完整的 ES5 環(huán)境。
npm install --save @babel/polyfill
注意 @babel/polyfill 不是在 Babel 配置文件中配置,而是在我們的代碼中引入。
import '@babel/polyfill';
const arrFun = ()=>{}
const arr = [1,2,3]
console.log(arr.includes(1))
Promise.resolve(true)
編譯后:
require("@babel/polyfill");
var arrFun = function arrFun() {};
var arr = [1, 2, 3];
console.log(arr.includes(1));
Promise.resolve(true);
這樣在低版本的瀏覽器中也能正常運(yùn)行了。不知道大家有沒(méi)有發(fā)現(xiàn)一個(gè)問(wèn)題,這里是require("@babel/polyfill")將整個(gè) @babel/polyfill 加載進(jìn)來(lái)了,但是在這里我們需要處理 Array.includes 和 Promise 就好了,如果這樣就會(huì)導(dǎo)致我們最終打出來(lái)的包體積變大,顯然不是一個(gè)最優(yōu)解。要是能按需加載就好了。其實(shí) Babel 早就為我們想好了。
useBuiltIns
回過(guò)頭來(lái)再說(shuō) @babel/preset-env,他出現(xiàn)的目的就是實(shí)現(xiàn)民族大統(tǒng)一,連 stage-x 都干掉了,又怎么會(huì)漏掉 Polyfill 這一功能,在 @babel/preset-env 的配置項(xiàng)中提供了 useBuiltIns 這一參數(shù),只要在使用 @babel/preset-env 的時(shí)候帶上他,Babel 在編譯的時(shí)候就會(huì)自動(dòng)進(jìn)行 Polyfill ,不再需要手動(dòng)的在代碼中引入@babel/polyfill 了,同時(shí)還能做到按需加載{
"presets": [
"@babel/preset-flow",
[
"@babel/preset-env",
{
"targets": {
"node": "8.10"
},
"corejs": "3", // 聲明 corejs 版本
"useBuiltIns": "usage"
}
]
]
}
注意,這里需要配置一下 corejs 的版本號(hào),不配置編譯的時(shí)候會(huì)報(bào)警告。講都講到這里了就再順便提一嘴 useBuiltIns 的機(jī)構(gòu)參數(shù):- false:此時(shí)不對(duì)Polyfill 做操作,如果引入 @babel/polyfill 則不會(huì)按需加載,會(huì)將所有代碼引入
- usage:會(huì)根據(jù)配置的瀏覽器兼容性,以及你代碼中使用到的 API 來(lái)進(jìn)行 Polyfill ,實(shí)現(xiàn)按需加載
- entry:會(huì)根據(jù)配置的瀏覽器兼容性,以及你代碼中使用到的 API 來(lái)進(jìn)行 Polyfill ,實(shí)現(xiàn)按需加載,不過(guò)需要在入口文件中手動(dòng)加上
import ' @babel/polyfill'
"use strict";
require("core-js/modules/es.array.includes");
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.promise");
var arrFun = function arrFun() {};
var arr = [1, 2, 3];
console.log(arr.includes(1));
Promise.resolve(true);
這個(gè)時(shí)候我們?cè)俳柚?Webpack 編譯后,產(chǎn)出的代碼體積會(huì)大大減小。
圖片說(shuō)完了上面這些你以為我就說(shuō)完了嗎?
圖片其實(shí) Babel 在編譯中會(huì)使用一些輔助函數(shù),比如:class Person {
constructor(){}
say(word){
console.log(":::",word)
}
}
編譯后:
"use strict";
require("core-js/modules/es.object.define-property");
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); return Constructor; }
var Person =
/*#__PURE__*/
function () {
function Person() {
_classCallCheck(this, Person);
}
_createClass(Person, [{
key: "say",
value: function say(word) {
console.log(":::", word);
}
}]);
return Person;
}();
這些方法會(huì)被 inject 到每個(gè)文件中,沒(méi)法做到復(fù)用,這樣也會(huì)導(dǎo)致打包體積的增加。沒(méi)事兒,逢山開(kāi)路遇水搭橋,是時(shí)候讓@babel/plugin-transform-runtime 登場(chǎng)了。
圖片@babel/plugin-transform-runtime
@babel/plugin-transform-runtime 可以讓 Babel 在編譯中復(fù)用輔助函數(shù),從而減小打包文件體積,不信你看:npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
順便說(shuō)一下,這一對(duì) CP 要同時(shí)出現(xiàn),形影不離,所以安裝的時(shí)候你就一起裝上吧~配置 Babel:{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
結(jié)果:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var Person =
/*#__PURE__*/
function () {
function Person() {
(0, _classCallCheck2["default"])(this, Person);
}
(0, _createClass2["default"])(Person, [{
key: "say",
value: function say(word) {
console.log(":::", word);
}
}]);
return Person;
}();
這些用到的輔助函數(shù)都從 @babel/runtime 中去加載,這樣就可以做到代碼復(fù)用了。

結(jié)語(yǔ)
在這個(gè)拿來(lái)主義的社會(huì),有時(shí)候知其然的同時(shí)也需要知其所以然。希望這篇關(guān)于 Babel 知識(shí)的梳理對(duì)你有幫助。
推薦閱讀
我的公眾號(hào)能帶來(lái)什么價(jià)值?(文末有送書(shū)規(guī)則,一定要看)
每個(gè)前端工程師都應(yīng)該了解的圖片知識(shí)(長(zhǎng)文建議收藏)
為什么現(xiàn)在面試總是面試造火箭?
