[TOC]
# Babel
**[Babel](https://new.babeljs.io/) 主要用于將采用 ECMAScript 2015+ 語法編寫的代碼轉換為向后兼容的 JavaScript 語法,以便能夠運行在當前和舊版本的瀏覽器或其他環境中。** 作用如下:
* 語法轉換
* 通過 Polyfill 方式在目標環境中添加缺失的特性(通過第三方 polyfill 模塊,例如 core-js,實現)
* 源碼轉換 (codemods)
換而言之就是 Babel 能讓我們現在就使用新的語法,無需等待瀏覽器的支持。
為了命令行執行編譯,你可以安裝`@babel/cli`。不過一般都是靠 webpack 等工具進行自動編譯。
babel 的功能都是通過 plugin 來完成的。每個 plugin 都有特定的代碼處理的功能,只有將它配置到 babel 里面運行,才能對代碼進行轉換。
babel 目前有很多的 plugin,既有官方的,也有第三方的。
在加入 plugins 測試之前我們需要知道一些前置知識,babel 將 ECMAScript 2015+ 版本的代碼分為了兩種情況處理:
* syntax(語法層面): let、const、class、arrow function等,這些需要在構建時進行轉譯,是指在**語法層面上的轉譯,babel 是可以直接轉換**的。
* features(api 方法層面):Promise、Set、Map等`ES6+` 標準推出的新特性,babel 不能直接轉換,這些是在全局或者 Object、Array 等的原型上新增的方法,它們可以由相應 ES5 的方式重新定義,即**需要為 Babel 做一些配置,也就是 polyfill 來實現**,polyfill 翻譯成中文就是**墊片**的意思,用來墊平不同瀏覽器環境之前差異。
# 例子
```
mkdir babel-demo
npm init -y
touch index.js #?? index.js內容
npm i -D @babel/cli @babel/core
npx babel index.js --out-file compiled.js # 編譯成 compiled.js
```
`index.js`內容:
```
const fn = () => {
console.log("wens");
};
const p = new Promise((resolve, reject) => {
resolve("wens");
});
const list = [1, 2, 3, 4].map(item => item * 2);
```
# 基礎配置
有三種方式對 babel 進行[配置](https://babeljs.io/docs/usage/babelrc/):
1. `.babelrc` 等專用配置文件。
2. `package.json` 中`babel`字段進行配置。
如果你同時使用了這兩種方式,那么`package.json`中的配置將被忽略!
*.babelrc*:
```json
{
"presets": [
[
"@babel/preset-env",
{
"debug": true, # 查看哪些 API 被 polyfill
"useBuiltIns": "usage",
"targets": {
"ie": 11
}
}
],
"@babel/preset-typescript" // presets是自下而上執行的
],
"plugins": [
"@babel/proposal-class-properties",
"@babel/proposal-object-rest-spread",
[
"@babel/plugin-transform-runtime",
{
"corejs": {
"version": 3, // 指定 runtime-corejs 的版本,目前有 2 3 兩個版本
"proposals": true // 使用尚在 提議 階段 ECMAScript 特性的 polyfill
},
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
```
*.browserlistrc*:
```json
> 0.25%
not dead
```
## 配置項
### `sourceType:?'unambiguous'`
由于 Babel 默認將文件視為 ES modules,因此通常這些 plugins/presets 將插入`import`語句。插入`import`語句會導致 Webpack 和其他工具將一個文件看作是 ES modules,從而破壞了一個正常的 CommonJS 文件,一般會出現:
<b style="color:red">`Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'`</b>
所以最好不要混用`import`and`module.exports`。在默認的 ES module 中,你需要進行更改:
```
// Change this
module.exports = foo;
// To this
export default foo;
```
如果你確實不能更改文件為 ES module ,那么你需要設置:
~~~
"sourceType": "unambiguous"
~~~
> [打包時遇到Cannot assign to read only property 'exports' of object 'Object'問題的解決方法](https://blog.csdn.net/fjh1997/article/details/88544354)
> [issues/3650#issuecomment-397830621](https://github.com/vercel/next.js/issues/3650#issuecomment-397830621)
> [options#sourcetype](https://babeljs.io/docs/en/options#sourcetype)
### `comments:?false`
> [options#comments](https://babeljs.io/docs/en/options#comments)
# @babel/core
包含了核心的轉換邏輯,需要對應的plugins/preset才能發揮作用。
## @babel/helpers
一系列工具,比如`class`語法的實現就是 helper 提供的。被`@babel/core`依賴。
# @babel/preset-env
官網:https://www.babeljs.cn/docs/babel-preset-env
顧名思義,preset 即**預設插件**,它可以將 ES6 轉換為 ES5,包含了各種可能用到的轉譯工具。**之前的以年份為準的 preset (如:babel-preset-2015)已經廢棄了,現在統一用這個總包**。
同時,babel 已經放棄開發 `stage-*` 包,以后的轉譯組件都只會放進 `@babel/preset-env`包里。
`@babel/preset-env` 對于插件的選擇是基于某些開源項目的,比如[`browserslist`](https://github.com/browserslist/browserslist)、[`compat-table`](https://github.com/kangax/compat-table)以及[`electron-to-chromium`](https://github.com/Kilian/electron-to-chromium),比如常用`.browserslistrc`來設置我們預想滿足的目標運行環境。
接下來說的是`@babel/preset-env`的重要配置。
## **useBuiltIns**
從其名字來說是“使用內置”,“內置”的什么呢?
從官方看來是“**polyfills(墊片)**”,控制 `@babel/preset-env` 使用何種方式幫我們導入 **polyfill** 的核心,`corejs`是 Babel 使用的內置 polyfills 庫。
它的取值可以是以下三種:
### `false`
默認值,不做任何 polyfil l處理。
### `entry`
一種入口導入方式, 需要我們在`打包配置入口` 或者 *文件入口*寫入 `import "core-js"` 這樣一串代碼, babel 就會替我們根據當前你所配置的目標瀏覽器(browserslist 配置)來引入所需要的 polyfill。其他多余引入的無論有沒有用到,會全部引入進來,`entry`的覆蓋面積全,打包體積自然就大。
我們先使用`entry`,除此之外我們還指定了 corejs 的版本,然后我們在文件頂部手動引入 polyfill 也就是 core-js:
```
import "core-js/stable";
import "regenerator-runtime/runtime";
const message = "hello world";
...
```
然后執行`npm run build`,會發現轉換后的代碼里面引入了所有polyfill,包括我們需要的`Promise`:
```
...
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.object.values");
require("core-js/modules/es.promise");
...
```
這樣,我們的代碼就可以在低版本瀏覽器中使用了。
### `usage`
即 **按需引用**,如果目標瀏覽器不支持需要的 feature,那么就引入 polyfill,不然的話就不引用。由于目前的打包工具越發智能,隨著 tree shaking 的完善,這樣可以最低限度引入 polyfill。但是對第三方依賴包無效,常用來在開發第三方庫時使用,因為開發者無法控制庫的瀏覽器運行環境。
### `corejs`
`corejs`只在`useBuiltIns`取值為`entry`或`usage`的時候有用,因為 Babel 內置的 polyfills 就是 `core-js`。
先安裝 corejs 的版本,可以配置為`2`或`3`:
~~~shell
npm i -S core-js@3
~~~
```
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 5%",
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}
```
絕大部分情況,推薦使用 `@babel/preset-env + useBuiltIns: 'usage'` 這種方式。
這種方式打包體積不大,但是如果我們排除`node_modules`目錄,遇上沒有經過轉譯的第三方包,就檢測不到第三方包內部的 `'hello'.includes('h')`這種語法,這時候我們就會遇到 bug(這種情況可以使用,參考本文中的`@babel/plugin-transform-runtime`)。
# `core-js@2`和`core-js@3`
**[core-js](https://github.com/zloirock/core-js) 是一個 JavaScript 的模塊化標準庫。包括 ECMAScript 到 2021 年的 polyfill。**
`core-js@2`分支中不包含一些最新的實例方法特性,新特性都會添加到 `core-js@3`,現在 2 版本被廢棄使用。例如:`core-js@2`不包含 `Array.prototype.flat()`。
由于`core-js@2`版本包的體積太大(~2M),并且有很多重復的文件被引用。`core-js@3`對包進行拆分,三個核心的包分別是:
* [core-js](https://github.com/zloirock/core-js/tree/master/packages/core-js):定義全局的 polyfill(~500k, 40k minified and gzipped)
* [core-js-pure](https://github.com/zloirock/core-js/tree/master/packages/core-js-pure):提供不污染全局環境的 polyfill,等價于 core-js@2/library(~440k)
* [core-js-compat](https://github.com/zloirock/core-js/tree/master/packages/core-js-compat):包含了 core-js 模塊和 API 必要的數據,通過 browserslist 來生成所需要的 core-js 模塊的列表
對于`core-js@3`的入口文件,我們可以這樣使用:
~~~js
// polyfill all `core-js` features:
import "core-js";
// polyfill only stable `core-js` features - ES and web standards:
import "core-js/stable";
// polyfill only stable ES features:
import "core-js/es";
// if you want to polyfill `Set`:
// all `Set`-related features, with ES proposals:
import "core-js/features/set";
// stable required for `Set` ES features and features from web standards
// (DOM collections iterator in this case):
import "core-js/stable/set";
// only stable ES features required for `Set`:
import "core-js/es/set";
// the same without global namespace pollution:
import Set from "core-js-pure/features/set";
import Set from "core-js-pure/stable/set";
import Set from "core-js-pure/es/set";
// if you want to polyfill just required methods:
import "core-js/features/set/intersection";
import "core-js/stable/queue-microtask";
import "core-js/es/array/from";
~~~
我們還可以使用`core-js-compat`用來提供目標引擎所需要的core-js的模塊信息:
~~~js
const {
list, // array of required modules
targets, // object with targets for each module
} = require('core-js-compat')({
targets: '> 2.5%', // browserslist query
filter: 'es.', // optional filter - string-prefix, regexp or list of modules
});
console.log(targets);
~~~
# babel 的 polyfill 包
## `@babel/polyfill`
**在 babel v7.4.0 版本中已經明確表示不推薦使用**,官方建議我們使用`core-js`來替代。
如果使用導入`@babel/polyfill`的方式的話,現在會報錯了:
> `@babel/polyfill` is deprecated. Please, use required parts of `core-js`
and `regenerator-runtime/runtime` separately
`@babel/polyfill`內部就是引用`core-js`、`regenerator-runtime`這兩個包。從而完整的模擬`ES2015+`環境。
-----------
**通過向全局對象和內置對象的`prototype`上添加方法來實現**,比如目標運行環境中不支持 `Array.prototype.find`,引入polyfill,前端就可以放心的在代碼里用 ES6 的語法來寫;
-----------
`@babel/polyfill`沒有提供從`core-js@2`到`core-js@3`平滑升級路徑:因為該原因,**決定棄用`@babel/polyfill`代之以分別引入需要的`core-js`和`regenerator-runtime`**。
`core-js@3`中等價替換`@babel/polyfill`是:
```
import "core-js/stable";
import "regenerator-runtime/runtime";
```
這里的`core-js`不一定要導入stable,具體視項目而定,點擊[這里](https://github.com/zloirock/core-js#commonjs-api)可以查看更多的`core-js`模式。
## `@babel/plugin-transform-runtime`
官網:[@babel/plugin-transform-runtime](https://github.com/babel/website/blob/main/docs/plugin-transform-runtime.md)
它將開發者依賴的全局內置對象等,抽取成單獨的模塊,并通過模塊導入的方式引入,避免了對全局作用域的修改(污染)。
1. babel 在轉碼過程中,會加入很多 babe l自己的 helper 函數,這些 helper 函數,在每個文件里可能都會重復存在,transform-runtime 插件可以把這些重復的 helper 函數,轉換成公共的、單獨的依賴引入,從而節省轉碼后的文件大小。
2. transform-runtime 可以幫助這種項目創建一個沙盒環境,即使在代碼里用到了新的 ES 特性,它能將這些特性對應的全局變量,轉換為對 core-js 和 regenerator-runtime 非全局變量版本的引用。這其實也應該看作是一種給代碼提供 polyfill 的方式。
Important to note here is that`@babel/preset-env`**respects your targets**, and doesn't include unnecessary pollyfills, *
> **注意**:`@babel/preset-env`會識別`.browserslistrc`,不包含不必要的 polyfills;而`@babel/transform-runtime`不識別`.browserslistrc`,會包含所有可填充的特性,這會導致許多不必要的膩子填充。
### 是2還是3
`@babel/plugin-transform-runtime` 就是一個工具庫,默認使用`@babel/runtime` 提供的幫助函數(helpers)進行模塊隔離(即`corejs: false`)。如果想啟用`transform-runtime`對`core-js`的 polyfill 的話,就得使用`@babel/runtime`另外的兩個版本:
* core-js@2 對應的`@babel/runtime`版本是:`@babel/runtime-corejs2`;
* core-js@3 對應的`@babel/runtime`版本是:`@babel/runtime-corejs3`。
| `corejs`option | Install command |
| --- | --- |
| `false` | `npm install --save @babel/runtime` |
| `2` | `npm install --save @babel/runtime-corejs2` |
| `3` | `npm install --save @babel/runtime-corejs3` |
> [babel-plugin-transform-runtime#corejs](https://babeljs.io/docs/en/babel-plugin-transform-runtime#corejs)
### 使用
```
npm i @babel/runtime-corejs3
npm i -D @babel/plugin-transform-runtime
```
`@babel/runtime-corejs3`是一個核心, 一種實現方式,在局部文件中以模塊化引用的形式導入,不會污染全局變量;而 `@babel/plugin-transform-runtime` 負責更好的重復使用`@babel/runtime-corejs3`。兩個包是一起使用的。
```
// webpack.config.js
module.exports = {
presets: ["@babel/env"],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: { version: 3 }
// 指定 runtime-corejs 的版本,目前有 2 3 兩個版本
}
]
]
};
```
去掉了`@babel/env`的相關參數,而給`@babel/plugin-transform-runtime`添加了`corejs`參數,最終轉換后的文件不會再出現 polyfill 的`require`的方法了。解決轉譯 api 層出現的**全局變量污染**。
主要是為了解決轉換之后代碼重復使用而造成的包體積較大的問題,因為 babel 在轉換代碼時會使用一些 helpers 輔助函數,比如下面的代碼:
```
class Person {
constructor(name) {
this.name = name;
}
say() {
console.log(this.name);
}
}
```
轉換之后,我們會發現生成的代碼除了一些 polyfill 和實際的代碼之外,還有一些 helpers 代碼:
~~~
...
"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"));
...
~~~
如果有很多文件需要轉換,那這些代碼可能就會重復,為了解決這個問題,我們可以使用`@babel/plugin-transform-runtime`將這些helpers輔助函數的使用方式改為引用的方式,讓它們都去引用`@babel/runtime`包里的代碼,這樣他們就是重復引用同一個代碼,避免了內容上的重復,以節省代碼的冗余,從而減小了程序包的體積。其中`@babel/runtime`這個包里面就包含了所有的 helpers 輔助函數。
`useBuiltIns`的`entry` 和 `@babel/runtime` 不要同時使用,會產生各種幫助函數還引入了許多 polyfill,導致包體積增大!
> [@babel/preset-env 與@babel/plugin-transform-runtime 使用及場景區別](https://segmentfault.com/a/1190000021188054)
## `@babel/runtime`
Babel modular runtime helpers(Babel模塊化運行時助手);
源碼包含兩個文件夾:
* helpers(定義了一些處理新的語法關鍵字的幫助函數
* regenerator(`regenerator-runtime`包的一個版本)。
它只是包含模塊化方式導出了一系列函數的包。
舉例:
```js
class Circle {}
// ------------轉譯后------------------
function _classCallCheck(instance, Constructor) { //... }
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
```
babel通常會對這類代碼進行轉譯,`_classCallCheck`會被多次生成,這樣就很不好,所有我們還需要借用`@babel/plugin-transform-runtime`變為從`@babel/runtime`導入的方式:
```js
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
```
## `@babel/runtime-corejs2`
[@babel/runtime-corejs2 · Babel 中文網](https://www.babeljs.cn/docs/babel-runtime-corejs2)
## `@babel/runtime-corejs3`
源碼包含四個文件夾:
* core-js(引用`core-js`這個包)
* core-js-stable(引用`core-js`這個包)
* helpers(定義了一些處理新的語法關鍵字的幫助函數)
* regenerator(僅僅是引用`regenerator-runtime`這個包)
——————————————————————————————
可以看出,`@babel/runtime-corejs3 ≈ @babel/runtime + @babel/polyfill`:
`@babel/runtime`只能處理語法關鍵字,而`@babel/runtime-corejs3`還能處理新的全局變量(例如,`Promise`)、新的原生方法(例如,`String.padStart` );
使用了`@babel/runtime-corejs3`,就無需再使用`@babel/runtime`了。
因此,該插件可以代替 polyfill,將`Promise`或`Symbol`轉換為引用`core-js`庫里的函數,但不能對內置對象的實例方法進行轉換。
```js
Promise
// ------轉換為:------
var _Promise = require("@babel/runtime-corejs3/core-js/promise.js");
```
> [core-js@3, babel展望未來](https://juejin.im/post/5e355be0f265da3e491a53c5#heading-15)
# @babel/preset-typescript
只做語法的轉換,不做類型檢查,因為類型檢查的任務可以交給 IDE (或者用 `tsc`)去做。
# 小結
Babel 負責兩件事:
1)語法轉換,由各種 transform 插件、helpers 完成;
2)對于可*polyfill*的 API 的提供,由 `corejs` 實現。
已經在 `@babel/preset-env` 中配置了polyfill,那么你連 `@babel/plugin-transform-runtime` 都是不必要的(他們二者都可以提供ES 新 API 的墊片,在這一項功能上是重復的。
`@babel/preset-env` 除了提供 polyfill 墊片,還提供 ES 新語法的轉譯,這一點 `@babel/plugin-transform-runtime` 做不了;`@babel/preset-env` 提供的 polyfill 墊片會污染原型鏈,這個既是缺點,也是優點,缺點是在開發第三方 JS 庫時不能這么干,會影響使用方的代碼。
## 情況選擇
* 如果你是應用開發(一般我們都不是開發工具庫,只是使用一些前端框架做業務~),推薦使用`@babel/preset-env`搭配 `useBuiltIns` 并按規則導入 polyfill 即可,而無需再使用`@babel/plugin-transform-runtime`,參考[issues]https://github.com/babel/babel/issues/10008"),,一勞永逸,不必使用 `@babel/plugin-transform-runtime`。
* 如果是框架/庫開發,需要安裝`@babel/runtime`、`@babel/plugin-transform-runtime`,同時使用 `@babel/preset-env` 去轉譯語法,但不用它的 polyfill(如前面推薦的那樣去配置即可)。
# issues
> [Using @babel/runtime-corejs2 and @babel/runtime-corejs3 leads to larger bundle sizes](https://github.com/babel/babel/issues/9853)
> [regeneratorRuntime error with 'external' helpers](https://github.com/rollup/plugins/issues/356#issuecomment-626398978)
# 參考
> [從零開始配置Babel](https://juejin.cn/post/6844904090833518600#heading-10)
> [用了babel還需要polyfill嗎??](https://juejin.cn/post/6845166891015602190)
> [jamiebuilds/babel-handbook](https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/user-handbook.md)
> [Babel 7 下配置 TypeScript 支持](https://zhuanlan.zhihu.com/p/102250469)
> [折騰 @babel/preset-env](https://blog.meathill.com/js/some-tips-of-babel-preset-env-config.html)
> [babel詳解](https://blog.liuyunzhuge.com/2019/09/02/babel%E8%AF%A6%E8%A7%A3%EF%BC%88%E5%9B%9B%EF%BC%89-core-js/)
- 講解 Markdown
- 示例
- SVN
- Git筆記
- github 相關
- DESIGNER'S GUIDE TO DPI
- JS 模塊化
- CommonJS、AMD、CMD、UMD、ES6
- AMD
- RequrieJS
- r.js
- 模塊化打包
- 學習Chrome DevTools
- chrome://inspect
- Chrome DevTools 之 Elements
- Chrome DevTools 之 Console
- Chrome DevTools 之 Sources
- Chrome DevTools 之 Network
- Chrome DevTools 之 Memory
- Chrome DevTools 之 Performance
- Chrome DevTools 之 Resources
- Chrome DevTools 之 Security
- Chrome DevTools 之 Audits
- 技巧
- Node.js
- 基礎知識
- package.json 詳解
- corepack
- npm
- yarn
- pnpm
- yalc
- 庫處理
- Babel
- 相關庫
- 轉譯基礎
- 插件
- AST
- Rollup
- 基礎
- 插件
- Webpack
- 詳解配置
- 實現 loader
- webpack 進階
- plugin 用法
- 輔助工具
- 解答疑惑
- 開發工具集合
- 花樣百出的打包工具
- 紛雜的構建系統
- monorepo
- 前端工作流
- 爬蟲
- 測試篇
- 綜合
- Jest
- playwright
- Puppeteer
- cypress
- webdriverIO
- TestCafe
- 其他
- 工程開發
- gulp篇
- Building With Gulp
- Sass篇
- PostCSS篇
- combo服務
- 編碼規范檢查
- 前端優化
- 優化策略
- 高性能HTML5
- 瀏覽器端性能
- 前后端分離篇
- 分離部署
- API 文檔框架
- 項目開發環境
- 基于 JWT 的 Token 認證
- 扯皮時間
- 持續集成及后續服務
- 靜態服務器搭建
- mock與調試
- browserslist
- Project Starter
- Docker
- 文檔網站生成
- ddd