前端利器躬行记(2)——Babel
Babel是一个JavaScript编译器,不仅能将当前运行环境不支持的JavaScript语法(例如ES6、ES7等)编译成向下兼容的可用语法(例如ES3或ES5),这其中会涉及新语法的转换和缺失特性的修补;还支持语法扩展,从而能随时随地的使用JSX、TypeScript等语法。目前最新版本是7.4,自从6.0以来,Babel被分解的更加模块化,各种转译功能都以插件的形式分离出来,可按自己的需求,灵活配置。
在7.0版本中,对Babel的包做了一次大调整,统一改成域级包,将原先以“babel-”为前缀的包迁移到@babel的命名空间,例如@babel/core、@babel/cli等。这种模块化的设计,既能区分是否是官方发布的,也能避免命名冲突。
一、@babel/core
如果要以编程的方式使用Babel,那么可以通过@babel/core实现,安装命令如下所示。
npm install --save-dev @babel/core
在引入该包后,就能在JavaScript文件中直接编译代码、文件或AST。以编译代码为例,可选择的方法有三个,如下所示。
var babel = require("@babel/core");
babel.transform(code, options, function(err, result) {
console.log(result); // => { code, map, ast }
});
babel.transformSync(code, options) // => { code, map, ast }
babel.transformAsync(code, options) // => Promise<{ code, map, ast }>
虽然transform()用于异步编译,transformSync()用于同步编译,但是它们得到的结果相同,都是一个由转换后的代码(code)、源码映射信息(map)和抽象语法树(ast)组成的对象。而transformAsync()得到的结果与前面两个方法不同,它返回一个包含code、map和ast的Promise对象。这些方法的第二个options参数,可用于传递配置信息,具体可参考官方文档。
如果要编译文件或AST,那么也有类似的三个方法可供选择。除了能编译之外,利用这个包还能把代码解析成AST(即传入一段代码,得到一个AST)以便其它插件对其进行语法分析,可使用的方法有parse()、parseSync()和parseAsync()。
二、@babel/cli
@babel/cli是一个Babel内置的命令行工具,可通过命令来编译文件,其安装如下所示。
npm install --save-dev @babel/cli
假设有一个src.js文件,其内容如下代码所示,用到了ES6中的箭头函数。
let fn = () => true;
在运行下面的命令后,可以看到输出和输入的代码相同,这是因为此时还未指定任何转译插件。
./node_modules/.bin/babel src.js
如果当前的npm版本是5.2以上,那么可将./node_modules/.bin缩短为npx,如下所示。
npx babel src.js
在babel中,有多个可用的参数,下面只列举其中的四个,其余参数的用法可参考官方文档。
npx babel src --out-dir dist
npx babel src.js --out-file dist.js
npx babel src.js --out-file dist.js --plugins=@babel/plugin-transform-classes,@babel/transform-modules-amd
npx babel src.js --out-file dist.js --presets=@babel/preset-env,@babel/flow
(1)“--out-dir”参数可编译整个src目录下的文件并输出到dist目录中。
(2)“--out-file”参数可编译src.js文件并输出到dist.js文件中。
(3)“--plugins”参数可指定编译过程中要使用的插件,多个插件用逗号隔开。
(4)“--presets”参数可指定编译过程中要使用的预设,多个预设用逗号隔开。
在package.json文件中,可以通过scripts字段声明脚本命令。如果想要简化babel命令,那么可以将它们迁移到scripts字段中,如下所示。
{
"scripts": {
"compile": "npx babel src.js --out-file dist.js"
}
}
现在要编译目录的话,只要执行下面的这条npm命令即可。
npm run compile
三、配置
在Babel中,可以将各种命令的参数集中到一个配置文件中,而可配置的文件包括babel.config.js、.babelrc和package.json。
1)babel.config.js
这是Babel 7最新引入的配置文件,存在于根目录中(即package.json文件所在的目录)。它不仅能以编程的方式声明全局生效的配置参数,还能利用overrides字段对不同的子目录进行针对性的配置,从而就能避免为相关目录创建一个.babelrc文件了。在下面的示例中,为test目录单独配置了预设(presets)。
module.exports = {
presets: [...],
overrides: [{
test: ["./test"],
presets: [...]
}]
};
当运行下面两条命令进行编译时,第一条读取的是最外层的预设,第二条读取的是overrides中的预设。
npx babel src.js
npx babel ./test/src.js
2).babelrc
babel.config.js并不是.babelrc的替代品,.babelrc文件用于局部配置(如下代码所示),可放置于所有目录中。如果当前目录没有.babelrc文件,那么就会往上查找直至找到为止。
{
"presets": [...]
}
注意,如果.babelrc的后缀是“.js”(即.babelrc.js),那么在文件中可以通过JavaScript配置参数。
3)package.json
在package.json文件中,可以声明一个babel字段,其值就是.babelrc文件中的配置参数,如下所示。
{
"babel": {
"presets": [...]
}
}
注意,不能让.babelrc和声明过babel字段的package.json处在相同的目录中。
4)配置函数或方法
前面三种都是用单独的文件来配置Babel的参数,其实还可以通过相关的函数或方法来达到相同地目的。在下面的示例中,引入gulp-babel包后就能通过得到的babel()函数来配置Babel的参数。
let babel = require("gulp-babel");
babel({
presets: [...]
});
四、插件
Babel的编译可分为三个阶段:解析、转换和生成,而插件(Plugin)在转换过程中起到了至关重要的作用。借助Babel的插件可将解析完成的AST按照特定的要求进行处理,然后再输出生成的代码。
1)插件类型
Babel中的插件分为两种类型:语法和转换。语法插件只允许Babel解析成特定类型的语法,例如JSX、Flow等。转换插件常用来编译ES6、ES7、React和Modules等,由于能自动开启相应的语法插件,因此不用显式的指定两种插件。以之前的箭头函数为例,如果要将其编译成所有浏览器都能识别的形式,那么就得安装相应的插件,如下所示。
npm install --save-dev @babel/plugin-transform-arrow-functions
在.babelrc文件中的配置如下所示。
{
"plugins": ["@babel/plugin-transform-arrow-functions"]
}
在配置文件中,不仅能指定插件的相对或绝对路径,还可省略插件名称中的“babel-plugin-”前缀。假设有个插件名叫@org/babel-plugin-name,那么它的相对路径和简写名称如下所示。
{
"plugins": [
"../@org/babel-plugin-name",
"@org/name"
]
}
2)执行顺序
插件的执行顺序会受配置时所处的位置的影响,具体规则如下所列,其中预设是指官方预先设计的一组插件集,本质上仍然是插件。
(1)插件执行在预设之前。
(2)插件会按顺序从前往后执行。
(3)预设与插件相反,从后往前执行。
以下面的配置信息为例,在插件中,先执行@babel/plugin-transform-arrow-functions,后执行@babel/plugin-transform-classes。而在预设中,先执行@babel/preset-react,后执行@babel/preset-env。
{
"plugins": ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-classes"],
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
3)插件参数
如果要配置插件的参数,那么得在插件名称后增加一个参数对象,并且写成数组的形式,如下所示。
{
"plugins": [
["@babel/plugin-transform-arrow-functions", { "spec": true }],
["@babel/plugin-transform-classes", { "loose": true }]
]
}
预设也能接收参数,其写法与之类似。
五、预设
如果在配置文件中一个一个的声明插件,那么不仅会让该文件变得巨大,而且还难免会有所遗漏。官方为了避免此类问题,引入了预设(Preset)的概念。预设类似于生活中的套餐,每个套餐会集合不同的插件,从而能够一次性安装各类插件,并且还可共享配置的参数。
1)@babel/preset-env
此预设不仅集合了最新的ES语法(即编译指定的ES标准),还能配置所要支持的平台(例如Node、Chrome等)和版本,以及按需加载插件,其安装命令如下所示。
npm install --save-dev @babel/preset-env
下面是一个预设的配置示例,支持Chrome 58和IE 11,以及超过市场份额5%的浏览器。还有许多其它可供选择的配置参数,具体可翻阅官方文档。
{
presets: [
[
"@babel/preset-env",
{
targets: {
chrome: "58",
ie: "11",
browsers: "> 5%"
}
}
]
]
}
除了@babel/preset-env之外,官方还给出了其它实用的预设,例如@babel/preset-flow、@babel/preset-react和@babel/preset-typescript等。
与插件类似,预设也能指定相对或绝对路径,只不过它省略的前缀是“babel-preset-”。假设有个预设名叫@org/babel-preset-name,那么它的相对路径和简写名称如下所示。
{
"presets": [
"../@org/babel-preset-name",
"@org/name"
]
}
2)stage-x
这是一组实验性质的预设,囊括了处于提案阶段的标准,TC39委员会将提案分为5个阶段,如表2所示。
表2 提案的五个阶段
阶段 | 描述 | 预设 |
Stage 0 | 设想(Strawman),由TC39的成员或已经注册成TC39贡献者的会员提出的想法 | @babel/preset-stage-0 |
Stage 1 | 建议(Proposal),产生一个正式的提案,确定提案负责人 | @babel/preset-stage-1 |
Stage 2 | 草案(Draft),规范的第一个版本,包含新特性的语法和语义,接下来只接受增量修改 | @babel/preset-stage-2 |
Stage 3 | 候选(Candidate),完成规范,获得用户反馈,接下来只有出现重大问题才做修改 | @babel/preset-stage-3 |
Stage 4 | 完成(Finished),将新规范纳入到标准中,准备发布 | 不存在,因为它就是已发布的标准预设,例如ES6、ES7等 |
以上4个阶段的预设存在着依赖关系,阶段靠前的依赖阶段靠后的(即数字小的包含数字大的),例如@babel/preset-stage-0依赖@babel/preset-stage-1。
由于这些提案阶段的预设不太稳定,很有可能会被TC39委员会除名或变更,并且混合使用在配置上容易出错,因此从Babel 7开始,它们都将被废弃。
六、@babel/polyfill
虽然env预设(@babel/preset-env)能统一JavaScript的新语法(即高版本编译成低版本),但是无法支持内置的新方法或新对象,例如Promise、Array.of()等。为此,Babel引入了的Polyfill技术(全部打包在@babel/polyfill中),将所缺的特性添加到全局对象中或内置对象的原型上,弥补env预设的不足,从而模拟出完整的ES6+语法和特性。
@babel/polyfill包含两个模块:regenerator-runtime和core-js,前者用于编译生成器与异步函数(async和await),后者用于处理其它兼容性问题。@babel/polyfill的安装命令如下所示,注意,使用的参数是--save而不是--save-dev,因为需要在源码之前先执行Polyfill。
npm install --save @babel/polyfill
1)useBuiltIns
在env预设中,存在一个与@babel/polyfill有紧密联系的useBuiltIns参数,它有三个关键字可供选择,分别是false、entry和usage,具体说明如下所列。
(1)false:默认值,不开启Polyfill,显式的配置如下所示。
{
presets: [
["@babel/preset-env", { useBuiltIns: false }]
]
}
(2)entry:加载运行环境(可在targets参数中声明)所需的Polyfill,下面是一个使用Promise的例子。
require("@babel/polyfill");
new Promise();
当useBuiltIns参数的值为entry时,这段代码在编译后,就会引入许多不相干的JavaScript文件,造成资源的浪费,如下所示。
require("core-js/modules/es6.promise");
require("core-js/modules/es6.array.fill");
require("core-js/modules/es6.math.trunc");
require("core-js/modules/es6.string.fixed");
......
new Promise();
(3)usage:自动加载源码所需的Polyfill,仍然以Promise为例,如下代码所示,不用再显式的引入@babel/polyfill。
new Promise();
当useBuiltIns参数的值为usage时,这段代码在编译后,就会只引入需要的JavaScript文件,如下所示。
require("core-js/modules/es6.promise");
require("core-js/modules/es6.object.to-string");
new Promise();
七、@babel/runtime
@babel/runtime与@babel/polyfill类似,也是用来增强env预设的,只是它以非侵入式的辅助函数来填补平台所没有的特性,其安装命令如下所示。
npm install --save @babel/runtime
在Babel 7中还引入了一个@babel/runtime-corejs2,它比@babel/runtime多包含一个core-js模块,即能够编译Promise、Symbol等。接下来以ES6的类为例,如下所示。
class People {
name() {}
}
在将People类编译后,得到的结果如下所示,省略了三个函数中的逻辑代码。
"use strict";
function _classCallCheck(instance, Constructor) { }
function _defineProperties(target, props) { }
function _createClass(Constructor, protoProps, staticProps) { } var People =
/*#__PURE__*/
function () {
function People() {
_classCallCheck(this, People);
}
_createClass(People, [{
key: "name",
value: function name() {}
}]);
return People;
}();
1)@babel/plugin-transform-runtime
由于编译生成的辅助函数会滞留在所使用的文件中,因此文件越多冗余的函数就越多。如果人工分离,那工作量将巨大,因此Babel提供了能自动将它们分离的@babel/plugin-transform-runtime插件,其安装命令如下所示。
npm install --save-dev @babel/plugin-transform-runtime
仍然以ES6的People类为例,先在配置文件中将其声明,如下所示。
{
plugins: ["@babel/plugin-transform-runtime"]
}
然后再将类编译,三个辅助函数就能通过引入的方式得到,如下所示。
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
八、动态编译
在Babel中有两种方式实现动态编译,分别是@babel/register和@babel/node,它们的安装命令如下所示。
npm install --save-dev @babel/register
npm install --save-dev @babel/node
虽然动态编译省去了手动操作,但是会损耗程序的速度和性能,因此不适合生产环境。
1)@babel/register
在将其安装后,就会为Node.js的require()函数增加一个钩子。每当通过require()加载后缀为.es6、.es、.jsx、.mjs或.js的文件时,就能自动对其进行Babel编译。接下来用一个例子来演示@babel/register的用法,首先创建一个src.js的文件,其代码如下所示。
module.exports = () => true;
然后创建一个default.js文件,引入@babel/register和之前的src.js,如下所示。
require("@babel/register");
var code = require("./src.js");
console.log(code.toString());
最后在命令行工具中输入“node default.js”,打印出编译后的匿名函数,如下所示。
function () {
return true;
}
在@babel/register中,如果需要使用Polyfill,那么得逐个引入。还要注意,它默认不会编译node_modules目录中的模块,但是可以通过ignore参数修改忽略规则来覆盖其行为。
ignore参数是一个由正则表达式和函数组成的数组(如下代码所示),如果要让文件不被编译,那么得将其路径与正则表达式匹配或函数返回true,其中函数的参数就是文件路径。
require("babel-register")({
ignore: [
/regex/,
function(filepath) {
return true;
}
]
});
另外还有一个only参数,其值也是一个数组,同样也用来设置文件不被编译的条件。只是其规则与ignore参数正好相反,即路径与正则表达式不匹配或函数返回false时,才不编译。
@babel/register还可以接收其它参数,例如extensions、cache等,并且会合并配置文件中的信息,作为其参数传递进来。
2)@babel/node
在Babel 7之前,@babel/cli会自带babel-node命令,但之后,官方将其拆成一个单独的包:@babel/node。与@babel/register不同,@babel/node不需要调整源码,直接一个命令就能实现动态编译,如下代码所示,其中src就是之前的src.js文件。
npx babel-node src
前端利器躬行记(2)——Babel的更多相关文章
- 前端利器躬行记(3)——webpack基础
webpack是一个静态模块打包器,此处的模块可以是任意文件,包括Sass.TypeScript.模板和图像等.webpack可根据输入文件的依赖关系,打包输出浏览器可识别的JavaScript.CS ...
- 前端利器躬行记(1)——npm
npm(Node Package Manager)是Node.js的包管理工具,相当于一个在线仓库.它提供了一个公共的平台,将分散在世界各地的包集中起来,能轻松的安装.分享和管理相关的包,不用再为搜索 ...
- 前端利器躬行记(4)——webpack进阶
webpack是一个非常强大的工具,除了前文所介绍的基础概念之外,还有各种进阶应用,例如Source Map.模块热替换.集成等,本文会对这些内容做依次讲解. 一. runtime和manifest ...
- 前端利器躬行记(5)——Git
Git是一款开源的分布式版本控制系统,它的出现和Linux紧密相关.Linux内核项目组为了能更好地管理和维护Linux内核开发,于2002年开始启用商业的分布式版本控制系统BitKeeper.虽然软 ...
- 前端利器躬行记(6)——Fiddler
Fiddler是一款免费的.基于Windows系统的代理服务器软件(即Web调试抓包工具),由Eric Lawrence用C#语言在2003年10月发布了第一个版本.注意,由于Fiddler依赖Mic ...
- 前端利器躬行记(8)——VSCode插件研发
VSCode提供了丰富的 API,可以借助编辑器扩展许多定制功能. 本次研发了一款名为 Search Method 的插件,在此记录整个研发过程. 一.准备工作 1)安装环境 首先是全局安装 yo 和 ...
- ES6躬行记(1)——let和const
古语云:“纸上得来终觉浅,绝知此事要躬行”.的确,不管看了多少本书,如果自己不实践,那么就很难领会其中的精髓.自己研读过许多ES6相关的书籍和资料,平时工作中也会用到,但在用到时经常需要上搜索引擎中查 ...
- ES6躬行记 笔记
ES6躬行记(18)--迭代器 要实现以下接口## next() ,return,throw 可以用for-of保证迭代对象的正确性 例如 var str = "向
- Node.js躬行记(4)——自建前端监控系统
这套前端监控系统用到的技术栈是:React+MongoDB+Node.js+Koa2.将性能和错误量化.因为自己平时喜欢吃菠萝,所以就取名叫菠萝系统.其实在很早以前就有这个想法,当时已经实现了前端的参 ...
随机推荐
- Pascal到c++,求大佬翻译!
请大佬翻译完毕后私信我谢谢! var a,b,c,d,f:array[1..1000]of longint; i,j,k,n,m,o:longint; procedure zx(p,q:longint ...
- [HNOI2015]亚瑟王[期望DP]
也许更好的阅读体验 \(\mathcal{Description}\) 给出\(n\)个技能,每个技能按输入顺序有\(p[i]\)的概率释放并造成\(d[i]\)的伤害.每轮游戏从前往后顺序查看每个技 ...
- RestClient测试
1,对象里面包含集合及字符串属性 {"roloeList":[{ "id":10001,"areaid":1,"name" ...
- jQuery写toTop(回到顶部)效果
在通常的网站开发中,页面有时候会很长,尤其是一些电商网站,为了提高用户的体验效果,我们通常会增加一个回到顶部的按钮,这个按钮我们同城会使用fixed定位,将其定位在当前可视区域某一固定位置.这个效果用 ...
- 物联网网关MQTT应用与配置测试介绍
1.MQTT介绍: MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),作为除Modbus外最常用的协议之一,因其基于发布/订阅的模式,具有资源消 ...
- css3系列之transform详解translate
translate translate这个参数的,是transform 身上的,那么它有什么用呢? 其实他的作用很简单,就是平移,参考自己的位置来平移 translate() translateX() ...
- http://regex.alf.nu/ 非标准答案
Plain strings (207) foo Anchors (206) ...
- 【转载】【VSCode】Windows下VSCode编译调试c/c++
转载自:http://blog.csdn.net/c_duoduo/article/details/51615381 懒得自己配置或自己配置出现不明问题的朋友可以点这里: [VSCode]Window ...
- c#异常后重试操作
private void TryConnect(System.Action action) { int retries = 3; whi ...
- 灰度级分层(一些基本的灰度变换函数)基本原理及Python实现
1. 基本原理 灰度级分层通常用于突出感兴趣的特定灰度范围内的亮度.灰度级分层有两大基本方法. 将感兴趣的灰度范围内的值显示为一个值(比如0),而其他范围的值为另外一个值(255). 将感兴趣的灰度范 ...