看着Babel
的官方文档学习实在是困难,大概仍是由于那年英语太差,所幸有不少大大们对文档进行了翻译。另外看了不少前辈们的文章,受益不浅。可是纸上得来终觉浅,眼睛老是在欺骗我。当我觉得我看懂了,闭上眼睛自省一下:“我会了什么?”这个时候才发现一切都是我觉得的。因此又从新整理了本身的思路,并写了下来,方便回顾。node
Babel 是一个 JavaScript 编译器react
Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便可以运行在当前和旧版本的浏览器或其余环境中。webpack
Babel 能干什么?
web
通俗地讲,Babel 只是转移新标准引入的语法,例如 ES6 中的箭头函数、解构等。而新标准中新增的方法、函数等就须要经过 Polyfill 在目标环境中添加缺失的特性(即新标准中新增的方法、函数等)来解决。typescript
Babel编译的过程分为三个阶段:npm
本文主要分析的是三个阶段中的 转换,大部分的内容参考自 Babel官网。json
Babel虽然能够开箱即用,可是若是什么都不配置,那么它会将代码解析以后再输出一样的代码。因此咱们须要经过配置插件(Plugins)和预设(Preset)来转换咱们的代码。promise
为了更清楚的了解 Babel 是如何 “起做用” 的,咱们能够进行如下的准备工做。浏览器
建立demobash
新建文件夹babel-test
,进入该目录下。使用 npm init -y
初始化,新建src/index.js
文件,并添加文件内容const fn = () => 1;
安装必要的依赖
@babel/core
是 Babel 的核心,包含了全部的核心 API。
@babel/cli
是命令行工具,为咱们提供 babel 命令来编译文件。
# 当前文章安装的版本:
# @babel/core: ^7.9.0
# @babel/cli: ^7.8.4
npm install --save-dev @babel/core @babel/cli
复制代码
添加编译命令
在package.json
文件中的的script
字段下添加一项:
{
...
"scripts": {
"trans": "babel src --out-dir lib"
}
...
}
复制代码
该命令行的做用是将babel-test/src
目录下的每一个JavaScript文件转换并输出到babel-test/lib
目录。
目前package.json
文件内容以下图所示:
npm run trans
咱们会发现
lib/index.js
中输出的内容和
src/index.js
是一致的,这是由于咱们没有告诉
Babel
应该执行什么样的转换,因此下面就须要咱们设置插件和预设来解决这个问题。
插件用于转换咱们的代码,分为两种:语法插件和转换插件。
语法插件
只容许Babel
解析(parse)特定类型的语法(而不是转换)。
转换插件
转换插件将启用相应的语法插件,所以咱们没必要同时指定这两个插件。
当咱们启用转换插件时,会自动启用相应的语法插件进行解析,而后经过转换插件进行转换。想要详细了解有哪些转换插件能够看这里:插件。
插件的使用
若是插件是在项目根目录下经过 npm 安装的,那咱们能够输入插件的名称,babel 会自动检查它是否已经被安装到node_modules
目录下。
要将src/index.js
中的箭头函数转换成普通函数,咱们能够借助官方提供的转换插件@babel/plugin-transform-arrow-functions
。
npm isntall --save-dev @babel/plugin-transform-arrow-functions
复制代码
新建bebal-test/.babelrc
文件并添加上面安装好的插件,文件内容以下:
{
"plugins": ["@babel/plugin-transform-arrow-functions"]
}
复制代码
配置完成后,再次执行npm run trans
命令,咱们能够看到lib/index.js
文件中已是咱们想要的内容了。
固然,在实际开发中,若是咱们一个一个这样去配置转换插件,那也麻烦了。所幸Babel
为咱们提供了预设Preset
。
Preset 能够做为 Babel 插件的组合。
官方针对咱们经常使用的环境编写了一些Preset
:
如需建立preset,导出一份配置便可。
module.exports = function() {
return {
plugins: [
"pluginA",
"pluginB",
"pluginC",
]
};
}
复制代码
preset 能够包含其余的 preset,以及带有参数的插件。
module.exports = () => ({
presets: [
require("@babel/preset-env"),
],
plugins: [
[require("@babel/plugin-proposal-class-properties"), { loose: true }],
require("@babel/plugin-proposal-object-rest-spread"),
],
});
复制代码
@babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s). This both makes your life easier and JavaScript bundles smaller!(官网)
意思是:@babel/preset-env
是一个灵活的预设,你不须要管理目标环境须要的语法转换或浏览器polyfills,就可使用最新的 JavaScript,同时也会让 JavaScript 打包后的文件更小。
那么@babel/preset-env
的做用是什么呢?
须要注意的是,@babel/preset-env它不支持stage-x插件。
安装
# @babel/preset-env: ^7.9.0
npm install --save-dev @babel/preset-env
复制代码
修改.babelrc
文件内容:
{
"presets": [
"@babel/preset-env"
]
}
复制代码
栗子🌰
修改src/index.js
中的内容以下所示:
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], x => x + x); // [2, 4, 6]
let promsie = new Promise();
复制代码
执行编译命令npm run trans
,lib/index.js
中的内容以下所示:
"use strict";
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], function (x) {
return x + x;
}); // [2, 4, 6]
let promsie = new Promise();
复制代码
经过上面两段代码的对比,咱们能够看到只有原先的箭头函数发生了转换。而Array.from
方法和Promise
构造函数并无发生转换。
小结:
由于@babel/preset-env转换的是语法,不包含新增的全局变量、方法等,因此须要加载浏览器polyfills来完善代码转换。(后面介绍完Polyfill会对.babelrc
文件进行完善)
@babel/preset-env
会拿到咱们指定的目标环境,检查这些映射表来编译一系列的插件并传给 Babel。针对基于浏览器的或Electron-based的项目,官方推荐使用.browserslistrc
文件来指定目标环境。如果咱们没有设置targets或ignoreBrowserslistConfig配置项,@babel/preset-env
默认会使用.browserslistrc
中的配置。
好比,当咱们只想包括大于0.25%市场份额的浏览器的那些polyfill和代码转换:
options(关于@babel/preset-env
的更多options配置)
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 0.25%, not dead"
}
]
]
}
复制代码
browserslist
> 0.25%
not dead
复制代码
package.json
{
"browserslist": "> 0.25%, not dead"
}
复制代码
polyfill 的中文意思是垫片,用来垫平不一样目标环境下的差别,让新的内置函数、实例方法等在低版本浏览器中也可使用。
Babel 7.4.0 版本开始,@babel/polyfill 已经被废弃不推荐使用,支持直接导入 core-js/stable(polyfill ECMAScript 特性)和 regenerator-runtime/runtime(须要使用转换后的 generator 函数)(官网)。
这里我就根据本身的理解介绍一下 Babel 7.4.0 版本以后 Polyfill 的用法,若是有理解错的地方,还请各位看官大大帮我指出。
core-js
和regenerator-runtime
将模拟完整的ES2015 +环境(不包含第4阶段的提议),在 JavaScript 代码执行前引入。
This means you can use new built-ins like Promise or WeakMap, static methods like Array.from or Object.assign, instance methods like Array.prototype.includes, and generator functions (provided you use the regenerator plugin). The polyfill adds to the global scope as well as native prototypes like String in order to do this.
意思是咱们可使用: 新的内置函数 如 Promise 和 WeakMap; 新的静态方法 如 Array.from 和 Object.assign; 新的实例方法 如 Array.prototype.includes和generator函数(前提是使用了 @babel/plugin-transform-regenerator 插件)。 polyfill
将添加到全局范围以及本机原型(如String)中,以便执行此操做。
安装core-js
和regenerator-runtime
。
# core-js: ^3.6.4;提供 es 新的特性。
# regenerator: ^0.14.4;应用代码中用到generator、async函数的话,提供对 generator 支持。
npm install --save core-js regenerator
复制代码
安装完成后修改src/index.js
文件。
import "core-js/stable";
import "regenerator-runtime/runtime";
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], x => x + x); // [2, 4, 6]
let promsie = new Promise();
复制代码
执行npm run trans
命令后,查看lib/index.js
中的内容。
"use strict";
require("core-js/stable");
require("regenerator-runtime/runtime");
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], function (x) {
return x + x;
}); // [2, 4, 6]
var promsie = new Promise();
复制代码
此时咱们的代码在低版本浏览器中已经可以正常运行了。
经过webpack打包后,发现包的大小为127kb。这是由于咱们引入了所有的Polyfill
致使压缩后包的体积变大,因此咱们更但愿按需引入,所幸Babel
已经为咱们提供了解决方案(webpack的配置文件在文末会贴出来)。
@babel/preset-env
提供了一个 useBuiltIns
参数,设置值为 usage
时,就只会包含代码须要的 polyfill 。设置该参数时,必需要同时设置 corejs,前面已经安装了这里就不重复提了。(core-js@2已经不会再添加新特性,新特性都会添加到 core-js@3)
优化
修改配置文件.babelrc
。
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}
复制代码
修改src/index.js
。
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], x => x + x); // [2, 4, 6]
let promsie = new Promise();
async function fn() {
return 1
}
复制代码
执行npm run trans
命令后,查看lib/index.js
中的内容。
"use strict";
require("core-js/modules/es.array.from");
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.promise");
require("core-js/modules/es.string.iterator");
require("regenerator-runtime/runtime");
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], function (x) {
return x + x;
}); // [2, 4, 6]
var promsie = new Promise();
function fn() {
return _fn.apply(this, arguments);
}
function _fn() {
_fn = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
return _context.abrupt("return", 1);
case 1:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return _fn.apply(this, arguments);
}
复制代码
从输出结果能够看到,咱们已经实现了按需引入Polyfill
,再次打包后发现包的大小已经变成了30kb,效果非常显著,可是这种使用方式始终存在污染全局环境的问题。
为了解决这个问题,Babel
给咱们提供了@babel-runtime
。它将开发者依赖的全局内置对象等,抽取成单独的模块,并经过模块导入的方式引入,避免了对全局环境的污染。
@babel/runtime
是一个包含 Babel modular runtime helpers 和 regenerator-runtime 的库。
与Polyfill
的区别:
Polyfill 会修改(覆盖)新增的内置函数、静态方法和实例方法。
@babel/runtime 不会,它只是引入一些 helper 函数,创造对应的方法。
安装(@babel-runtime
是代码运行时须要的依赖,因此须要做为生产依赖安装)
npm install --save @babel/runtime
复制代码
有时Babel
可能会在输出中注入一些跨文件的相同代码,所以可能会被重用。
栗子🌰
修改src/index.js
中的内容:
class Parent {}
复制代码
执行编译命令npm run trans
,lib/index.js
中的内容:
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Parent = function Parent() {
_classCallCheck(this, Parent);
};
复制代码
这意味着每一个包含类的文件都将引入_classCallCheck,重复的代码注入必然致使包的变大。这个时候就须要使用插件@babel/plugin-transform-runtime
。
@babel/plugin-transform-runtime
用于构建过程的代码转换,而 @babel/runtime
是提供帮助方法的模块,这样就能够避免重复的代码注入。
@babel/plugin-transform-runtime
是一个能够重复使用 Babel 注入的帮助程序,以节省代码大小的插件。
Babel使用很小的帮助器来完成例如Class的功能。默认状况下,它将被添加到须要它的每一个文件中。有时不须要重复,特别是当咱们的应用程序分布在多个文件中时。
这是@babel/plugin-transform-runtime
插件的来源:全部帮助程序都将引用该模块,@babel/runtime
以免在编译后的输出中出现重复。运行时将被编译到咱们的构建中。
该转换器的另外一个目的是为咱们的代码建立一个沙盒环境。若是直接引入core-js
或@babel/polyfill
,它提供了诸如内置插件Promise,Set和Map等,这些会污染全局环境。尽管这对于应用程序或命令行工具多是能够的,可是若是咱们的代码是要发布供他人使用的库,或者咱们没法彻底控制代码运行的环境,则将成为一个问题。@babel/plugin-transform-runtime
会将这些内置别名做为core-js
的别名,所以咱们能够无缝使用它们,而无需使用polyfill。(官网)
安装
npm install --save-dev @babel/plugin-transform-runtime
复制代码
修改.babelrc
文件内容:
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
复制代码
执行编译命令npm run trans
:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var Parent = function Parent() {
(0, _classCallCheck2["default"])(this, Parent);
};
复制代码
从lib/index.js
中输出的内容能够看出classCallCheck不是直接注入到代码中,而是从 @babel/runtime
中引入,这就避免了相同代码的重复注入。
经过添加配置避免全局环境被污染。
安装@babel/runtime-corejs3
npm install --save @babel/runtime-corejs3
复制代码
修改src/index.js
:
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], x => x + x); // [2, 4, 6]
let promsie = new Promise();
async function fn() {
return 1
}
复制代码
修改.babelrc
:
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}
复制代码
执行编译命令npm run trans
,lib/index.js
中的内容:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _from = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/from"));
(0, _from["default"])('foo'); // ['f', 'o', 'o']
(0, _from["default"])([1, 2, 3], function (x) {
return x + x;
}); // [2, 4, 6]
var promsie = new _promise["default"]();
function fn() {
return _fn.apply(this, arguments);
}
function _fn() {
_fn = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee() {
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
return _context.abrupt("return", 1);
case 1:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return _fn.apply(this, arguments);
}
复制代码
从输出文件中能够看到,@babel/plugin-transform-runtime
经过模块导入的方式引入所需的功能代码,避免了对全局环境的污染。
# 本文使用的webpack相关的版本
# webpack-cli@3.3.11
# webpack@4.42.0
# clean-webpack-plugin@3.0.0
# babel-loader@8.1.0
npm install --save-dev webpack-cli webpack babel-loader clean-webpack-plugin
复制代码
babel-test/webpack.config.js
文件const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode: 'production',
entry: './lib/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash].js'
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader'
}
]
},
plugins: [
new CleanWebpackPlugin()
]
}
复制代码
添加命令
在package.json
文件中的的script
字段下添加一项:
{
...
"scripts": {
"build": "webpack --mode=production"
}
...
}
复制代码
执行命令
npm run build
复制代码
纸上得来终觉浅,绝知此事要躬行。
另外也谢谢各位大大的文章,让我受益不浅,下面的连接能够进去看看哦。 7
若是这篇文章一样对你有帮助,那就给我留个赞吧,谢谢~
参考连接
* Babel官网
* 不可错过的 Babel7 知识
* Babel 社区概览* ...