一篇文章读懂babel

本文将从下面几个问题来回答对babel对理解:

1. 什么是bable;
2. bable的工作原理是什么;
3. babel应该怎么使用、任何配置;

一、什么是babel:

Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.

我们已经能够熟练地使用 es2015+ 的语法。但是对于浏览器来说,可能和它们还不够熟悉,我们得让浏览器理解它们,这就需要 Babel.

Babel 是一个通用的多功能 JavaScript 编译器,但与一般编译器不同的是它只是把同种语言的高版本规则转换为低版本规则,而不是输出另一种低级机器可识别的代码,并且在依赖不同的拓展插件下可用于不同形式的静态分析。(静态分析:指在不需要执行代码的前提下对代码进行分析以及相应处理的一个过程,主要应用于语法检查、编译、代码高亮、代码转换、优化、压缩等等)

二、bable的工作原理是什么:

可以理解babel本质也是一个编译器,只是这个编译器输出对不是二进制机器码而是在浏览器能运行对js语法. 所以更准确点说他是一个转译器,它工作分为3个步骤:

1. 解析 Parse
2. 转换 Transform
3. Generate
babel.jpg
  • 解析 Parse

将代码解析生成抽象语法树( 即AST ),也就是计算机理解我们代码的方式(扩展:一般来说每个 js 引擎都有自己的 AST,比如熟知的 v8,chrome 浏览器会把 js 源码转换为抽象语法树,再进一步转换为字节码或机器代码),而 babel 则是通过 babylon 实现的 。简单来说就是一个对于 JS 代码的一个编译过程,进行了词法分析与语法分析的过程。

谈到AST就必须理解js引擎是如何理解我们代码的,简答来说是两个步骤:1、分词; 2、语意分析。

1、分词:

将整个代码字符串分割成语法单元数组(token)

JS 代码中的语法单元主要指如标识符(if/else、return、function)、运算符、括号、数字、字符串、空格等等能被解析的最小单元。比如下面的代码生成的语法单元数组如下:

分词小工具

var answer = 6 * 7;
=> 
//分词token
[
    {
        "type": "Keyword",
        "value": "var"
    },
    {
        "type": "Identifier",
        "value": "answer"
    },
    {
        "type": "Punctuator",
        "value": "="
    },
    {
        "type": "Numeric",
        "value": "6"
    },
    {
        "type": "Punctuator",
        "value": "*"
    },
    {
        "type": "Numeric",
        "value": "7"
    },
    {
        "type": "Punctuator",
        "value": ";"
    }
]

2、语意分析:

语义分析则是将得到的词汇进行一个立体的组合,确定词语之间的关系。考虑到编程语言的各种从属关系的复杂性,语义分析的过程又是在遍历得到的语法单元组,相对而言就会变得更复杂。

简单来说语义分析既是对语句和表达式识别,这是个递归过程,在解析中,babel 会在解析每个语句和表达式的过程中设置一个暂存器,用来暂存当前读取到的语法单元,如果解析失败,就会返回之前的暂存点,再按照另一种方式进行解析,如果解析成功,则将暂存点销毁,不断重复以上操作,直到最后生成对应的语法树。

{
    "type": "Program",
    "body": [
        {
            "type": "VariableDeclaration",
            "declarations": [
                {
                    "type": "VariableDeclarator",
                    "id": {
                        "type": "Identifier",
                        "name": "answer"
                    },
                    "init": {
                        "type": "BinaryExpression",
                        "operator": "*",
                        "left": {
                            "type": "Literal",
                            "value": 6,
                            "raw": "6"
                        },
                        "right": {
                            "type": "Literal",
                            "value": 7,
                            "raw": "7"
                        }
                    }
                }
            ],
            "kind": "var"
        }
    ],
    "sourceType": "script"
}
  • 转换 Transform

对于 AST 进行变换一系列的操作,babel 接受得到 AST 并通过 babel-traverse 对其进行遍历,在此过程中进行添加、更新及移除等操作

  • 生成 Generate

将变换后的 AST 再转换为JS代码, 使用到的模块是 babel-generator。

而 babel-core 模块则是将三者结合使得对外提供的API做了一个简化。

此外需要注意的是,babel 只是转译新标准引入的语法,比如ES6箭头函数:而新标准引入的新的原生对象,部分原生对象新增的原型方法,新增的 API 等(Proxy、Set 等), 这些事不会转译的,需要引入对应的 polyfill 来解决。

而我们编写的 babel 插件则主要专注于第二步转换过程的工作,专注于对于代码的转化规则的拓展,解析与生成的偏底层相关操作则有对应的模块支持,在此我们理解它主要做了什么即可。

经过babel转换后高版本js语法就变成了ECMAScript 2015+ 的代码,支持在旧的浏览器上运行,如:

// es2015 的 const 和 arrow function
const add = (a, b) => a + b;
let a =1;
let b ={a};

// Babel 转译后
var add = function add(a, b) {
  return a + b;
};
var a = 1;
var b = { a: a };

三、babel该如何使用及配置:

目前JS没有统一的构建构建、平台,所以babel支持对各种工具对基gulp、webpack、node

开始使用babel之前我们首先理解几个babel文件:

