阅读 269

babel大揭秘

babel分享

babel介绍

js 转译器 es6 to es5 es7 to es5 简称 es typescript 代码转换 除了这些 我们还可以做一些 静态分析 eslint等

babel 是源码 => 源码的过程

babel 代码转换的过程分为三步

  1. parse 代码 => ast
  2. transform 操作语法书 增删改 ast => ast (修改后的)
  3. generate 修改后的 ast => 新的代码
复制代码

ast 介绍

    抽象语法书 (Abstract Syntax Tree,AST)以树形结构表达 编程语言 的语法
复制代码

ast语法树 代码是从 经过词法分析 和 语法分析 最终生成ast 举一个例子 我是张三 词法分析的过程就相当于 张三 语法分析 一个赋值语句 张三 赋值给

me = 'zhangsan' 词法分析过程最终生成结果 token (不能再拆分的单词) me、= 、'zhangsan' 语法分析的过程 发现 有一个等号(operator 操作符) 发现是赋值操作 left 是 me (标识符 identifierright 是 (字符串字面量 StringLiteral) 'xiaomenggang '

  1. 举个例子

astexplorer.net/

javascript:

    console.log(1)
复制代码

WX20210711-000224@2x.png

WX20210711-000251@2x.png

他的树形结构是这样的

流程图.png

ast 常见节点

Literal 字面量 字符串字面量 StringLiteral, 例如 'xmg' 数字字面量 NumericLiteral, 例如 1 布尔字面量 BooleanLiteral , 例如 true or false 正则表达式字面量 RegExpLiteral , 例如 /^[0-9]/ Identifier 标识符

Statement 语句

代码节点名称中文名
forForStatement循环语句
whileWhileStatementwhile语句
continueContinueStatementcontinue语句
SwitchSwitchStatementSwitch语句

Declaration 声明语句

代码节点名称中文名
var a = 'xmg';variableDeclaratio变量声明
function fn(){}functionDeclaratio函数声明
import d from 'e';importDeclaratio导出声明
SwitchSwitchStatementSwitch语句

Expression 表达式

代码节点名称中文名
[1,2,3]ArrayExpression数组表达式
a = 1AssignmentExpression赋值表达式
1 + 2BinaryExpression二元表达式
funciton () {}FunctionExpression函数表达式
() => {}ArrowFunctionExpression箭头函数表达式

超级微小编译器

github.com/jamiebuilds…

简单的从 词法分析 到 语法分析的一个简单实现过程

我们先来看下babel 的api

parse

parse @babel/parser 将代码转换成 ast

参数 pluginsouseTypescript script 则不解析 es module 语法 module 是解析 es module 语法 unambiguous 根据环境自动判断 )

transform @babel/traverse 遍历AST 使用访问者 模式 对ast修改

traverse(ast, visitor)

visitor: {
  StringLiteral(path) {
    debugger
    console.log(path.node.value)
  }
}
复制代码

path 上面有很多方法 比如 path.node 当前节点 比如 path.parent 获取父级节点 path.insertBefore 向前插入 path.repalceWith 替换 path.remove 删除 path.stop 停止遍历

generate @babel/generate 将代码 从 ast 转成 code

@babel/types 提供快速生成 ast 和 断言的方法 创建 ast和判断ast的节点类型 比如 types.IfStatement()

if(1){}
复制代码
  types.ifStatement(types.NumericLiteral(1),types.blockStatement([]))
复制代码

types.isIfStatement():boolean

我们再来做一个例子 删除console代码

const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const types = require('@babel/types');


// console.log(types.ifStatement(types.NumericLiteral(1),types.blockStatement([])))
// console.log(generate(types.ifStatement(types.NumericLiteral(1),types.blockStatement([]))))

const sourceCode = `
    console.log(1);
    function func() {
        var a = 2
        console.info(a);
    }
    export default class Clazz {
        say() {
            var b = 3
            console.debug(b);
        }
        render() {
            let bbb = 333
            console.log(bbb)
            return <div>{bbb}</div>
        }
    }
`;

const ast = parser.parse(sourceCode, {
    sourceType: 'unambiguous',
    plugins: ['jsx']
});

traverse(ast, {
    StringLiteral(path) {
        debugger
        console.log(path)
    },
    CallExpression(path) {
        // if(path.scope) {
        //     path.scope.generateUid('maidian')
        //     console.log(path.scope.generateUid('maidian'))
        // }
        // types.ifStatement
        if ( types.isMemberExpression(path.node.callee) 
            && path.node.callee.object.name === 'console' 
           ) {
            path.remove()
        }
    }
});

const { code, map } = generate(ast);
console.log(code);
复制代码

我们再来做一个例子 实现代码混淆

我们要说下 path 上的 scope 是作用域信息 生成作用域的就是模块、函数、块 作用域之间会形成嵌套关系,也就是作用域链

scope.bindings 当前作用域内声明的所有变量

const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const types = require('@babel/types');
let num=0;
const string = `abcdefghigklsisysjsks`

const sourceCode = `
    function getScr() {
        const num1 = 3
        const num3 = num1**num1
        const num5 = num3**num3
        function add () {
            return num5 + num3
        }
        const sum = add()
        return sum
    }
`;

const ast = parser.parse(sourceCode, {
    sourceType: 'unambiguous',
});

traverse(ast, {
    StringLiteral(path) {
        console.log(path)
    },
    Scopable(path, state) {
        if(path.scope.bindings) {
            Object.entries(path.scope.bindings).forEach(([key,binding] )=> {
                binding.scope.rename(key,binding.scope.generateUid(string[num++]))
            })
        }
    }
});

const { code, map } = generate(ast);
console.log(code);
复制代码
文章分类
前端
文章标签