Fork me on GitHub

Babel

Babel7 概念和用法
https://github.com/YvetteLau/Blog

参考
https://mp.weixin.qq.com/s/xTfjMG2graIrfGGqhue_Jg

概念

Babel 是一个 JS 编译器。
主要将 ECMAScript2015+版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境。

  • 语法转换
  • 通过Polyfill方式在目标环境中添加缺失特性(@babel/polyfill模块)
  • 源码转换

核心库

@babel/core 使用 babel 必须安装

命令行工具

@babel/cli 适合安装在项目里
@babel/node 适合全局安装

1
npm install --save-dev @babel/core @babel/cli

命令配置,package.json

1
2
3
"scripts": {
"compiler": "babel src --out-dir lib --watch"
}

执行npm run compiler就可以编译了
现在运行不会有编译任何文件,如果需要Babel做一些实际工作,就需要为其添加插件(plugin)

配置文件

  • babel.config.js
    以编程的方式配置文件或者希望编node_modules目录下的模块
    根目录下创建babel.config.js

具体规则参考:文档

  • .babelrc
    简单的并且用于单个软件包的配置
    根目录下创建.babelrc

    1
    2
    3
    4
    {
    "presets": [],
    "plugins": []
    }

具体规则参考:文档

  • package.json
    可以将.babelrc中的配置信息作为babelkey 添加到package.json文件中
1
2
3
4
5
6
7
{
"name": "my-package",
"babel": {
"presets": [],
"plugins": []
}
}
  • .babelrc.js
    配置与.babelrc相同,但是可以使用 js 编写
1
2
3
4
5
//可以在其中调用 Node.js 的API
const presets = [];
const plugins = [];

module.exports = { presets, plugins };

插件

  • 语法插件
    解析特定类型的语法

  • 转换插件
    转换插件会启用相应的语法插件,先解析,再转换

插件的使用

根目录新建.babelrc文件,直接使用插件名字,Babel会自动检查  是否已安装

1
2
3
4
//.babelrc
{
"plugins": ["@babel/plugin-transform-arrow-functions"]
}

运行npm run compiler,index.js 编译后

1
2
3
const a = function () {
console.log('test');
};

箭头函数转为正常函数了,如果想将其他新的 JS 特性转换成低版本,需要对应的plugin,如果一个一个配置太麻烦,可以使用预设

预设

创建一个preset,可以使用一组插件

  • @babel/preset-env
  • @babel/preset-flow
  • @babel/preset-React
  • @babel/preset-typescript
    注意,babel7 开始,所有针对标准天阶段的功能所编写的预设(stage preset)已经被弃用,以及移除@babel/preset-stage-x

@babel/preset-env

