从AST编译解析谈到写babel插件

之前一直在掘金上看到一些关于面试写babel插件的文章,最近也在学,以下就是学习后的总结。

关键词:AST编译解析, babel

AST编译解析

AST[维基百科]:在计算机科学中,抽象语法树Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于 if-condition-then 这样的条件跳转语句,可以使用带有两个分支的节点来表示。

和抽象语法树相对的是具体语法树(通常称作分析树)。一般的,在源代码的翻译和编译过程中,语法分析器创建出分析树。一旦AST被创建出来,在后续的处理过程中,比如语义分析阶段,会添加一些信息。

如何利用AST解析function ast(){},更改后重新恢复

分三步走:

  • 解析js的语法=>语法树
  • 遍历树(先序深度优先)=> 更改树的内容
  • 生成新的树
const esprima = require(‘esprima‘);//解析js的语法的包
const estraverse = require(‘estraverse‘);//遍历树的包
const escodegen = require(‘escodegen‘);//生成新的树的包

let code = `function ast(){}`;
//解析js的语法
let tree = esprima.parseScript(code);
//遍历树
estraverse.traverse(tree, {
    enter(node) {
        console.log(‘enter: ‘+node.type);
        }, leave(node){
         console.log(‘leave: ‘+node.type);
     }
});
//生成新的树
let r = escodegen.generate(tree);
console.log(r);

更改树的内容后

const esprima = require(‘esprima‘);
const estraverse = require(‘estraverse‘);
const escodegen = require(‘escodegen‘);

let code = `function ast(){}`;
let tree = esprima.parseScript(code);
estraverse.traverse(tree, {
    enter(node) {
        if (node.type === ‘Identifier‘) {
            node.name = ‘Jomsou‘;
        }
        // console.log(‘enter: ‘+node.type);
        // }, leave(node){
        //  console.log(‘leave: ‘+node.type);
     }
});
let r = escodegen.generate(tree);
console.log(r);
//结果
function Jomsou() {
}

babel插件

