你必須要知道的babel二三事
1. 什麼是babel
本文基於的babel版本是7.11.6,本文所有示例github
Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.
Babel是一個工具鏈,主要用於將ECMAScript 2015+程式碼轉換為當前和較老的瀏覽器或環境中的向後兼容的JavaScript版本。
1.1 我們能用bebel做什麼?
-
針對於新出的ECMAScript標準,部分瀏覽器還不能完全兼容,需要將這部分語法轉換為瀏覽器能夠識別的語法。比如有些瀏覽器不能正常解析es6中的箭頭函數,那通過babel轉換後,就能將箭頭函數轉換為瀏覽器能夠「認懂」得語法。
-
針對於一些較老的瀏覽器,比如IE10或者更早之前。對一些最新的內置對象
Promise/Map/Set
,靜態方法Arrary.from/Object.assign
以及一些實例方法Array.prototype.includes
,這些新的特性都不存在與這些老版本的瀏覽器中,那麼就需要給這些瀏覽器中的原始方法中添加上這些特性,即所謂的polyfill
。 -
可以做一些源碼的轉換,即可以直接使用babel中提供的API對程式碼進行一些分析處理,例如
-
const filename = 'index.js' const { ast } = babel.transformSync(source, { filename, ast: true, code: false }); const { code, map } = babel.transformFromAstSync(ast, source, { filename, presets: ["minify"], babelrc: false, configFile: false, });
-
2. 使用babel
下面講到的幾種轉換方式,其實本質上都是一樣的,都是調用babel-core中的API來進行直接轉換
2.1 使用babel.transform直接轉換
const source = `
const someFun = () => {
console.log('hello world');
}
`;
require("@babel/core").transform(source, {
plugins: ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-parameters"],
}, result => {
console.log(result.code);
});
2.1 使用babel-cli
babel提供了cli的方式,可以直接讓我們使用命令行的方式來使用babel,具體參照一下做法
## install
## 首先需要安裝 @babel/core @babel/cli
## @babel/cli是提供的命令行工具,會內部調用@babel/core來進行程式碼轉換
npm install @babel/core @babel/cli --save-dev
## usage
npx babel ./cli/index.js
本地安裝完依賴後,就可以使用babel來進行程式碼轉換了,npx babel [options] files
,babel提供了一些常用的cli命令,可以使用npx babel --help
來查看
> $ npx babel --help ⬡ 12.13.0 [±master ●●●]
Usage: babel [options] <files ...>
Options:
-f, --filename [filename] The filename to use when reading from stdin. This will be used in source-maps, errors etc.
--presets [list] A comma-separated list of preset names.
--plugins [list] A comma-separated list of plugin names.
--config-file [path] Path to a .babelrc file to use.
--env-name [name] The name of the 'env' to use when loading configs and plugins. Defaults to the value of BABEL_ENV, or else NODE_ENV, or else
'development'.
下面是一個簡單的例子,比如有這麼一段源程式碼,
// cli/index.js
const arrayFn = (...args) => {
return ['babel cli'].concat(args);
}
arrayFn('I', 'am', 'using');
執行以下命令:npx babel ./cli/index.js --out-file ./cli/index.t.js
,結果如下圖:
程式碼和源程式碼竟然是一模一樣的,為什麼箭頭函數沒有進行轉換呢?這裡就會引入plugins以及preset的概念,這裡暫時不會具體講解,只需要暫時知道,程式碼的轉換需要使用plugin進行。
轉換箭頭函數,我們需要使用到@babel/plugin-transform-arrow-functions/parameters
,首先安裝完之後,在此執行轉換
npm install @babel/plugin-transform-arrow-functions @babel/plugin-transform-parameters --save-dev
npx babel ./cli/index.js --out-file ./cli/index.t.js --plugins=@babel/plugin-transform-arrow-functions,@babel/plugin-transform-parameters
執行完之後,再看生成的文件
2.3 使用webpack babel-loader來進行轉換
創建webpack.config.js,編寫如下配置
// install
npm install webpack-cli --save-dev
// webpack/webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: 'index.bundle.js'
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
plugins: ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-parameters"]
}
}
}
]
}
};
// usage
cd webpack
npx webpack
可以得到轉換之後的程式碼如下:
可以對比查看babel-cli的轉換之後的程式碼是一致的。
2.4 使用配置文件來進行轉換
參看以上三種方式,都必須載入了plugins這個參數選項,尤其是在cli方式中,如果需要載入很多插件,是非常不便於書寫的,同時,相同的配置也不好移植,比如需要在另外一個項目中同樣使用相同的cli執行,那麼顯然插件越多,就會越容易出錯。鑒於此,babel提供了config的方式,類似於webpack的cli方式以及config方式。
babel在7.0之後,引入了babel.config.[extensions]
,在7.0之前,項目都是基於.babelrc
來進行配置,這裡暫時不會講解它們之間的區別。下面就是一個比較基於上面例子的一個.babelrc文件。
// .babelrc
{
"plugins": ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-parameters"]
}
我們將這個文件放置在根目錄下,新建一個config
的文件夾,從cli目錄中將index.js文件copy到config目錄下,然後執行npx babel ./config/index.js --out-file ./config/index.t.js
,完成之後,會發現和cli執行的方式並沒有什麼差別。
3. babel.config.json vs .babelrc
babel.config.js是在babel第7版引入的,主要是為了解決babel6中的一些問題,參看//babeljs.io/docs/en/config-files#6x-vs-7x-babelrc-loading
- .babelrc會在一些情況下,莫名地應用在node_modules中
- .babelrc的配置不能應用在使用符號鏈接引用進來的文件
- 在node_modules中的.babelrc會被檢測到,即使它們中的插件和預設通常沒有安裝,也可能在Babel編譯文件的版本中無效
3.1 .babelrc在monorepo項目中的一些問題
另外如果只使用.babelrc,在monorepo項目中會遇到一些問題,這得從.babelrc載入的兩條規則有關
- 在向上搜索配置的過程中,一旦在文件夾中找到了package.json,就會停止搜尋其它配置(babel用package.json文件來劃定package的範圍)
- 這種搜索行為找到的配置,如
.babelrc
文件,必須位於babel運行的root目錄下,或者是包含在babelrcRoots
這個option配置的目錄下,否則找到的配置會直接被忽略
下面我們在之前的例子上進行改造,文件結構如下:
在mod1文件夾中創建一個package.json文件,內容為{}
。現在執行以下程式碼:
npx babel ./config/mod1/index.js -o ./config/mod1/index.t.js
可以發現,index.js沒有編譯,因為在向上查找的時候,找到了mod1中的package.json,但是在此目錄中並沒有找到.babelrc
文件,因此不會編譯。
下面,我們將.babelrc
文件移至mod1中,然後再執行上面的命令,這次會編譯成功么?
答案依舊是不會,因為當前的執行目錄是在src下面,所以在mod1
目錄中的配置文件將會被忽略掉。
這裡有兩種方法來解決這個問題:
-
進入到mod1目錄中直接執行
cd ./config/mod1 & npx babel index.js -o index.t.js
-
在執行的root目錄下,添加一個
babel.config.json
文件,在其中添加babelrcRoots
將這個目錄添加進去
然後再執行npx babel ./config/mod1/index.js -o ./config/mod1/index.t.js
就可以正常編譯了。
正是基於上述的一些問題,babel在7.0.0之後,引入了babel.config.[json/js/mjs/cjs]
,基於babel.config.json的配置會靈活得多。
3.2 Project-wide configuration
一般babel.config.json
會放置在根目錄下,在執行編譯時,babel會首先去尋找babel.config.json
文件,以此來作為整個項目的根配置。
如果在子目錄中不存在.babelrc的配置,那麼在編譯時,會根據根目錄下的配置來進行編譯,比如在config/index.js中添加如下程式碼
執行npx babel ./config/index -o ./config/index.t.js
後會發現for..of
這段程式碼會被原樣輸出,因為在config目錄中並沒有針對for..of
配置插件。現在在config文件中添加.babelrc
,內容如下:
{
"plugins": [
"@babel/plugin-transform-for-of"
]
}
再次執行完,會發現,for..of
會被babel編譯
說明,如果子文件夾中存在相應的babel配置,那麼編譯項會在根配置上進行擴展。
但這點在monorepo
項目中會有點例外,之前我在mod1文件家中放置了一個package.json
文件:
執行下面命令
npx babel ./config/mod1/index.js -o ./config/mod1/index.t.js
發現for..of
部分並沒有被babel編譯,這個原因和之前在講bablerc的原因是一樣的,因為執行的根目錄是src,因此在mod1中並不能去載入.babelrc配置,因此只根據根目錄中的配置來執行編譯。想要mod1中的配置也被載入,可以按照相同的方法在babel.config.json
中配置babelrcRoots
。
另外如果子文件家中不存在相應的配置,比如在cli目錄下,在src目錄下執行config/index.js文件是沒有問題的,但是如果進入cli中,然後直接執行,會發現index.js文件不會被編譯。由此,你需要告訴babel去找到這個配置,這裡可以使用rootMode: upward
來使babel向上查找babel.config.json,並以此作為根目錄。
cd cli & npx babel ./index.js -o ./index.t.js --root-mode upward
3.3 推薦使用場景
- babel.config.json
- 你正在使用一個
monorepo
(可以理解為在一個項目中會有多個子工程) - 你希望編譯node_modules以及symobllinked-project中的程式碼
- 你正在使用一個
- .babelrc
- 你的配置僅適用於項目的單個部分
- 需要在子目錄/文件中運行一些特定的轉換,比如你可能不希望一些第三方庫被轉碼
- 綜合推薦使用babel.config.json,Babel itself is using it
4. plugins & Presets
Babel is a compiler (source code => output code). Like many other compilers it runs in 3 stages: parsing, transforming, and printing.
Now, out of the box Babel doesn’t do anything. It basically acts like
const babel = code => code;
by parsing the code and then generating the same code back out again. You will need to add plugins for Babel to do anything.
沒有plugins,babel將啥事也做不了。
babel提供了豐富的插件來對不同時期的程式碼進行轉換。例如我們在es6最常使用的箭頭函數,當需要轉化為es5版本時,就用到了arrow-functions這個插件。
具體的插件列表,可以查看plugins。
presets的中文翻譯為預設,即為一組插件列表的集合,我們可以不必再當獨地一個一個地去添加我們需要的插件。比如我們希望使用es6的所有特性,我們可以使用babel提供的ES2015這個預設。
4.1 基本用法
// 如果plugin已經在發布到npm中
// npm install @babel/plugin-transform-arrow-functions -D
// npm install @babel/preset-react -D
{
"plugins": ["@babel/plugin-transform-arrow-functions"],
"presets": ["@babel/preset-react"]
}
// 或者按照babel的規範,引入自己編寫的plugin/preset
{
"plugins": ["path/to/your/plugin"],
"presets": ["path/to/your/preset"],
}
4.2 選項
任何一個插件都可以擁有自定義的屬性來定義這個插件的行為。具體的寫法可以為:
{
"plugins": ["pluginA", ["pluginA"], ["pluginA", {}]],
"presets": ["presetA", ["presetA"], ["presetA", {}]]
}
// example
{
"plugins": [
[
"@babel/plugin-transform-arrow-functions",
{ "spec": true }
]
],
"presets": [
[
"@babel/preset-react",
{
"pragma": "dom", // default pragma is React.createElement (only in classic runtime)
"pragmaFrag": "DomFrag", // default is React.Fragment (only in classic runtime)
"throwIfNamespace": false, // defaults to true
"runtime": "classic" // defaults to classic
// "importSource": "custom-jsx-library" // defaults to react (only in automatic runtime)
}
]
]
}
4.3 執行順序
- 插件執行順序在presets之前
- 插件會按照聲明的插件列表順序順序執行(first to last)
- preset會按照聲明的列表順序逆序執行(last to first)
4.3.1 plugin的執行順序測試
下面我們來做幾個例子測試一下,首先,官方給出的插件標準寫法如下(在此之前,強烈建議閱讀babel-handbook來了解接下來插件編碼中的一些概念):
// 1. babel使用babylon將接受到的程式碼進行解析,得到ast樹,得到一系列的令牌流,例如Identifier就代表一個字
// 符(串)的令牌
// 2. 然後使用babel-traverse對ast樹中的節點進行遍歷,對應於插件中的vistor,每遍歷一個特定的節點,就會給visitor添加一個標記
// 3. 使用babel-generator對修改過後的ast樹重新生成程式碼
// 下面的這個插件的主要功能是將字元串進行反轉
// plugins/babel-plugin-word-reverse.js
module.exports = function() {
return {
visitor: {
Identifier(path) {
console.log("word-reverse plugin come in!!!");
const name = path.node.name;
path.node.name = name
.split("")
.reverse()
.join("");
},
},
};
}
// 然後我們再提供一個插件,這個插件主要是修改函數的返回值
// plugins/babel-plugin-replace-return.js
module.exports = function({ types: t }) {
return {
visitor: {
ReturnStatement(path) {
console.log("replace-return plugin come in!!!");
path.replaceWithMultiple([
t.expressionStatement(t.stringLiteral('Is this the real life?')),
t.expressionStatement(t.stringLiteral('Is this just fantasy?')),
t.expressionStatement(t.stringLiteral('(Enjoy singing the rest of the song in your head)')),
]);
},
},
};
}
首先我們來測試一下原始程式碼是否通過我們自定義的插件進行轉換了,源程式碼如下:
// plugins/index.js
const myPluginTest = (javascript) => {
return 'I love Javascript';
}
// 然後在plugins目錄下創建一個.babelrc文件,用於繼承默認的babel.config.json文件
// plugins/.babelrc
{
"plugins": ["./babel-plugin-word-reverse", "./babel-plugin-replace-return"]
}
// usage
npx babel ./plugins/index.js -o ./plugins/index.t.js
以下是執行完之後的結果
從截圖可以看出,字元串被反轉了,以及返回的字元串也被替換掉了。
然後我們再來看看執行的順序
可以看到,排在插件列表之前的插件會在提前執行。
4.3.2 preset的執行順序測試
下面再新建一個插件,用於自定義的preset編寫
// presets/babel-plugin-word-replace.js
// 這個插件主要的功能是給每個節點類型為Identifier的名稱拼接一個_replace的後綴
module.exports = function() {
return {
visitor: {
Identifier(path) {
console.log("word-replace plugin come in!!!");
let name = path.node.name;
path.node.name = name += '_replace';
},
},
};
}
然後我們藉助之前編寫的babel-plugin-word-reverse
來編寫兩個新的presets
// presets/my-preset-1.js
module.exports = () => {
console.log('preset 1 is executed!!!');
return {
plugins: ['../plugins/babel-plugin-word-reverse']
};
};
// presets/my-preset-2.js
module.exports = () => {
console.log('preset 2 is executed!!!');
return {
presets: ["@babel/preset-react"],
plugins: ['./babel-plugin-word-replace', '@babel/plugin-transform-modules-commonjs'],
};
};
// 創建.babelrc配置
// presets/.babelrc
{
"presets": [
"./my-preset-1",
"./my-preset-2"
]
}
// 測試程式碼
// presets/index.jsx
import React from 'react';
export default () => {
const text = 'hello world';
return <div>{text}</div>;
}
// 執行
npx babel ./presets/index.jsx -o ./presets/index.t.js
可以看到在.babelrc中,將preset-1放在了preset-2的前面,如果按照babel官網給出的解析,那麼preset2會被先執行,執行的順序如下
可以看到控制台列印的順序是preset1 -> preset2,這點與官網給出的preset執行順序是相反的???
然後再看編譯之後生成的文件,發現竟然又是先執行了preset-2中的插件,然後在執行preset-1中的插件,如圖:
可以看到顯然是首先經過了添加後綴_replace
,然後在進行了整體的reverse
。這裡是不是意味著,在presets列表中後聲明的preset中的插件會先執行呢???
懷著這個問題,去啃了下源程式碼。發現babel所說的執行順序,其實是traverse
訪問插件中vistor
的順序。因為presets其實也是一組插件的集合,經過程式處理之後,會使得presets末尾的plugins會出現在整個plugins列表的前面。
同時可以看圖中控制台的列印結果,word-replace
始終會在word-reverse
之前,並且是成對出現的。
// babel/packages/babel-core/src/transform.js [line 21]
const transformRunner = gensync<[string, ?InputOptions], FileResult | null>(
function* transform(code, opts) {
const config: ResolvedConfig | null = yield* loadConfig(opts);
if (config === null) return null;
return yield* run(config, code);
},
);
loadConfig(opts)
會被傳遞進來的plugins以及presets進行處理,進去看看發生了什麼?
// babel/packages/babel-core/src/config/full.js [line 59]
export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
inputOpts: mixed,
): Handler<ResolvedConfig | null> {
const result = yield* loadPrivatePartialConfig(inputOpts);
// ...
const ignored = yield* (function* recurseDescriptors(config, pass) {
const plugins: Array<Plugin> = [];
for (let i = 0; i < config.plugins.length; i++) {
const descriptor = config.plugins[i];
if (descriptor.options !== false) {
try {
plugins.push(yield* loadPluginDescriptor(descriptor, context));
} catch (e) {
// ...
}
}
}
const presets: Array<{|
preset: ConfigChain | null,
pass: Array<Plugin>,
|}> = [];
for (let i = 0; i < config.presets.length; i++) {
const descriptor = config.presets[i];
if (descriptor.options !== false) {
try {
presets.push({
preset: yield* loadPresetDescriptor(descriptor, context),
pass: descriptor.ownPass ? [] : pass,
});
} catch (e) {
// ...
}
}
}
// resolve presets
if (presets.length > 0) {
// ...
for (const { preset, pass } of presets) {
if (!preset) return true;
const ignored = yield* recurseDescriptors(
{
plugins: preset.plugins,
presets: preset.presets,
},
pass,
);
// ...
}
}
// resolve plugins
if (plugins.length > 0) {
pass.unshift(...plugins);
}
})(//...)
}
loadPrivatePartialConfig
中會依次執行我們定義的plugins以及presets,這也是為什麼在上面的例子中preset1會列印在preset2。
// babel/packages/babel-core/src/config/config-chain.js [line 629]
function mergeChainOpts(
target: ConfigChain,
{ options, plugins, presets }: OptionsAndDescriptors,
): ConfigChain {
target.options.push(options);
target.plugins.push(...plugins());
target.presets.push(...presets());
return target;
}
recurseDescriptors
這裡是一個遞歸函數,是用來在passes中存放解析過後的plugins以及presets的,passes通過unshift的方式解析每次循環之後的插件,因此presets的循環越靠後,在passes中的plugins反而會越靠前,這也是為什麼presets列表中的執行順序是逆序的原因。
// babel/packages/babel-core/src/config/full.js [line 195]
opts.plugins = passes[0];
opts.presets = passes
.slice(1)
.filter(plugins => plugins.length > 0)
.map(plugins => ({ plugins }));
opts.passPerPreset = opts.presets.length > 0;
return {
options: opts,
passes: passes,
};
設置解析後的plugins
,然後返回新的config。
5. polyfill
Babel 7.4.0之後,
@babel/polyfill
這個包已經廢棄了,推薦直接是用core-js/stable
以及regenerator-runtime/runtime
import "core-js/stable"; import "regenerator-runtime/runtime";
polyfill
的直接翻譯為墊片,是為了添加一些比較老的瀏覽器或者環境中不支援的新特性。比如Promise/ WeakMap
,又或者一些函數Array.form/Object.assign
,以及一些實例方法Array.prototype.includes
等等。
注意:這些新的特性會直接載入全局的環境上,在使用時請注意是否會污染當前的全局作用域
5.1 基本使用
npm install --save @babel/polyfill
// commonJs
require('@babel/polyfill')
// es6
import('@babel/polyfill')
當在webpack中使用時,官方推薦和@babel/preset-env
一起使用,因為這個preset會根據當前配置的瀏覽器環境自動載入相應的polyfill,而不是全部進行載入,從而達到減小打包體積的目的
// .bablerc
{
"presets": [
[
"@babel/preset-env", {
"useBuiltIns": "usage", // 'entry/false'
"corejs": 3
}
]
]
}
useBuiltIns
有三個選項
-
usage 當使用此選項時,只需要安裝
@babel-polyfill
即可,不需要在webpack中引入,也不需要在入口文件中引入(require/import) -
entry 當使用此選項時,安裝完
@babel-polyfill
之後,然後在項目的入口文件中引入 -
false 當使用此選項時,需要安裝依賴包,然後加入webpack.config.js的entry中
module.exports = { entry: ["@babel/polyfill", "./app/js"], };
在瀏覽器中使用,可以直接引入@bable/polyfill
中的dist/polyfill.js
<script src='dist/polyfill.js'></script>
5.2 示例
通過配合使用@babel/preset-env
之後,我們可以來看看編譯之後生成了什麼?
// polyfill/.babelrc
{
"presets": [
[
"@babel/preset-env", {
"useBuiltIns": "usage", // 其他兩個選項 'entry/false'
"corejs": 3 // 如果需要使用includes,需要安裝corejs@3版本
}
]
]
}
// polyfill/index.js
const sym = Symbol();
const promise = Promise.resolve();
const arr = ["arr", "yeah!"];
const check = arr.includes("yeah!");
console.log(arr[Symbol.iterator]());
編譯之後的結果如下
可以看到,瀏覽器中缺失的方法、對象都是直接引入的。當你只需要在特定的瀏覽器中做兼容時,可以顯式地聲明,使用方式可以參照browserslist-compatible。
{
"targets": "> 0.25%, not dead",
// 或者指明特定版本
"targets": {
"chrome": "58",
"ie": "11"
}
}
6. transform-runtime
A plugin that enables the re-use of Babel’s injected helper code to save on codesize.
@babel/plugin-transform-runtime
的主要有三個用處
- 自動引入
@babel/runtime/regenerator
,當你使用了generator/async
函數(通過regenerator
選項打開,默認為true) - 提取一些babel中的工具函數來達到減小打包體積的作用
- 如果開啟了
corejs
選項(默認為false),會自動建立一個沙箱環境,避免和全局引入的polyfill產生衝突。
這裡說一下第三點,當開發自己的類庫時,建議開啟corejs選項,因為你使用的polyfill可能會和用戶期待的產生衝突。一個簡單的比喻,你開發的類庫是希望兼容ie11的,但是用戶的系統是主要基於chorme的,根本就不要去兼容ie11的一些功能,如果交給用戶去polyfill,那就的要求用戶也必須要兼容ie11,這樣就會引入額外的程式碼來支援程式的運行,這往往是用戶不想看到的。
6.1 基本使用
// dev dependence
npm install --save-dev @babel/plugin-transform-runtime
// production dependence
// 因為我們需要在生產環境中使用一些runtime的helpers
npm install --save @babel/runtime
// .babelrc
// 默認配置
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": false,
"version": "7.0.0-beta.0"
}
]
]
}
6.2 示例
說了這麼多,下面來看一個示例
// transform-runtime/.babelrc
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"helpers": false
}
]
]
}
// transform-runtime/index.js
const sym = Symbol();
const promise = Promise.resolve();
const arr = ["arr", "yeah!"];
const check = arr.includes("yeah!");
class Person {}
new Person();
console.log(arr[Symbol.iterator]());
這裡暫時關閉了helpers
,我們來看看編譯之後會是什麼結果
可以看到,編譯之後,將Person class
生成了一個函數_classCallCheck
,你可能覺得一個生成這樣的函數也沒什麼特別大的關係,但是如果在多個文件中都聲明了class
,那就意味著,將會在多個文件中生成一個這麼一模一樣的工具函數,那麼體積就會變大了。因此,開啟了helpers
之後,效果又是怎樣的呢?
可以看到,需要生成的方法變成了引入的方式,注意引入的庫是@babel-runtime
下面來試試開啟了corejs
選項之後生成的文件是啥樣的?
可以看到所有的工具方式都來自於@babel/runtime-corejs2
,因為是獨立於polyfill生成的,所以不會污染全局環境。
總結
- 推薦使用babel.config.js來作為整個項目的babel配置,.babelrc更加適用於
monorepo
項目 - babel的編譯基本都是依賴於plugin,preset是一組plugin的集合
- polyfill為一些較老的瀏覽器提供一些新特性的支援
- transform-runtime可以提取一些幫助函數來減小打包的體積,在開發自己的類庫是,建議開啟corejs選項