共计 11532 个字符,预计需要花费 29 分钟才能阅读完成。
Babel 7 使用总结
2019-07-08
本文基于 Babel 7.4.5。
Babel 主要模块如上图所示,接下来将分别介绍。
1. @babel/core
@babel/core
主要是进行代码转换的一些方法,可以将源代码根据配置转换成兼容目标环境的代码。
import * as babel from "@babel/core";
babel.transform("code();", options, function(err, result) {
result.code;
result.map;
result.ast;
});
2. @babel/cli
@babel/cli
是 babel 提供的命令行工具,用于命令行下 编译 源代码。
首先安装依赖:
npm install --save-dev @babel/core @babel/cli
新建一个 js 文件:
let array = [1,2,3,4,5,6];
array.includes(function(item){return item>2;})
class Robot {constructor (msg) {this.message = msg}
say () {alertMe(this.message)
}
}
Object.assign({},{a:1,b:2})
const fn = () => 1;
new Promise();
执行转换:
npx babel index.js --out-file out.js
可以发现输出代码没有变化,这是因为没有进行配置来确定怎么进行转换。
3. @babel/plugin*
babel 是通过插件来进行代码转换的,例如箭头函数使用 plugin-transform-arrow-functions
插件来进行转换。
首先安装该插件:
npm install --save-dev @babel/plugin-transform-arrow-functions
可以通过 @babel/cli 传参或者配置文件的方式使用插件:
-
@babel/cli
npx babel index.js --out-file out.js --plugins=@babel/plugin-transform-arrow-functions
则可以得到 out.js 文件,可以看到箭头函数已经被转换。
let array = [1, 2, 3, 4, 5, 6]; array.includes(function (item) {return item > 2;}); class Robot {constructor (msg) {this.message = msg} say () {alertMe(this.message) } } Object.assign({}, { a: 1, b: 2 }); const fn = function () {return 1;}; new Promise();
-
配置文件 babel.config.js(javascript 写法)或.babelrc(json 写法),使用配置文件是更加常用的方式。
module.exports = function (api) {api.cache(true); const plugins = ["@babel/plugin-transform-arrow-functions"]; return {plugins}; }
4. @babel/presets
我们在 index.js 中使用了多种 es6 的语法,一个个的导入插件很麻烦,presets 是一组预设好的插件集合。官方为常见环境组装了一些 presets (当然也可以自己配置):
- @babel/preset-env
- @babel/preset-flow
- @babel/preset-react
- @babel/preset-typescript
我们使用 @babel/preset-env 为例(使用前需 npm install @babel/preset-env):
module.exports = function (api) {api.cache(true);
const presets = [["@babel/preset-env"]
];
return {presets};
}
得到的结果如下, 可以看到箭头函数被编译、es6 类、let 声明被编译了。
"use strict";
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 array = [1, 2, 3, 4, 5, 6];
array.includes(function (item) {return item > 2;});
var Robot =
/*#__PURE__*/
function () {function Robot(msg) {_classCallCheck(this, Robot);
this.message = msg;
}
_createClass(Robot, [{
key: "say",
value: function say() {alertMe(this.message);
}
}]);
return Robot;
}();
Object.assign({}, {
a: 1,
b: 2
});
var fn = function fn() {return 1;};
new Promise();
但是可以看到数组的实例方法 includes、对象的静态方法,以及 promise 并没有被编译。
这是因为 babel 把 Javascript 语法为 syntax 和 api,api 指那些我们可以通过 函数重新覆盖的语法,类似 includes,map,includes,Promise,凡是我们能想到重写的都可以归属到 api。syntax 指像箭头函数,let,const,class,依赖注入 Decorators 等等这些,我们在 Javascript 在运行是无法重写的,想象下,在不支持的浏览器里不管怎么样,你都用不了 let 这个关键字。
@babel/presets 默认只对 syntax 进行转换,我们需要使用 @babel/polyfill 来提供对 api 的的支持。
5. @babel/polyfill
@babel/polyfill 由 core-js2 和 regenerator-runtime 组成,后者是 facebook 开源库,用来实现对 generator、async 函数等的支持,前者是 js 标准库,包含不同版本 javascipt 语法的实现。
只要在 js 文件的入口顶部引入 @babel/polyfill 就可以在后问的代码中自由的使用 es6 api 了。
但是整体 @babel/polyfill 整个包体积较大,我们通常只使用了其中一部分方法,而引入整个库显然是不合适的。所以你可以只引入使用的方法:
import 'core-js/features/array/from'; // <- at the top of your entry point
import 'core-js/features/array/flat'; // <- at the top of your entry point
import 'core-js/features/set'; // <- at the top of your entry point
import 'core-js/features/promise'; // <- at the top of your entry point
Array.from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3]
[1, [2, 3], [4, [5]]].flat(2); // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x => console.log(x)); // => 32
如果你不想污染全局命名空间(例如在写一个 npm 库时,要保持其隔离性)。可以引入纯净版:
import from from 'core-js-pure/features/array/from';
import flat from 'core-js-pure/features/array/flat';
import Set from 'core-js-pure/features/set';
import Promise from 'core-js-pure/features/promise';
from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3]
flat([1, [2, 3], [4, [5]]], 2); // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x => console.log(x)); // => 32
preset-env 的配置项中的 useBuiltIns
属性可以方便 @babel/polyfill 的使用。
-
useBuiltIns:false(default)
: 此时不对polyfill
做操作。如果引入@babel/polyfill
,则无视配置的浏览器兼容,引入所有的polyfill
。 -
useBuiltIns:"entry"
: 根据配置的浏览器兼容,引入浏览器不兼容的polyfill
。需要在入口文件手动添加import '@babel/polyfill'
,会自动根据browserslist
替换成浏览器不兼容的所有polyfill
。 -
useBuiltIns:"usage"
: 不需要在文件顶部手动引入 @babel/polyfill,会根据代码中的使用进行按需添加。
在这里使用 useBuiltIns:"usage"
作为示例,babel.config.js 文件如下:
module.exports = function (api) {api.cache(true);
const presets = [
["@babel/preset-env",
{
"useBuiltIns": "usage",
"targets":{"browsers":["> 1%", "last 2 versions", "not ie <= 8"]
}
}
]
];
return {
presets,
// plugins
};
}
得到的编译结果:
"use strict";
require("core-js/modules/es6.promise");
require("core-js/modules/es6.object.to-string");
require("core-js/modules/es6.object.assign");
require("core-js/modules/es7.array.includes");
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 array = [1, 2, 3, 4, 5, 6];
array.includes(function (item) {return item > 2;});
var Robot =
/*#__PURE__*/
function () {function Robot(msg) {_classCallCheck(this, Robot);
this.message = msg;
}
_createClass(Robot, [{
key: "say",
value: function say() {alertMe(this.message);
}
}]);
return Robot;
}();
Object.assign({}, {
a: 1,
b: 2
});
var fn = function fn() {return 1;};
new Promise();
可以看到实现了 polyfill 的按需引入。但是在配置文件中未指定 core-js 版本时,默认会使用 core-js2。命令行会出现如下提示:
WARNING: We noticed you’re using the
useBuiltIns
option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via thecorejs
option.
这是因为 core-js3 已经发布,@babel/polyfill 不支持从 core-js2 到 core-js3 的平滑过渡,所以在 babel 7.4 版本中,已经废弃 @babel/polyfill(只能用 core-js2),而是直接引入 core-js3 和 regenerator-runtime 代替。
import "@babel/polyfill";
// migration
import "core-js/stable";
import "regenerator-runtime/runtime";
使用 core-js3 有很多优点,首先就是新,包含很多新特性,其次就是可以配合 @babel/runtime(后文详述)。更多优点见 core-js@3, babel and a look into the future。
使用 core-js3 是 babel.config.js 如下:
module.exports = function (api) {api.cache(true);
const presets = [
["@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs":3,
"targets":{"browsers":["> 1%", "last 2 versions", "not ie <= 8"]
}
}
]
];
return {
presets,
// plugins
};
}
仔细观察上面的编译结果可以发现有两个问题。
- 高阶语法向低阶语法转化时引入了了很多 helper 函数(如_classCallCheck)。当文件数量很多时,每个文件都引入这些 helper 函数会使得文件体积增大,怎么这些 helper 函数抽离到单独的模块,然后按需引入呢?
- 虽然 polyfill 是按需引入的,但是会污染全局命名空间,当你写的是公共库时,可能会与使用者本地的方法产生冲突。例如你在你的库中引入了 polyfill 中的 Promise,使用者自身定义了自己的 Promise,这就容易产生冲突。如何将你的公共库中引入的 polyfill api 隔离起来呢?
要解决这两个问题,就要需要使用 @babel/runtime 和 @babel/plugin-transform-runtime 了。
6. @babel/runtime
@babel/runtime 依赖 @babel/helpers 和 regenerator-runtime,helper 函数都可以从这里面引入,手动的肯定不可能,于是 babel 提供了 @babel/plugin-transform-runtime
来替我们做这些转换。
babel.config.js 文件为:
module.exports = function (api) {api.cache(true);
const presets = [
["@babel/preset-env",
{
"useBuiltIns": "usage",
"targets":{"browsers":["> 1%", "last 2 versions", "not ie <= 8"]
}
}
]
];
const plugins = [["@babel/plugin-transform-runtime"]
]
return {
presets,
plugins
};
}
得到的编译结果是:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
require("core-js/modules/es6.promise");
require("core-js/modules/es6.object.to-string");
require("core-js/modules/es6.object.assign");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
require("core-js/modules/es7.array.includes");
var array = [1, 2, 3, 4, 5, 6];
array.includes(function (item) {return item > 2;});
var Robot =
/*#__PURE__*/
function () {function Robot(msg) {(0, _classCallCheck2.default)(this, Robot);
this.message = msg;
}
(0, _createClass2.default)(Robot, [{
key: "say",
value: function say() {alertMe(this.message);
}
}]);
return Robot;
}();
Object.assign({}, {
a: 1,
b: 2
});
var fn = function fn() {return 1;};
new Promise();
可以看到我们的第一个问题已经圆满解决了。
解决第二个问题需要使用 @babel/plugin-transform-runtime option 中的 corejs 参数。默认为 false,不对 polyfill 进行处理。可以设为不同版本的 core-js。
例如使用 core-js2 时,需要先安装
npm install --save @babel/runtime-corejs2
配置文件为:
module.exports = function (api) {api.cache(true);
const presets = [
["@babel/preset-env",
{
"useBuiltIns": "usage",
"targets":{"browsers":["> 1%", "last 2 versions", "not ie <= 8"]
}
}
]
];
const plugins = [["@babel/plugin-transform-runtime",{corejs:2}]
]
return {
presets,
plugins
};
}
得到的结果是:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
var _assign = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/assign"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
require("core-js/modules/es7.array.includes");
var array = [1, 2, 3, 4, 5, 6];
array.includes(function (item) {return item > 2;});
var Robot =
/*#__PURE__*/
function () {function Robot(msg) {(0, _classCallCheck2.default)(this, Robot);
this.message = msg;
}
(0, _createClass2.default)(Robot, [{
key: "say",
value: function say() {alertMe(this.message);
}
}]);
return Robot;
}();
(0, _assign.default)({}, {
a: 1,
b: 2
});
var fn = function fn() {return 1;};
new _promise.default();
可以看到 polyfill 引入时得到了一个别名,可以避免全局变量污染,但是可以发现实例方法 includes 并没有得到相应的处理。这是 core-js2 没有解决的问题,随着 2019 年 3 月 core-js3 的发布,这个问题得到了完美解决。我们将 corejs 设为 3,得到了结果如下:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _assign = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/assign"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var array = [1, 2, 3, 4, 5, 6];
(0, _includes.default)(array).call(array, function (item) {return item > 2;});
var Robot =
/*#__PURE__*/
function () {function Robot(msg) {(0, _classCallCheck2.default)(this, Robot);
this.message = msg;
}
(0, _createClass2.default)(Robot, [{
key: "say",
value: function say() {alertMe(this.message);
}
}]);
return Robot;
}();
(0, _assign.default)({}, {
a: 1,
b: 2
});
var fn = function fn() {return 1;};
new _promise.default();
7. @babel/register
经过 babel 的编译后,我们的源代码与运行在生产下的代码是不一样的。
babel-register 则提供了动态编译。换句话说,我们的源代码能够真正运行在生产环境下,不需要 babel 编译这一环节。
我们先在项目下安装 babel-register:
$ npm install --save-dev @babel/register
然后在入口文件中 require
:
require('@babel/register')
require('./app')
在入口文件头部引入 @babel/register
后,我们的 app
文件中即可使用任意 es2015 的特性。
当然,坏处是动态编译,导致程序在速度、性能上有所损耗。(我们在启动测试脚本的时候可以使用)
7. @babel/node
我们上面说,babel-register 提供动态编译,能够让我们的源代码真正运行在生产环境下 – 但其实不然,我们仍需要做部分调整,比如新增一个入口文件,并在该文件中 require('@babel/register')
。而 babel-node 能真正做到一行源代码都不需要调整:
$ npm install --save-dev @babel/core @babel/node
$ npx babel-node app.js
只是,请不要在生产环境中使用 babel-node,因为它是动态编译源代码,应用启动速度非常慢
参考
http://babel.docschina.org/docs/en/babel-plugin-transform-runtime#technical-details
https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md
https://blog.hhking.cn/2019/0…
https://segmentfault.com/a/11…
https://zhuanlan.zhihu.com/p/…
https://blog.zfanw.com/babel-…
https://www.thebasement.be/up…