消除面对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抽象语法树转译的原理,原理其实很简单,不接触的话,是会有些恐惧。复杂的转译会有难度,但是类似这种简单实用的转译还是可以做到举一反三的效果。

相关推荐
new出一个对象1 小时前
uniapp接入BMapGL百度地图
javascript·百度·uni-app
你挚爱的强哥2 小时前
✅✅✅【Vue.js】sd.js基于jQuery Ajax最新原生完整版for凯哥API版本
javascript·vue.js·jquery
y先森3 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy3 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189113 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿4 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡5 小时前
commitlint校验git提交信息
前端
虾球xz5 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇5 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒5 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript