美文网首页
babel 修改抽象语法树——入门与实践

babel 修改抽象语法树——入门与实践

作者: 四爷在此 | 来源:发表于2018-10-08 23:43 被阅读251次

问题由来

最近有个想法,之前用Angular2.x 的时候,官方提供了ng-cli 可以一键生成component、service、directive 等代码文件,并且还可以修改对应的routes 配置文件,使得组件自动加入app 的前端路由中(一键生成或修改数个文件)。这使得前端开发效率大为提高,我们不必再手动去创建那么多文件夹、文件,并且手动修改route 配置。但是vue-cli 没有提供这种功能,所以我们想要写个node.js 脚本去做这个工作。除了用正则替换的解决办法,更科学的实际上就需要用到修改代码抽象语法树的方法。

babel 编译过程

我们通常用 babel 去编译ES6/ES7 为ES5,以便于 js 脚本运行在各种浏览器上。这个编译的过程实际上是语法转换的过程,比如箭头函数转为函数表达式,this 的显式绑定等等。那么babel 在做这个工作的时候实际上经历了几个步骤,parse => transform (AST) => generate

https://www.sitepoint.com/understanding-asts-building-babel-plugin/

所以要想完成这几个步骤,babel 提供了几个实用工具(Babylon,babel-traverse,babel-generator),我们的思路就是找到route 配置表中该插入新路由的地方,插入新路由并且保存文件。
babel.transform 核心函数接受源代码字符串和options 作为输入,返回一个Object 包含几个属性:新的代码字符串,sourcemap,ast 语法树对象。

babel.transform("code();", options, function(err, result) {
  result.code;
  result.map;
  result.ast;
});

实践新增一个route对象

以下是等待修改的路由配置文件,

// './src/router.ts'
export default new Router({
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/about',
      name: 'about',
      component: About
    }
    // to be append new route.
  ]
})

以下是通过 babel 修改route 配置文件的过程

var fs = require('fs');
let babel = require('babel-core');
let t = require('babel-types');
let template = require('@babel/template');
// 读取需要修改的源代码内容
var content = fs.readFileSync('./src/router.ts').toString();

const newRoute = {
  path: '/list',
  name: 'list'
  // component: ListComponent
};

// 定义一个 babel 插件,拦截并修改 routes 的数组表达式
let visitor = {
  ArrayExpression(path) {
    const elements = path.node.elements;
    console.warn(`routes number:  ${elements.length}`);
    // 新增一个构建出来的 route 对象
    elements.push(t.objectExpression([
      t.objectProperty(t.identifier('path'), t.stringLiteral(newRoute.path)),
      t.objectProperty(t.identifier('name'), t.stringLiteral(newRoute.name)),
      t.objectProperty(t.identifier('component'), t.identifier('ListComponent'))
    ]));
  }
}

// 通过 plugin 转换源代码 parse 出来的AST 抽象语法树,并且返回结果
let result= babel.transform(content, {
   plugins: [
     { visitor }
   ]
 });

 console.warn(`res: ${result.code}`);
 // 把新代码写入新文件.
 fs.writeFileSync('newRoute.ts', result.code);

比较关键的部分就是在visitor 这个自定义的插件中,拦截ArrayExpression,这是routes: [] 对应的路由数组。而这个数组表达式包含了一个elements 数组,每个对象在AST 中都是ObjectExpression 类型。不论是数组表达式,还是对象表达式,都是对应 babel-types 中不同的节点(node)类型。所以我们在构建新的 AST 节点时,可以参考AST explorer 中已有的节点类型。

例如这里我们要新增一个route 对象,则是用babel-types 中的 types.objectExpression(objectProperty[]) 生成一个,根据智能提示传入参数,要求是objectProperty 数组,那么我们又利用 types.objectProperty(key: identifier, value: string) 生成一个。

t.objectExpression([ 
  // 对象中的第一个属性 path: string;
  t.objectProperty(t.identifier('path'), t.stringLiteral(newRoute.path)),
  // ...
]);
AST explorer 中源代码和AST的对应关系

根据官方docs,也可以利用 AST explorer 去寻找对应关系,结合 IDE 的智能提示来构建你所需要的 AST 语法树,就可以自动转换成你想要的代码了。实际上,搞懂了babel ,就可以做出ng-cli generate 这样智能高效的功能了。

参考文章:
https://www.sitepoint.com/understanding-asts-building-babel-plugin/
http://welefen.com/post/understanding-asts-by-building-your-own-babel-plugin.html
babel 官方文档
AST Explorer

相关文章

  • babel 修改抽象语法树——入门与实践

    问题由来 最近有个想法,之前用Angular2.x 的时候,官方提供了ng-cli 可以一键生成component...

  • vue3原理

    AST AST:抽象语法树,Abstract Syntax Tree。TypeScript、babel、webpa...

  • 零散专题35 AST抽象语法树.md

    什么是抽象语法树 抽象语法树(abstract syntax tree,AST,或者简称语法树)是源代码的抽象语法...

  • JS抽象语法树(AST)

    什么是抽象语法树 抽象语法树(Abstract Syntax Tree)也称为AST语法树,是源代码语法所对应的树...

  • AST

    抽象语法树 抽象语法树(Abstract Syntax Tree)简称 AST ,是源代码的抽象语法结构的树状表现...

  • babel插件的使用和AST抽象语法树

    AST抽象语法树 代码都可以拆解成抽象语法树,语法树解析网站:https://astexplorer.net/ 作...

  • 基于抽象语法树改造的计算器实例

    抽象语法树 在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Synt...

  • 编译小解

    在使用Babel的时间,总是遇到AST(抽象语法树)的问题,觉得有点高深。其实这些东西在大学编译原理这么课程中学到...

  • 抽象语法树AST的全面分析(三)

    AST操作 抽象语法树AST的全面分析(一)抽象语法树AST的全面分析(二)前面两篇文章写到了抽象语法树的生成过程...

  • Babel的简单了解

    在了解Babel之前我们有必要了解下AST, 什么是AST呢? AST(抽象语法树) 如上代码可以被表示如下的一棵...

网友评论

      本文标题:babel 修改抽象语法树——入门与实践

      本文链接:https://www.haomeiwen.com/subject/zmwknftx.html