1、ES6箭头函数`let sum = (a, b)=>{return a+b};转化为ES5普通函数

const babel = require(‘babel-core‘);//babel核心解析库
const t = require(‘babel-types‘);//babel类型转化库

let code = `let sum = (a, b)=>{return a+b}`;
let ArrowPlugins = {
    //访问者模式
    visitor: {
        //捕获匹配的API
        ArrowFunctionExpression(path){
            let {node} = path;
            let body = node.body;
            let params = node.params;
            let r = t.functionExpression(null, params, body, false, false);
            path.replaceWith(r);
        }
    }
}
let d = babel.transform(code, {
    plugins: [
        ArrowPlugins
    ]
})
console.log(d.code);

箭头函数这样写let sum = (a, b)=>a+b;的转化

let babel = require(‘babel-core‘);
let t = require(‘babel-types‘);

let code = `let sum = (a, b)=>a+b`;

//.babelrc
let AllowPlugins = {
    visitor: {
        ArrowFunctionExpression(path){
            let node = path.node;
            let params = node.params;
            let body = node.body;
            if(!t.isBlockStatement(body)){
                let returnStatement = t.returnStatement(body);
                body = t.blockStatement([returnStatement]);
            }
            let funcs = t.functionExpression(null, params, body, false, false);
            path.replaceWith(funcs);
        }
    }
}
let r = babel.transform(code, {
    plugins:[
        AllowPlugins
    ]
})

console.log(r.code);

2、class

let code = `
class Jomsou{
    constructor(name){
        this.name = name;
    }
    getName(){
        return this.name;
    }
}
`

a) 实现constructor的转化

const babel = require(‘babel-core‘);//babel核心解析库
const t = require(‘babel-types‘);//babel类型转化库

/**
 * function Jomsou(name){
 *  this.name = name;
 * }
 * Jomsou.prototype.getName = function(){
 *  return this.name;
 * }
 */
let code = `
class Jomsou{
    constructor(name){
        this.name = name;
    }
    getName(){
        return this.name;
    }
}
`
let ClassPlugin = {
    visitor: {
        ClassDeclaration(path){
            let {node} = path;
            let className = node.id.name;
            className = t.identifier(className);
            //console.log(className);
            let funs = t.functionDeclaration(className, [], t.blockStatement([]), false, false);
            path.replaceWith(funs);
        }
    }
}
let d = babel.transform(code, {
    plugins: [
        ClassPlugin
    ]
})
console.log(d.code);

b) 实现class的方法函数转化为原型方法

const babel = require(‘babel-core‘);//babel核心解析库
const t = require(‘babel-types‘);//babel类型转化库

/**
 * function Jomsou(name){
 *
 * }
 */
let code = `
class Jomsou{
    constructor(name){
        this.name = name;
    }
    getName(){
        return this.name;
    }
}
`
let ClassPlugin = {
    visitor: {
        ClassDeclaration(path){
            let {node} = path;
            let className = node.id.name;
            className = t.identifier(className);
            let classList = node.body.body;
            //console.log(className);
            let funs = t.functionDeclaration(className, [], t.blockStatement([]), false, false);

            let es5func = [];

            classList.forEach((item, index)=>{
                let body = classList[index].body;

                if(item.kind===‘constructor‘)
                {
                    let params = item.params.length?item.params.map(item=>item.name):[];
                    params = t.identifier(params);
                    funs = t.functionDeclaration(className, [params], body, false, false);
                    path.replaceWith(funs);
                }else {
                    let protoObj = t.memberExpression(className, t.identifier(‘prototype‘));
                    let left = t.memberExpression(protoObj, t.identifier(item.key.name));
                    let right = t.functionExpression(null, [], body, false, false);

                    let assign = t.assignmentExpression(‘=‘, left, right);
                    es5func.push(assign);
                }
            })
            if(es5func.length==0)
            {
                path.replaceWith(funs);
            }
            else {
                es5func.push(funs);
                path.replaceWithMultiple(es5func);
            }
        }
    }
}
let d = babel.transform(code, {
    plugins: [
        ClassPlugin
    ]
})
console.log(d.code);

3、实现模块的按需加载

eg:

//babel-plugin-固定的前缀,放在node_module里
//babel-plugin-czq-import
const babel = require(‘babel-core‘);//babel核心解析库
const t = require(‘babel-types‘);//babel类型转化库
let code = `import {Button, ALert} from ‘antd‘`;
let importPlugin = {
    visitor: {
        ImportDeclaration(path){
            let {node} = path;
            //console.log(node);
            let source = node.source.value;
            let specifiers =  node.specifiers;
            if(!t.isImportDefaultSpecifier(specifiers[0])){
                specifiers = specifiers.map(specifier=>{
                    return t.importDeclaration(
                        [t.importDefaultSpecifier(specifier.local)],
                        t.stringLiteral(`${source}/lib/${specifier.local.name.toLowerCase()}`)
                    )
                });
                path.replaceWithMultiple(specifiers);
            }
        }
    }
}
let r = babel.transform(code, {
    plugins: [
        importPlugin
    ]
})

module.exports = importPlugin;

最后的测试

安装依赖:

npm antd babel-preset-env babel-preset-react react react-dom webpack webpack-cli --save-dev

测试代码:

//test.js
import React from ‘react‘;
import ReactDOM from ‘react-dom‘;

import {Button} from ‘antd‘;

测试:

npx webpack

用babel-plugin-czq-import前后的效果对比:

前:

后:

原文:从AST编译解析谈到写babel插件,欢迎star,欢迎交流。

项目地址babelPlugin

参考地址:

esprima官网

babel在github上的文档

原文地址:https://www.cnblogs.com/Jomsou/p/10341234.html

时间: 2024-11-10 02:15:40

从AST编译解析谈到写babel插件的相关文章

MYSQL 源代码 编译原理 AST和解析树 代码语法解析

MYSQL 源代码 编译原理 AST和解析树 代码语法解析 http://blog.csdn.net/wfp458113181wfp/article/details/17082355 使用AST树 分类:             antlr              2013-12-02 22:39     255人阅读     评论(0)     收藏     举报 目录(?)[+] 第五章使用AST树中间结果来计算表达式值 创建ASTS 第五章.使用AST树中间结果来计算表达式值 现在我们已

TableML-GUI篇(Excel编译/解析工具)

项目情况 本文接上篇TableML Excel编译/解析工具,本文主要介绍GUI工具的使用,及配置项,如果你想了解此工具更加详细的说明,请阅读上篇文章. 项目地址:https://github.com/zhaoqingqing/TableML 项目介绍和更新日志 项目介绍 TableML, Table Markup Language, 基于电子表格的标记语言, 类似JSON, XML, INI,TableML可以作为软件项目的配置标记语言, 与之不同的是,您可以使用Excel等电子表格编辑软件来

快速写一个babel插件

es6/7/8的出现,给我们带来了很多方便,但是浏览器并不怎么支持,目前chrome应该是支持率最高的,所以为了兼容我们只能把代码转成es5,这应该算是我们最初使用babel的一个缘由,随着业务的开发,我们会有很多自己定制化的需求,单纯的bebel并不能解决我们所有的问题,所以babel插件应用而来,本文将会采用较为通俗的语言来描述如何快速写一个babel插件. 一.babel的作用 babel的作用其实就是一个转换器,把我们的代码转成浏览器可以运行的代码,类似于加工厂的概念.解析代码都是一个文

一步一步写jQuery插件

转载自:http://www.cnblogs.com/joey0210/p/3408349.html 前言 如今做web开发,jquery 几乎是必不可少的,就连vs神器在2010版本开始将Jquery 及ui 内置web项目里了.至于使用jquery好处这里就不再赘述了,用过的都知道.今天我们来讨论下jquery的插件机制,jquery有着成千上万的第三方插件,有时我们写好了一个独立的功能,也想将其与jquery结合起来,可以用jquery链式调用,这就要扩展jquery,写成插件形式了,如下

写JQuery 插件 什么?你还不会写JQuery 插件

http://www.cnblogs.com/Leo_wl/p/3409083.html 前言 如今做web开发,jquery 几乎是必不可少的,就连vs神器在2010版本开始将Jquery 及ui 内置web项目里了.至于使用jquery好处这里就不再赘述了,用过的都知道.今天我们来讨论下jquery的插件机制,jquery有着成千上万的第三方插件,有时我们写好了一个独立的功能,也想将其与jquery结合起来,可以用jquery链式调用,这就要扩展jquery,写成插件形式了,如下面就是一个简

什么?你还不会写JQuery 插件

前言 如今做web开发,jquery 几乎是必不可少的,就连vs神器在2010版本开始将Jquery 及ui 内置web项目里了.至于使用jquery好处这里就不再赘述了,用过的都知道.今天我们来讨论下jquery的插件机制,jquery有着成千上万的第三方插件,有时我们写好了一个独立的功能,也想将其与jquery结合起来,可以用jquery链式调用,这就要扩展jquery,写成插件形式了,如下面就是一个简单扩展Jquery对象的demo: //sample:扩展jquery对象的方法,bold

锋利的jQuery--编写jQuery插件(读书笔记五)[完结篇]

1.表单验证插件Validation   2.表单插件Form 3.动态事件绑定插件livequery 可以为后来的元素绑定事件 类似于jQuery中的live()方法 4.jQuery UI   5.jQuery Cookie   6.遮罩层插件:thickbox 7.编写jQuery插件 <1>编写插件的目的:给已经有的一些列方法或函数做一个封装,一遍在其他地方使用,方便后期维护和提高开发效率. <2>三种类型的插件 a:封装对象方法的插件 jQuery.fn.extend()

写jQuery插件

手把手教你怎么写jQuery插件 这次随笔,向大家介绍如何编写jQuery插件.啰嗦一下,很希望各位IT界的‘攻城狮’们能和大家一起分享,一起成长.点击左边我头像下边的“加入qq群”,一起分享,一起交流,当然,可以一起吹水.哈,不废话,进入正题. jQuey是一个非常好用的javascript类库,提供了非常多的接口给程序员使用.但在某些具体方面,并没有完全提供解决方法,这就要求我们自己实现.jQuery官方也提供jQuery拓展的标准.编写jQuery插件时,应该注意一下几点原则: 1. 避免

写chrome插件---一个优酷自动加粉丝助手

写chrome插件主要就是写js , 我们要构造界面(HTML), 以及样式(CSS),  以及chrome给我们提供的jsAPI, 主要是chrome的API, 调试的话可以使用chrome的开发者工具(f12)直接调试; API地址的截图先过一遍, 这个非常重要: 如果从来没写过chrome插件, 我们可以参考这个打开, 里面有很多现成的DEMO, 我们能够直接在线看一些简单的DEMO代码: 我写的这个youku自动订阅助手使用了bootstrap和jQ(个人认为是标配,(●'?'●)),也