作用:对我们所使用的并且目标浏览器缺失的功能进行代码转换和加载polyfill。不进行任何配置时,@babel/preset-env所包含的插件将支持所有最新的 JS 特性(ES2015/es6,ES2016/es7 等,不包括 stage 阶段),将其转换成 ES5 代码。如果使用了 stage 阶段的语法,编译时会报错,需要安装对应的插件。
@babel/preset-env会很具配置的目标环境,来生成插件列表来编译,对于基于浏览的项目,可以使用.browserslistrc文件来指定目标环境。也可以在 package.json 里面添加 key 为browserslist配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
//create-React-app默认配置
"browserslist": {
"production": [
">0.2%", //包括浏览器市场份额超过0.2%的用户所需的 polyfill 和代码转换
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},

新建.browserslistrc,配置如下

1
last 2 Chrome versions

index 编译后箭头函数还在,因为 chrome 最新的两个版本已经支持箭头函数

1
2
3
4
5
"use strict";

const a = () => {
console.log('test');
};

如果不是要兼容所有的浏览器和环境,推荐你指定目标环境,编译代码能够保持最小。

内置函数、实例方法

修改index.js

1
2
3
4
const isHas = [1, 2, 3].includes(2);
const p = new Promise((resolve, reject) => {
resolve();
});

编译后

1
2
3
4
5
6
"use strict";

const isHas = [1, 2, 3].includes(2);
const p = new Promise((resolve, reject) => {
resolve();
});

语法转换只是将高版本的语法转换为低版本的,但是新的内置函数和实例方法无法转换。
polyfill垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用。

@babel/polyfill

包括core-js和一个自定义的regenerator runtime模块,可以模拟完整的 es2015+环境(不包括第 4 阶段前的提议)

内置组件:Promise,WeakMap
静态方法:Array.from,Object.assign
实例方法:Array.prototype.includes
生成器函数(还需要@babel/plugin-transform-regenerator插件)
为了实现以上功能,polyfill 将添加到全局范围和类似 String 的内置原型中(会造成全局污染)

安装

1
npm install --save @babel/polyfill

注意:不使用 –save-dev,因为这是一个需要在源码之前运行的垫片。

1
2
3
4
5
import "@babel/polyfill";
const isHas = [1, 2, 3].includes(2);
const p = new Promise((resolve, reject) => {
resolve();
});

编译后

1
2
3
4
5
6
7
8
"use strict";

require("@babel/polyfill");

const isHas = [1, 2, 3].includes(2);
const p = new Promise((resolve, reject) => {
resolve();
});

@babel/polyfill需要在其他代码之前引入,在webpack中可以进行配置

1
2
3
4
entry:{
require.resolve('./polyfills'),
path.resolve('./index')
}

.polyfills.js

1
2
//当然,还可能有一些其它的 polyfill,例如 stage 4之前的一些 polyfill
import '@babel/polyfill';

编译后

1
2
3
4
5
6
7
8
"use strict";

require("@babel/polyfill");

var isHas = [1, 2, 3].includes(2);
var p = new Promise(function (resolve, reject) {
resolve();
});

现在的代码在高版本或低版本或 node 环境都能运行了。但是会引入整个@babel/polyfill包,会导致打包的体积增大,我们期望按需加载。

  • 参数useBuiltIns参数,设置值为usage时,就只会包含代码需要的polyfill
  • 必须同时设置core.js,默认’core.js’:2
    注意:需要安装@babel/polyfill,默认安装 corejs@2
    使用 core-js@3 的原因 core-js@2 分支已经不会添加新特性,新特性都会添加到 core-js@3。例如 Array.prototype.flat()
    安装依赖
1
npm install --save core-js@3

修改配置文件

1
2
3
4
5
6
7
8
9
10
11
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}

编译后

1
2
3
4
5
6
7
8
9
10
11
12
"use strict";

require("core-js/modules/es.array.includes");

require("core-js/modules/es.object.to-string");

require("core-js/modules/es.promise");

var isHas = [1, 2, 3].includes(2);
var p = new Promise(function (resolve, reject) {
resolve();
});

在 useBuiltIns 参数值为 usage 时,仍然需要安装 @babel/polyfill,
如果使用到了 async/await,那么编译出来的代码需要 require(“regenerator-runtime/runtime”),在 @babel/polyfill 的依赖中。也可以只安装 regenerator-runtime/runtime 取代安装 @babel/polyfill。

1
2
3
4
5
6
import "@babel/polyfill";

async function a() {
await console.log(1);
console.log(0);
}

编译为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"use strict";

require("core-js/modules/es.object.to-string");

require("core-js/modules/es.promise");

require("regenerator-runtime/runtime");

function a() {
return regeneratorRuntime.async(function a$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return regeneratorRuntime.awrap(console.log(1));

case 2:
console.log(0);

case 3:
case "end":
return _context.stop();
}
}
});
}

@

Babel 会使用很小的辅助函数来实现类似 _createClass 等公共方法。默认情况下,它将被添加(inject)到需要它的每个文件中。
修改 index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import "@babel/polyfill";

class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
getX() {
return this.x;
}
}

let cp = new Point(25, 6);

编译后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
"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 Point =
/*#__PURE__*/
function () {
function Point(x, y) {
_classCallCheck(this, Point);

this.x = x;
this.y = y;
}

_createClass(Point, [{
key: "getX",
value: function getX() {
return this.x;
}
}]);

return Point;
}();

var cp = new Point(25, 6);

如果多个文件使用了 class,_classCallCheck,_defineProperties,_createClass,这些方法就会被多次inject,导致包变大。

@babel/plugin-transform-runtime

一个可以重复使用Babel注入的帮助程序,以节省代码大小的插件。
@babel/plugin-transform-runtime需要配合@babel/runtime使用。
安装

1
2
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime

除了减少编译后代码的体积外,@babel/plugin-tranform-runtime还会创建一个沙盒环境,如果使用@babel/polyfill及其提供的内置程序会污染全局,虽然这对于应用程序或命令行工具可能是可以的,但是如果你的代码是要发布供他人使用的库,或者无法完全控制代码运行的环境,则将成为一个问题。

@babel/plugin-transform-runtime会将这些内置别名为core-js的别名,因此可以无缝使用它们,无需polyfill
修改配置

1
2
3
4
5
6
7
8
9
10
11
12
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [["@babel/plugin-transform-runtime"]]
}

重新编译 index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
"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 Point =
/*#__PURE__*/
function () {
function Point(x, y) {
(0, _classCallCheck2.default)(this, Point);
this.x = x;
this.y = y;
}

(0, _createClass2.default)(Point, [{
key: "getX",
value: function getX() {
return this.x;
}
}]);
return Point;
}();

var cp = new Point(25, 6);

帮助函数不是被直接inject到代码中,而是从@babel/runtime中引入。

如何避免全局污染

index.js

1
2
3
4
5
6
import "@babel/polyfill";

const isHas = [1, 2, 3].includes(2);
const p = new Promise((resolve, reject) => {
resolve();
});

编译后

1
2
3
4
5
6
7
8
9
10
11
12
"use strict";

require("core-js/modules/es.array.includes");

require("core-js/modules/es.object.to-string");

require("core-js/modules/es.promise");

var isHas = [1, 2, 3].includes(2);
var p = new Promise(function (resolve, reject) {
resolve();
});

现在的配置,Array.prototype 上新增了 includes 方法,并且新增了全局的 Promise 方法,污染了全局环境。
如果希望@babel/plugin-transform-runtime不仅处理帮助函数,同时也能加载polyfill,需要增加配置信息。
新增依赖@babel/runtimr-corejs3

1
npm install @babel/runtime-corejs3 --save

需要改配置文件,移除@babe/preset-env 的 useBuitIns 的配置,不然就重复了

1
2
3
4
5
6
7
8
9
10
11
12
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [["@babel/plugin-transform-runtime", { "corejs": 3 }]]
}

index.js

1
2
3
4
const isHas = [1, 2, 3].includes(2);
const p = new Promise((resolve, reject) => {
resolve();
});

编译为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));

var _context;

var isHas = (0, _includes.default)(_context = [1, 2, 3]).call(_context, 2);
var p = new _promise.default(function (resolve, reject) {
resolve();
});

没有直接去修改 Array.prototype,或者是新增 Promise 方法,避免了全局污染。如果上面 @babel/plugin-transform-runtime 配置的 core-js 是 “2”,其中不包含实例的 polyfill 需要单独引入。

插件/预设补充知识

执行顺序

  • 插件在预设前运行
  • 插件从前往后调用
  • 预设从后往前调用
1
2
3
4
{
"presets": ["@babel/preset-env", "@babel/preset-React"]
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-syntax-dynamic-import"]
}

先执行 @babel/plugin-proposal-class-properties,后执行 @babel/plugin-syntax-dynamic-import
先执行 @babel/preset-React, 后执行 @babel/preset-env。

参数结构

插件和预设都可以接受参数,参数由插件名和参数对象组成一个数组。

插件短名称

如果插件名称为 @babel/plugin-XXX,可以使用短名称@babel/XXX
如果插件名称为 babel-plugin-XXX,可以使用端名称 XXX,该规则同样适用于带有 scope 的插件

1
2
3
4
5
6
{
"plugins": [
"newPlugin", //同 "babel-plugin-newPlugin"
"@scp/myPlugin" //同 "@scp/babel-plugin-myPlugin"
]
}

stage1,2,3 阶段的编译插件

解决方法:https://segmentfault.com/a/1190000020237923

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"plugins": [
// Stage 0
"@babel/plugin-proposal-function-bind",

// Stage 1
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-logical-assignment-operators",
["@babel/plugin-proposal-optional-chaining", { "loose": false }],
["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }],
["@babel/plugin-proposal-nullish-coalescing-operator", { "loose": false }],
"@babel/plugin-proposal-do-expressions",

// Stage 2
["@babel/plugin-proposal-decorators", { "legacy": true }],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions",

// Stage 3
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
["@babel/plugin-proposal-class-properties", { "loose": false }],
"@babel/plugin-proposal-json-strings"
]
}
-------------本文结束感谢阅读-------------