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

相关推荐
记忆深处的声音几秒前
vue2 + Element-ui 二次封装 Table 组件,打造通用业务表格
前端·vue.js·代码规范
陈随易1 分钟前
兔小巢收费引发的论坛调研Node和Deno有感
前端·后端·程序员
熊的猫16 分钟前
webpack 核心模块 — loader & plugins
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
速盾cdn23 分钟前
速盾:vue的cdn是干嘛的?
服务器·前端·网络
四喜花露水1 小时前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
前端Hardy1 小时前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
web Rookie2 小时前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
Au_ust2 小时前
css:基础
前端·css
帅帅哥的兜兜2 小时前
css基础:底部固定,导航栏浮动在顶部
前端·css·css3
工业甲酰苯胺2 小时前
C# 单例模式的多种实现
javascript·单例模式·c#