浅谈babel原理

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 很早之前就听同事分享了babel原理,其核心就是 AST(Abstract Syntax Tree),今天将自己所了解的知识点简单整理记录一下。

很早之前就听同事分享了babel原理,其核心就是 AST(Abstract Syntax Tree),今天将自己所了解的知识点简单整理记录一下。


Babel处理流程


有简单了解过 loader 实现的朋友知道,对于 loader 来说,源代码将作为字符串参数传入。


举个例子,有以下代码

const name = 'Job';
复制代码


对于 loader 来说,其实就是运行

babelLoader(`const name = 'Job'`)
复制代码


babelLoader 函数是如何处理呢?以前我以为是通过正则处理的,但其实不是,真实处理的流程是以 AST 为桥梁实现的


parse => transform => generate解析 => 转化 => 生成


解析


解析就是将代码字符串解析为 ast,我们将其分为两个阶段,词法分析语法分析

这边推荐个查看解析结果的网站AST查看


词法分析


词法分析就是将代码(字符串序列)拆分为词法单元,我们将词法单元称为token,所以我们也将词法分析叫做tokenization


还是以上面的例子继续

const name = 'Job'; 
复制代码

经过词法分析得到词法单元数组


[
  {
    "type": "Keyword",
    "value": "const"
  },
  {
    "type": "Identifier",
    "value": "name"
  },
  {
    "type": "Punctuator",
    "value": "="
  },
  {
    "type": "String",
    "value": "'Job'"
  },
  {
    "type": "Punctuator",
    "value": ";"
  }
]
复制代码

可以看出词法分析const name = 'Job';拆分为constname='Job';,而且分别有不同的类型描述 KeywordIdentifier


语法分析


词法分析得到的词法单元将作为参数供语法分析进一步处理,经过语法分析将得到解析的最终产物 ast,我们直接来看看生成的 ast

{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "name"
          },
          "init": {
            "type": "Literal",
            "value": "Job",
            "raw": "'Job'"
          }
        }
      ],
      "kind": "const"
    }
  ],
  "sourceType": "script"
}
复制代码


如此,我们就得到一个可以描述我们代码的 json 数据,实际真实的 ast 会比这样要复杂一些,例如还包括了词法单元所在的行列数据。


转化

transform就是处理我们从代码中得到的ast,对其进行修改,例如我们将const修改为var

{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "name"
          },
          "init": {
            "type": "Literal",
            "value": "Job",
            "raw": "'Job'"
          }
        }
      ],
      "kind": "var"
    }
  ],
  "sourceType": "script"
}
复制代码

生成

generate就是将我们在前面经过处理后的ast处理生成code的过程。


示例


我们用babel提供的工具包来演示一遍刚刚的分析过程


  1. 使用@babel/parser将代码解析为ast
const ast = require("@babel/parser").parse("const name = 'Job';");
复制代码
{
    "type":"File",
    "start":0,
    "end":19,
    "loc":{
        "start":{
            "line":1,
            "column":0
        },
        "end":{
            "line":1,
            "column":19
        }
    },
    "errors":[
    ],
    "program":{
        "type":"Program",
        "start":0,
        "end":19,
        "loc":{
            "start":{
                "line":1,
                "column":0
            },
            "end":{
                "line":1,
                "column":19
            }
        },
        "sourceType":"script",
        "interpreter":null,
        "body":[
            {
                "type":"VariableDeclaration",
                "start":0,
                "end":19,
                "loc":{
                    "start":{
                        "line":1,
                        "column":0
                    },
                    "end":{
                        "line":1,
                        "column":19
                    }
                },
                "declarations":[
                    {
                        "type":"VariableDeclarator",
                        "start":6,
                        "end":18,
                        "loc":{
                            "start":{
                                "line":1,
                                "column":6
                            },
                            "end":{
                                "line":1,
                                "column":18
                            }
                        },
                        "id":{
                            "type":"Identifier",
                            "start":6,
                            "end":10,
                            "loc":{
                                "start":{
                                    "line":1,
                                    "column":6
                                },
                                "end":{
                                    "line":1,
                                    "column":10
                                },
                                "identifierName":"name"
                            },
                            "name":"name"
                        },
                        "init":{
                            "type":"StringLiteral",
                            "start":13,
                            "end":18,
                            "loc":{
                                "start":{
                                    "line":1,
                                    "column":13
                                },
                                "end":{
                                    "line":1,
                                    "column":18
                                }
                            },
                            "extra":{
                                "rawValue":"Job",
                                "raw":"'Job'"
                            },
                            "value":"Job"
                        }
                    }
                ],
                "kind":"const"
            }
        ],
        "directives":[
        ]
    },
    "comments":[
    ]
}
复制代码


可以发现 program 和我们之前分析的 ast 差不多,但是会复杂一些,主要是加上了很多记录位置的字段。


  1. 使用@babel/traverse转化ast