babel-cli 是一种在命令行下使用Babel编译工具
babel-register 在node下运行babel的小工具
babel-polyfill Babel一起使用的polyfill是babel-polyfill
babele-runtime 转换api工具
babel-core Babel 的核心代码
babel-loader 解析 js 的 loader
babel-preset-react 用于解析 jsx 语法
babel-preset-env 取代之前的 babel-preset-es2015 babel-preset-es2016 等包,解析 ES6 等语法
babel-preset-stage-0 同类的还有 stage-1、stage-2、stage-3,stage-0 包含es7的解析内容,且同时包含 stage-1/2/3 的所有功能

* 以上是babel6,如果需要使用babel7的话需要增加@前缀,@babel-core
  • babel-cli

从名字就可以理解,这个是一个命令行工具,运行在node环境,能将es6或者es7语法的js文件转换为es5文件。

$ npm install --global babel-cli
$ babel example.js --out-file compiled.js
# 或
$ babel example.js -o compiled.js

  • babel-register

babel-cli会把一个文件转换成一个es5文件执行,但是如果你想直接执行这个文件:

$ node index.js

直接来运行它是不会使用 Babel 来编译的,这个时候就需要使用babel-register,首先在项目中创建 register.js 文件并添加如下代码:

require("babel-register");
require("./index.js");

这样做可以把 Babel 注册到 Node 的模块系统中并开始编译其中 require 的所有文件。

现在我们可以使用 register.js 来代替 node index.js 来运行了。

$ node register.js
  • babel-polyfill与babele-runtime

Babel默认只转换新的JavaScript语法,而不转换新的API。 例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。 如果想使用这些新的对象和方法,则需要为当前环境提供一个polyfill

目前最常用的配合Babel一起使用的polyfill是babel-polyfill,它会”加载整个polyfill库”,针对编译的代码中新的API进行处理,并且在代码中插入一些帮助函数。

babel-polyfill解决了Babel不转换新API的问题,但是直接在代码中插入帮助函数,会导致污染了全局环境,并且不同的代码文件中包含重复的代码,导致编译后的代码体积变大。 (比如:上述的帮助函数_defineProperty有可能在很多的代码模块文件中都会被插入)

Babel为了解决这个问题,提供了单独的包babel-runtime用以提供编译模块的工具函数, 启用插件babel-plugin-transform-runtime后,Babel就会使用babel-runtime下的工具函数,上述的代码就会变成这样

  • babel-core

以上都是基于cli或者node环境时使用babel,如果你想通过代码的方式实现babel,这个时候就需要使用babel-core

$ npm install babel-core
var babel = require("babel-core");

字符串形式的 JavaScript 代码可以直接使用 babel.transform 来编译。

babel.transform("code();", options);
// => { code, map, ast }

配置 Babel

目前为止通过运行 Babel 自己我们并没能“翻译”代码,而仅仅是把代码从一处拷贝到了另一处。

这是因为我们还没告诉 Babel 要做什么。

你可以通过安装插件(plugins)或预设(presets,也就是一组插件来指示 Babel 去做什么事情。

  • .babelrc

在我们告诉 Babel 该做什么之前,我们需要创建一个配置文件。你需要做的就是在项目的根路径下创建 .babelrc 文件。然后输入以下内容作为开始:

{
  "presets": [],
  "plugins": []
}

这个文件就是用来让 Babel 做你要它做的事情的配置文件。

  • plugins

上面说过babel本身是不会去翻译代码,需要插件告诉babel需要做什么,比如你需要翻译箭头函数就需要transform-es2015-arrow-functions插件。

  • presets(预设)

不想自己动手组合插件?没问题!preset 可以作为 Babel 插件的组合,甚至可以作为可以共享的 options 配置。

官方针对常用环境编写了一些 preset:

babel/preset-env
babel/preset-flow
babel/preset-react 翻译jsx文件
babel/preset-typescript
babel-preset-react-app creat-react-app
babel-preset-es2015 es6=>es5
babel-preset-stage-x 提案阶段语法
  • presets与plugins差别

举个例子, babel-preset-es2015 包含了 transform-es2015-arrow-functions(编译箭头函数)、transform-es2015-block-scoping(编译 const、let)、transform-es2015-classes(编译class)等几十个插件

如果不使用 preset,如果想要用完整的 es6 语法,使用者可能需要自己配置几十个 plugins。

  • babel-preset-env

单独讲一下babel-preset-env 这个预设插件,
为了让开发者能够尽早用上新的JS特性,babel团队开发了babel-preset-latest。这个preset比较特殊,它是多个preset的集合(es2015+),并且随着ECMA规范的更新更增加它的内容。

随着时间的推移,babel-preset-latest 包含的插件越来越多,这带来了如下问题:

1、加载的插件越来越多,编译速度会越来越慢;

2、随着用户浏览器的升级,ECMA规范的支持逐步完善,编译至低版本规范的必要性在减少(比如ES6 -> ES5),多余的转换不单降低执行效率,还浪费带宽。

因为上述问题的存在,babel官方推出了babel-preset-env插件。它可以根据开发者的配置,按需加载插件。

需要支持的平台:比如node、浏览器等。
需要支持的平台的版本:比如支持node@6.1等。

参考文献

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348