babel脚本实现原理

为什么要用babel转换代码

我们之前做一些兼容都会都会接触一些 Polyfill 的概念,比如如果某个版本的浏览器不支持 Array.prototype.find 方法,但是我们的代码中有用到 Array 的 find 函数,为了支持这些代码,我们会人为的加一些兼容代码。

if (!Array.prototype.find) {
    Object.defineProperty(Array.prototype, 'find', {
        // todo someting
    })
}

对于这种情况做兼容也很好实现,引入一个 Polyfill 文件就可以了,但是有一些情况我们使用到了一些新语法,或者一些其他写法

// 箭头函数
var a = () => {}
// jsx
var component = () => <div />

这种情况靠 Polyfill,因为一些浏览器根本就不识别这些代码,这时候就需要把这些代码转换成浏览器识别的代码。 babel就是做这个事情的。

babel做了哪些事情babel做了哪些事情

image

为了转换我们的代码, babel做了三件事:

  • Parser 解析我们的代码转换为 AST。

  • Transformer 利用我们配置好的 plugins/presets把 Parser生成的 AST转变为新的 AST。

  • Generator 把转换后的 AST生成新的代码

从图上看 Transformer 占了很大一块比重,这个转换过程就是 babel中最复杂的部分,我们平时配置的 plugins/presets就是在这个模块起作用。

从简单的说起

可以看到要想搞懂 babel, 就是去了解上面三个步骤都是在干什么,我们先把比较容易看懂的地方开始了解一下。

Parser 解析

解析步骤接收代码并输出 AST,这其中又包含两个阶段词法分析和语法分析。词法分析阶段把字符串形式的代码转换为 令牌(tokens) 流。语法分析阶段会把一个令牌流转换成 AST 的形式,方便后续操作。

Generator 生成

代码生成步骤把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,同时还会创建源码映射(source maps)。代码生成其实很简单:深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。

babel的核心内容

看起来 babel的主要工作都集中在把解析生成的 AST经过 plugins/presets然后去生成 新的AST这上面了。

AST抽象语法树(AbstractSyntaxTree)

我们想象一下要表示上述代码应该是什么样子,首先必须有东西可以表示这些具体的 声明, 变量, 常量的具体信息,比如

var a = 1 + 2

一个声明语句,声明类型是var,左侧是变量,右侧是表达式。有了这些信息我们就可以还原这个程序,这也是把代码解析成 AST时候所做的事情,对应上面我们说的 词法分析语法分析

AST 在线解析

AST Node节点说明

节点

看这个文档时候我们可以看到说明大多是类似这种

interface Node {
  type: string;
  loc: SourceLocation | null;
}

这里提到 interface这个我们在其他语言中是比较常见的,比如 Node规定了 type和 loc属性,如果其他节点继承自 Node,那么它也会实现 type和 loc属性就是说继承自 Node的节点也会有这些属性,基本所有节点都继承自 Node,所以我们基本可以看到 loc这个属性 loc表示个一些位置信息

节点遍历

babel拿到抽象语法树后会使用 babel-traverse进行递归的树状遍历,对于每一个节点都会向下遍历到尽头,然后向上遍历退出分支去寻找下一个分支。这样确保我们能找到任何一个节点,也就是能访问到我们代码的任何一个部分。可是我们要怎么去完成修改操作呢, babel给我们提供了下面这两个概念。

如何写一个plugin

visitor

我们已经知道 babel会遍历节点组成的抽象语法树,每一个节点都会有自己对应的 type,比如变量节点 Identifier等。我们需要给 babel提供一个 visitor对象,在这个对象上面我们以这些节点的 type做为 key,已一个函数作为值,类似如下:

const visitor = {
    Identifier: {
        enter() {
            console.log(
                'traverse enter a Identifier node!'
            )
        },

        exit() {
            console.log(
                'traverse exit a Identifier node!'
            )
        }
    }
}

这样在遍历进入到对应到节点时候, babel就会去执行对应的 enter函数,向上遍历退出对应节点时候, babel就会去执行对应的 exit函数,接着上面的代码我们可以做一个测试

const MyVisitor = {
    visitor
}

const result = babel.transform(code, {
    plugins: [
        MyVisitor
    ]
})

console.log(result.code)

Paths

visitor在遍历到对应节点执行对应函数时候会给我们传入 path参数。它传入的 path参数看起来是这样的:

{
    "parent": {
        "type": "VariableDeclarator",
        "id": {...
        },
    
        ....
    },
    "node": {
        "type": "Identifier",
        "name": "..."
    }
}

从上面我们可以看到 path 表示两个节点之间的连接,通过这个对象我们可以访问到节点、父节点以及进行一系列跟节点操作相关的方法。

babel为了方便我们开发,在每一个环节都有很多人性化的定义也提供了很多实用性的工具,比如之前我们在定义 visitor时候分别定义了 enter, exit函数,可很多时候我们其实只用到了一次在 enter的时候做一些处理就行了。所以我们如果我们直接定义节点的 key为函数,就相当于定义了 enter函数。

const visitor = {
    Identifier: {
        enter(path) {
            // todo
        }
    }
}

// 等同于
const visitor = {
    Identifier() {
        // todo
    }
}

bable-core API参见

bable-types API参见

bable相关核心库与组件

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

推荐阅读更多精彩内容