重新说一说 babel
babel 单词解释
英语单词 Babel 指的是一个城市(古巴比伦)。
据《圣经·创世纪》记载,大洪水后,诺亚的子孙越来越多,遍布地面。他们向东迁移,在古巴比伦附近定居下来。他们彼此商量说,要烧砖为石,拿石漆为灰泥,建造一座城市和一座通天塔,以传扬其名。由于他们语言相通,齐心协力,很快就建成了一座巨大的城市,高塔也直插云霄,快到天上了。
没想到此事惊动了上帝。上帝担心人类办成此事后将狂妄自大,为所欲为,便变乱众人的语言,使大家无法交流,通天塔也因此半途而废。这座城市和塔便被称为 Babel,在英语中比喻“嘈杂声”。
babel 简介
官网上介绍到,Babel 是一个 JavaScript 编译器。
它是一个工具链,主要用来转换 ECMAScript 2015+ 代码,将其变成兼容当前版本和老版本浏览器的 JavaScript 代码。
Babel 能做不止如下牛逼的事情:
- 转换语法
- 在你的目标环境中添加没被实现的 API。
- 源代码转换
babel 概括理解
babel 意味 babel 塔,将混乱的东西变得整洁清晰。它能将 es6+ 代码转为 es5 代码,能识别 ts 代码。
babel 是一系列插件构成的。每一个插件都有自己的职责。比如箭头函数插件,可以将箭头函数转为普通函数。class 插件,可以将 class 类转为由 Function 模拟的类。ts 插件,可以将 ts 的文件及语法转为 js 的文件及语法。
但在实际项目中,一个个的配置多麻烦呀。于是出现了 preset-env 预设。它是一系列插件的集合。
体验 babel
1 在一个空目录下执行 npm init -y 命令,然后下载三个包。
npm init -y
npm install --save-dev @babel/core @babel/cli @babel/preset-env2 在根目录下创建 babel 配置文件 babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 0.25%, not dead",
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
]
}3 创建 src/index.js 文件
console.log([1, 2, 3].map(n => n + 1))4 执行命令 npx babel src --out-dir lib
改变 targets 选项,查看转换后的 index.js。
学习使用预设 preset-env
在上面体验 babel 中,我们使用到了预设 preset-env,并且对其选项进行了配置。
babel 的插件有很多,而 preset-set 是一些插件的集合。
现在我们来学习一下如何使用 preset-env。
在上面体验 babel 的时候,我们已经下载了 @babel/preset-env 包,所以不需要再下载。
preset-env 的默认配置
preset-env 全部默认配置如下。但是最重要的配置就三个,我们主要了解这三个就可以了。
{
"presets": [
[
"@babel/preset-env",
{
"targets": {},
// "bugfixes": false,
// "spec": false,
// "loose": false,
// "modules": "auto",
// "debug": "auto",
// "include": [],
// "exclude": [],
"useBuiltIns": false,
"corejs": "2.0",
// "forceAllTransforms": false,
// "configPath": process.cwd(),
// "ignoreBrowserslistConfig": false,
// "browserslistEnv": undefined,
// "shippedProposals": false
}
]
]
}targets
targets 为默认值空对象 {} 时,将会把所有 ES6 语法转换为 ES5 语法。
怎么判断哪些是 ES5 语法,哪些是 ES6 语法呢?
ES6 比 ES5 增加了很多特殊的方法,如果你遇到了这些特殊的方法,你就可以确定它是 ES6。
如果你的代码中没有引用这些特殊的方法,那我们就可以认为他是 ES5 的。
所以前提你需要了解 ES6 的语法才能做判断,高频使用的特性有箭头函数、解构赋值、let、const 等等。
这里推荐两本书,更好的区分 ES5 代码和 ES6 代码。一本是阮一峰的 ES6 入门教程,一本是 JavaScript 教程 https://wangdoc.com/javascript/ 。
下面这行代码
console.log([1, 2, 3].map(n => n + 1))将会被转为如下代码。因为箭头函数是 ES6 新增的新特性,而其它都是 ES5 就有的东西。
"use strict";
console.log([1, 2, 3].map(function (n) {
return n + 1;
}));useBuiltIns
useBuiltIns 选项告诉 @babel/preset-env 怎么处理垫片 polyfill。注意它是不会影响 preset-env 怎么处理 ES6 的基本语法的。
当设置为 false 时,不会为每个文件自动添加 polyfill。也就是说,只会转换一些基本的语法,比如 let,const,箭头函数等。而如果你使用了一些需要 polyfill 的 特性,比如 Promise,则不会进行处理。
比如下面这行代码
console.log([1, 2, 3].map(n => n + 1))
console.log(new Promise())转换后并没有对 Promise 进行垫片处理
"use strict";
console.log([1, 2, 3].map(function (n) {
return n + 1;
}));
console.log(new Promise());corejs
默认为 2.0。支持所有 core-js 版本。比如 "3.8" 或者 "2.0"。
这个选项仅仅会在 useBuiltIns 选项设置为 usage 或 entry 时生效。它的作用是控制 @babel/preset-env 注入 polyfill 时所引入的 core-js 版本。
取默认值或 2 的时候,Babel 转码的时候使用的是 core-js@2 版本(即 core-js 2.x.x)。因为某些新 API 只有 core-js@3 里才有,例如数组的 flat 方法,我们需要使用 core-js@3 的 API 模块进行补齐,这个时候我们就把该项设置为 3。
为进行演示,把配置文件改一下:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {},
+ "useBuiltIns": "usage",
"corejs": "2.0"
}
]
]
}再对一下代码进行转换
console.log([1, 2, 3].map(n => n + 1))
console.log(new Promise())转换结果如下。可以看到 polyfill 是从 core-js 包引入的。
"use strict";
require("core-js/modules/es6.array.map.js");
require("core-js/modules/es6.object.to-string.js");
require("core-js/modules/es6.promise.js");
console.log([1, 2, 3].map(function (n) {
return n + 1;
}));
console.log(new Promise());除此之外,如果将配置文件中的 corejs 选项去掉,控制台还会警告:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "chrome 44",
"useBuiltIns": "usage",
- "corejs": "3.8"
}
]
]
}大意就是叫我们,当 useBuiltIns 设置为 usage 或则 entry 时最好指定 corejs 选项版本。(在 package.json 里 core-js 的版本。)否则 corejs 默认指定 2.x 版本。
学习使用插件 plugin-transform-runtime
默认配置
使用 plugin-transform-runtime 插件默认开启什么功能?
1 将每个文件里生成定义的辅助函数转换为从包 @babel/runtime 里引入的辅助函数
2 转换 async/await 函数,并且不污染全局作用域
以下的配置是默认的。可以看到参数 helpers、regenerator 配置是默认开启的,为 true。
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false,
"helpers": true,
"regenerator": true
}
]
]
}辅助函数是什么?
babel 在转换某些语法的时候,会在转换后的代码里注入一些函数。比如转换 ES6 的 class 语法,会顺带生成一些内联辅助函数,如下:
转换前
class a {
}转换后
"use strict";
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, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var a = /*#__PURE__*/_createClass(function a() {
_classCallCheck(this, a);
});而如果应用了 plugin-transform-runtime 插件并使用其默认配置,转换结果如下
"use strict";
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var a = /*#__PURE__*/(0, _createClass2.default)(function a() {
(0, _classCallCheck2.default)(this, a);
});corejs 配置选项
corejs 配置能干什么?有哪些注意点?
corejs 有三个选项。false、2、3。默认值为 false。
为 false 时,辅助函数会从 包 @babel/runtime 里引入。
为 2 时,辅助函数会从包 @babel/runtime-corejs2 里引入,并且也会从这个包里引入 polyfill 实现垫片功能。
为 3 时,辅助函数会从包 @babel/runtime-corejs3 里引入,并且也会从这个包里引入 polyfill 实现垫片功能。
runtime-corejs2 和 runtime-corejs3 有什么区别?
runtime-corejs2 只会垫片全局变量(比如 Promise)和静态属性(比如 Array.from)
runtime-corejs3 支持 2 的所有垫片功能,还支持实例属性(比如 [].includes 方法)
plugin-transform-runtime 和 preset-env 区别
plugin-transform-runtime 和 preset-env 有何区别?应用场景是什么?
preset-env 和 plugin-transform-runtime 都能实现 polyfill 功能。但 preset-env 会转换 ES6 语法。
preset-env 引入的 polyfill 会污染全局空间,会根据目标环境选择性的将 ES6 特性转为 ES5 的。
plugin-transform-runtime 引入的 polyfill 不会污染全局空间,会将代码中用到的全部 ES6 特性转为 ES5 的。
建议
如果你写的代码会作为库给其他人使用,或者你不知道你的代码将会在哪个目标环境中运行,推荐使用 plugin-transform-runtime。因为它引入的 polyfill 不会污染全局作用域。
如果你是在写业务代码,可以直接使用 preset-env 提供的 polyfill。虽然它污染了全局作用域,但其实影响不大。
至于 preset-env 的 useBuiltIns 该选择 usage 还是 entry,可以参考这篇文章:
「前端基建」探索不同项目场景下 Babel 最佳实践方案 https://juejin.cn/post/7051355444341637128
babel 转换 ts 文件为 js 文件
需要使用预设 preset-typescript。
下载
npm install --save-dev @babel/preset-typescript新增配置。对于多个 preset,babel 会按照其声明次序逆序执行。
{
"presets": [
[
"@babel/preset-env",
{
"targets": "chrome 44",
"useBuiltIns": "usage",
"corejs": "3.8"
}
],
+ ["@babel/preset-typescript"]
]
}新增 src/main.ts 文件
const a: number = 123
console.log(a);目录截图
如果要养 babel 处理 .ts 文件,cli 必须加上参数 --extensions ".ts"
所以我们执行脚本命令 npx babel src --out-dir lib --extensions ".ts"