[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