const traverse = require("@babel/traverse").default
traverse(ast, {
  enter(path) {
    // 用var声明替换const
    if (path.node.type === 'VariableDeclaration' && path.node.kind === 'const') {
      path.node.kind = 'var'
    };
  }
})
复制代码
{
    "type":"File",
    "start":0,
    "end":19,
    "loc":{
        "start":{
            "line":1,
            "column":0
        },
        "end":{
            "line":1,
            "column":19
        }
    },
    "errors":[
    ],
    "program":{
        "type":"Program",
        "start":0,
        "end":19,
        "loc":{
            "start":{
                "line":1,
                "column":0
            },
            "end":{
                "line":1,
                "column":19
            }
        },
        "sourceType":"script",
        "interpreter":null,
        "body":[
            {
                "type":"VariableDeclaration",
                "start":0,
                "end":19,
                "loc":{
                    "start":{
                        "line":1,
                        "column":0
                    },
                    "end":{
                        "line":1,
                        "column":19
                    }
                },
                "declarations":[
                    {
                        "type":"VariableDeclarator",
                        "start":6,
                        "end":18,
                        "loc":{
                            "start":{
                                "line":1,
                                "column":6
                            },
                            "end":{
                                "line":1,
                                "column":18
                            }
                        },
                        "id":{
                            "type":"Identifier",
                            "start":6,
                            "end":10,
                            "loc":{
                                "start":{
                                    "line":1,
                                    "column":6
                                },
                                "end":{
                                    "line":1,
                                    "column":10
                                },
                                "identifierName":"name"
                            },
                            "name":"name"
                        },
                        "init":{
                            "type":"StringLiteral",
                            "start":13,
                            "end":18,
                            "loc":{
                                "start":{
                                    "line":1,
                                    "column":13
                                },
                                "end":{
                                    "line":1,
                                    "column":18
                                }
                            },
                            "extra":{
                                "rawValue":"Job",
                                "raw":"'Job'"
                            },
                            "value":"Job"
                        }
                    }
                ],
                "kind":"var"
            }
        ],
        "directives":[
        ]
    },
    "comments":[
    ]
}
复制代码


不出所料,其中的kind: const转化为kind: const了,其它部分保持不变

  1. 使用@babel/generator生成代码
const generator = require("@babel/generator").default;
const code = generator(ast, {}).code;
复制代码


经过generator函数最终生成我们的编译后的代码字符串

var name = 'Job';


至此,我们完成了parse(code) => ast => tansform(ast) => generator(ast) => code 这一babel流程。当然我们的示例代码非常简单,处理的过程也非常粗暴,真实的const 替换为 var的过程还要考虑作用域相关的问题,实际的词法分析语法分析转化都是非常复杂的过程,我们这边稍微了解其原理即可。


示例代码

const traverse = require("@babel/traverse").default
const generator = require("@babel/generator").default
const ast = require("@babel/parser").parse("const name = 'Job';");
traverse(ast, {
  enter(path) {
    if (path.node.type === 'VariableDeclaration' && path.node.kind === 'const') {
      path.node.kind = 'var'
    };
  }
})
const code = generator(ast, {}).code;
复制代码


后话


本篇文章简述了babel原理,分析了babel编译过程中ast的生成及运用,通过示例简单粗暴描述了babel编译的流程。good good staduy day day up


参考



相关文章
|
JavaScript 前端开发 编译器
分享:Babel7的配置
分享:Babel7的配置
291 0
|
1天前
|
JavaScript 前端开发
Babel 插件的作用是什么?
Babel 插件的作用是什么?
|
17天前
|
开发框架 自然语言处理 JavaScript
babel 原理,怎么写 babel 插件
【10月更文挑战第23天】要深入理解和掌握如何编写 Babel 插件,需要不断实践和探索,结合具体的项目需求和代码结构,灵活运用相关知识和技巧。你还可以进一步扩展和深入探讨各个方面的内容,提供更多的实例和细节,以使文章更加丰富和全面。同时,关注 Babel 插件开发的最新动态和研究成果,以便及时了解其发展和变化。
|
15天前
怎么写的 babel 插件
【10月更文挑战第25天】我们可以编写一个简单的Babel插件,并根据实际需求对其进行扩展和修改,以满足特定的代码转换需求。在实际编写Babel插件时,还需要对Babel的AST结构和各种节点类型有更深入的了解,以便能够更灵活地处理各种复杂的代码转换场景。
|
5月前
|
JavaScript 前端开发 API
你好,babel
你好,babel
|
6月前
|
编解码 JSON JavaScript
babel的学习
babel的学习
43 0
|
自然语言处理 前端开发 JavaScript
Babel 的工作原理以及怎么写一个 Babel 插件
Babel 的工作原理以及怎么写一个 Babel 插件
208 0
|
缓存 前端开发 JavaScript
Vite的原理
Vite的原理
87 0
|
JavaScript API
十问babel,用最简单的话说清楚babel(二)
十问babel,用最简单的话说清楚babel
106 0
|
Web App开发 移动开发 JavaScript
十问babel,用最简单的话说清楚babel(一)
十问babel,用最简单的话说清楚babel
275 0