# Babel
思考: 我们当前使用的是 ES5 的语法,随着 ECMAScript 不断推出新的标准,我们也需要跟随时代的脚步。那不支持新 ES 语法的浏览器怎么办呢?这一部分用户我们要遗弃吗?是否有方法让我们用上新标准且能让浏览器识别呢?
有这么一个工具,它可以将新标准的 ECMAScript 转化成当前和旧浏览器都能识别的向后兼容的 JavaScript 版本的代码,这就是 Babel (opens new window) 。
Babel 一词来源
"BABEL" 一词源自《圣经·旧约·创世纪》,在中文的《圣经》中被译为“巴别塔”。讲述的人类的祖先最初讲的是同一种语言。他们在底格里斯河和幼发拉底河之间,发现了一块非常肥沃的土地,于是就在那里定居下来,修起了城池。后来,他们的日子越过越好,决定修建一座可以通到天上去的高塔,这就是巴别塔(Babel)。他们用砖和河泥作为建筑的材料。直到有一天,高高的塔顶已冲入云霄。上帝耶和华得知此事,立即从天国下凡视察。上帝一看,又惊又怒,认为这是人类虚荣心的象征。上帝心想,人们讲同样的语言,就能建起这样的巨塔,日后还有什么办不成的事情呢?于是,上帝决定让人世间的语言发生混乱,使人们互相言语不通。于是,人们因无法沟通就停工不造塔了。就这样,上帝留下了历史上第一座“烂尾工程”--Towl of Babel。而 Babel 是人类语言第一次混乱纷杂的地方,该词在英语中也就被赋予了“嘈杂和混乱的场面,嘈杂的声音”等意思了。摘自 (opens new window)
# 改造
将之前的代码以 ES5 以上的新标准进行改造:
// src/util.js
// 随机数
export function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
// 加载图片
export async function loadImg(src) {
return new Promise((resolve) => {
const img = document.createElement("img");
img.src = src;
img.onload = () => {
resolve(img);
};
});
}
export function notUse() {
console.log("全局都没有引用的代码块");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/browser.js
const inBrowser = typeof window !== "undefined";
const UA = inBrowser && window.navigator.userAgent.toLowerCase();
const isIE = UA && /msie|trident/.test(UA);
const isEdge = UA && UA.includes("edg/");
const isChrome = UA && UA.includes("chrome") && !isEdge;
export function browser() {
return [`ie: ${isIE}`, `edge: ${isEdge}`, `chrome: ${isChrome}`].join(", ");
}
2
3
4
5
6
7
8
9
10
// src/element.js
import * as util from "./util";
// 使用 background 显示图片
export async function createBackgroudImg(src) {
const img = await util.loadImg(src);
const div = document.createElement("div");
div.style.width = img.width + "px";
div.style.height = img.height + "px";
div.style.background = `url(${src})`;
return div;
}
// 创建一个有随机数的节点
export function createRandomTextElement() {
const div = document.createElement("div");
div.innerText = `random: ${util.getRandomInt(1000, 9999)}`;
return div;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/index.js
// 无改动
2
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="./dist/library.js"></script>
<script>
// 随机数文本节点
var randomText = window.library.createRandomTextElement();
document.body.appendChild(randomText);
// 显示浏览器类型
var browserText = document.createElement("div");
browserText.innerHTML = window.library.browser();
document.body.appendChild(browserText);
// 添加图片 callback -> Promise
window.library
.createBackgroudImg("https://hxin.link/images/avatar.jpg")
.then(function(el) {
document.body.appendChild(el);
});
</script>
</body>
</html>
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
运行 node build.js
,打开 index.html
。运行结果:Chrome 正常运行,IE 无法运行。这是因为 IE 无法识别新标准的部分语法和 API。可以在 IE 的控制台输入字符串模板、箭头函数、const
、String.prototype.includes
等,均无法使用。浏览器兼容性 (opens new window)
# 安装
Babel 就像一个平台,其实我们使用的基本都是 Babel 上的插件。例如:语法转化的 @babel/preset-env (opens new window) 插件。
# 安装核心
npm install @babel/core --save-dev
# 安装具有语法转化功能的插件
npm install @babel/preset-env --save-dev
# 如果通过 cli 运行 Babel,你还需要安装
npm install webpack-cli --save-dev
2
3
4
5
6
# 简单的使用
在安装完上述@babel/core
、@babel/preset-env
后,就可以进行代码 语法 转化了。和 插件模块化-改造 一样,为了加深对 Node.js 和 Babel 的理解,后续方法都采用创建 JS 文件用 Node.js 去运行的方式。创建一个 run-babel.js
文件,例如:
// run-babel.js
const fs = require("fs");
const path = require("path");
const babel = require("@babel/core");
const filename = path.resolve(__dirname, "./src/element.js");
const buildFileName = path.resolve(__dirname, "./src/element-es5.js");
const data = fs.readFileSync(filename, "utf8");
// 配置会读取根目录下的 babel.config.json 或 .babelrc.json (也有其他的扩展名文件)
// const transformData = babel.transformSync(data);
// 指定配置
const transformData = babel.transformSync(data, {
presets: ["@babel/preset-env"],
});
// 代码写入 buildFileName 文件中
fs.writeFileSync(buildFileName, transformData.code);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
你也可以将配置单独提取出一个文件,本节是采用传入配置项的方式(理由在上方说过了)。
// babel.config.json
{
"presets": [["@babel/preset-env"]]
}
2
3
4
运行 node run-babel.js
,运行成功后,生成一个 src/element-es5.js
文件。
"use strict";
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function _typeof(obj) {
return typeof obj;
};
} else {
_typeof = function _typeof(obj) {
return obj &&
typeof Symbol === "function" &&
obj.constructor === Symbol &&
obj !== Symbol.prototype
? "symbol"
: typeof obj;
};
}
return _typeof(obj);
}
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.createBackgroudImg = createBackgroudImg;
exports.createRandomTextElement = createRandomTextElement;
var util = _interopRequireWildcard(require("./util"));
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== "function") return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache = function _getRequireWildcardCache(
nodeInterop
) {
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
})(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (
obj === null ||
(_typeof(obj) !== "object" && typeof obj !== "function")
) {
return { default: obj };
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor =
Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor
? Object.getOwnPropertyDescriptor(obj, key)
: null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj["default"] = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
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);
});
};
}
// 使用 background 显示图片
function createBackgroudImg(_x) {
return _createBackgroudImg.apply(this, arguments);
} // 创建一个有随机数的节点
function _createBackgroudImg() {
_createBackgroudImg = _asyncToGenerator(
/*#__PURE__*/ regeneratorRuntime.mark(function _callee(src) {
var img, div;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
_context.next = 2;
return util.loadImg(src);
case 2:
img = _context.sent;
div = document.createElement("div");
div.style.width = "".concat(img.width, "px");
div.style.height = "".concat(img.height, "px");
div.style.background = "url(".concat(src, ")");
return _context.abrupt("return", div);
case 8:
case "end":
return _context.stop();
}
}
}, _callee);
})
);
return _createBackgroudImg.apply(this, arguments);
}
function createRandomTextElement() {
var div = document.createElement("div");
div.innerText = "random: ".concat(util.getRandomInt(1000, 9999));
return div;
}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
查看代码,可以发现已经转化为 ES5 的语法了。
注意
如果 regeneratorRuntime
为 undefined
,说明没有正确使用 Babel。请用以下方式修复(二选一):
- 引入
regenerator-runtime/runtime
(全局污染) - 使用
@babel/plugin-transform-runtime
(避免全局污染)
以上方式在后面的小节中会讲到,这里暂时跳过,本节内容只是简单的使用 Babel。
尝试:
- 尝试将
src/index.js
里的element.js
引用切换成element-es.js
。如果regeneratorRuntime
为undefined
,请查看上面的注意事项。在src/element.js
加入regenerator-runtime/runtime
,重新运行run-babel.js
。转化后的文件是否可以使用? - 尝试修改
run-babel.js
文件将src/browser.js
文件进行转化,会发现String.prototype.includes
的 ES6 API 并未转化。在后续章节 Babel - API 转化 将会解决这个问题,本节忽略。