消除面对Babel的恐惧——Babel其实没那么可怕

前言

不管是浏览器解析JS代码的还是适配浏览器的兼容性、开发各种打包插件,基本都离不开babel的身影,babel的原理其实没有我们想象中的的那么复杂,只是它做的事情比较繁多和繁杂,原理还是挺简单的,下面,我们一一来梳理一下babel的原理

Babel是什么

babel 最开始叫 6to5,顾名思义是 es6 转 es5,但是后来随着 es 标准的演进,有了 es7、es8 等, 6to5 的名字已经不合适了,所以改名为了 babel。

babel实际上就是转译器,它可以将ESnext、Typescript、flow转换成浏览器所能支持的JS代码,它也可以做一些静态分析和代码转换,比如type check和js解释器,现在最流行的taro就是通过修改babel转换后的AST来来实现的。

Babel探究Babel的原理

babel将代码转换成AST抽象语法树后会暴露出很多api,这些api可以让让我们操作这个AST抽象语法树,从而实现我们的需求。

babel刚开始会将需要转译的代码进行语法分析和词法分析,所谓的词法分析就是,例如 let name =1in,把这段代码拆分成 let、name、=、1in,就是词法分析,把代码拆分成不可再拆分的token,就是词法分析,所谓的语法分析就是通过拆分后的token组成AST抽象语法树,就是语法分析。

随后,在转换的AST的抽象语法树中,我们可以通过babel暴露出来的api操纵AST抽象语法树,修改AST抽象语法树,生成新的抽象语法树,然后我们在通过新的抽象语法树去生成新的代码,这就是babel的转换原理。

babel转译的每个阶段都有对应的packge,我们只需要下载对应的packge引用api就可以根据自己的需求实现转译。

关于babel的api和ast抽象语法树的类型分别有哪些和描述,大家可以通过babel官网去查看,这里就不过多赘述

下面,我们做一个小案例,来体验一下babel的厉害之处。

我们在代码中经常使用console.log去调试代码,但是我们一旦console.log过多的时候,就不知道console.log是在哪一行输出的,所以我们可以用babel来帮助我们记录一下console.log是在哪一行的。

js 复制代码
//也就是  转译前
console.log(111)

//转译后
console.log('line column',111)

明白需求后,我们动手。

我们先使用astexplorer.net/来看一下console.log的抽象语法树是什么样子的。

我们可以看到,函数表达式的AST属于CallExression。

那我们要做的是在遍历 AST 的时候对 console.log、console.info 等 api 自动插入一些参数,也就是要通过 visitor 指定对 CallExpression 的 AST 做一些修改。

CallExrpession 节点有两个属性,callee 和 arguments,分别对应调用的函数名和参数, 所以我们要判断当 callee 是 console.xx 时,在 arguments 的数组中中插入一个 AST 节点。

js 复制代码
const parser = require('@babel/parser');
// 因为 `@babel/parser` 等包都是通过 es module 导出的,
// 所以通过 commonjs 的方式引入有的时候要取 default 属性。
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const types = require('@babel/types');

const ast = parser.parse(sourceCode, {
    //这里有这个选项是因为我们不知道使用ES module导出的还是commonjs导出的,所以我们用
    // unambiguous让它自己判断
    sourceType: 'unambiguous',
    // 因为可能会用到jsx语法,所以这里引入jsx插件
    plugins: ['jsx']
});

const targetCalleeName = ['log', 'info', 'error', 'debug'].map(item => `console.${item}`);

traverse(ast, {
    CallExpression(path, state) {
        //通过path.node.callee拿到调用的函数名,然后用genrate的code把它转换成字符串
        const calleeName = generate(path.node.callee).code;
        
        if (targetCalleeName.includes(calleeName)) {
            //从path.node.loc.start中拿到行号和列号
            const { line, column } = path.node.loc.start;
            path.node.arguments.unshift(types.stringLiteral(`filename: (${line}, ${column})`))
        }
    }
});

以上就是babel利用AST抽象语法树转译的原理,原理其实很简单,不接触的话,是会有些恐惧。复杂的转译会有难度,但是类似这种简单实用的转译还是可以做到举一反三的效果。

相关推荐
发现一只大呆瓜23 分钟前
AI流式交互:SSE与WebSocket技术选型
前端·javascript·面试
m0_719084111 小时前
React笔记张天禹
前端·笔记·react.js
Ziky学习记录2 小时前
从零到实战:React Router 学习与总结
前端·学习·react.js
wuhen_n2 小时前
JavaScript链表与双向链表实现:理解数组与链表的差异
前端·javascript
wuhen_n2 小时前
JavaScript数据结构深度解析:栈、队列与树的实现与应用
前端·javascript
我是一只puppy2 小时前
使用AI进行代码审查
javascript·人工智能·git·安全·源代码管理
颜酱2 小时前
从二叉树到衍生结构:5种高频树结构原理+解析
javascript·后端·算法
狗哥哥2 小时前
微前端路由设计方案 & 子应用管理保活
前端·架构
前端大卫3 小时前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘3 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js