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

关键词:AST编译解析, babel

AST编译解析

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

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

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

分三步走:

  • 解析js的语法=>语法树
  • 遍历树(先序深度优先)=> 更改树的内容
  • 生成新的树
  1. const esprima = require('esprima');//解析js的语法的包
  2. const estraverse = require('estraverse');//遍历树的包
  3. const escodegen = require('escodegen');//生成新的树的包
  4. let code = `function ast(){}`;
  5. //解析js的语法
  6. let tree = esprima.parseScript(code);
  7. //遍历树
  8. estraverse.traverse(tree, {
  9. enter(node) {
  10. console.log('enter: '+node.type);
  11. }, leave(node){
  12. console.log('leave: '+node.type);
  13. }
  14. });
  15. //生成新的树
  16. let r = escodegen.generate(tree);
  17. console.log(r);

更改树的内容后

  1. const esprima = require('esprima');
  2. const estraverse = require('estraverse');
  3. const escodegen = require('escodegen');
  4. let code = `function ast(){}`;
  5. let tree = esprima.parseScript(code);
  6. estraverse.traverse(tree, {
  7. enter(node) {
  8. if (node.type === 'Identifier') {
  9. node.name = 'Jomsou';
  10. }
  11. // console.log('enter: '+node.type);
  12. // }, leave(node){
  13. // console.log('leave: '+node.type);
  14. }
  15. });
  16. let r = escodegen.generate(tree);
  17. console.log(r);
  1. //结果
  2. function Jomsou() {
  3. }

babel插件

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



  1. const babel = require('babel-core');//babel核心解析库
  2. const t = require('babel-types');//babel类型转化库
  3. let code = `let sum = (a, b)=>{return a+b}`;
  4. let ArrowPlugins = {
  5. //访问者模式
  6. visitor: {
  7. //捕获匹配的API
  8. ArrowFunctionExpression(path){
  9. let {node} = path;
  10. let body = node.body;
  11. let params = node.params;
  12. let r = t.functionExpression(null, params, body, false, false);
  13. path.replaceWith(r);
  14. }
  15. }
  16. }
  17. let d = babel.transform(code, {
  18. plugins: [
  19. ArrowPlugins
  20. ]
  21. })
  22. console.log(d.code);

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

  1. let babel = require('babel-core');
  2. let t = require('babel-types');
  3. let code = `let sum = (a, b)=>a+b`;
  4. //.babelrc
  5. let AllowPlugins = {
  6. visitor: {
  7. ArrowFunctionExpression(path){
  8. let node = path.node;
  9. let params = node.params;
  10. let body = node.body;
  11. if(!t.isBlockStatement(body)){
  12. let returnStatement = t.returnStatement(body);
  13. body = t.blockStatement([returnStatement]);
  14. }
  15. let funcs = t.functionExpression(null, params, body, false, false);
  16. path.replaceWith(funcs);
  17. }
  18. }
  19. }
  20. let r = babel.transform(code, {
  21. plugins:[
  22. AllowPlugins
  23. ]
  24. })
  25. console.log(r.code);

2、class

  1. let code = `
  2. class Jomsou{
  3. constructor(name){
  4. this.name = name;
  5. }
  6. getName(){
  7. return this.name;
  8. }
  9. }
  10. `

a) 实现constructor的转化

  1. const babel = require('babel-core');//babel核心解析库
  2. const t = require('babel-types');//babel类型转化库
  3. /**
  4. * function Jomsou(name){
  5. * this.name = name;
  6. * }
  7. * Jomsou.prototype.getName = function(){
  8. * return this.name;
  9. * }
  10. */
  11. let code = `
  12. class Jomsou{
  13. constructor(name){
  14. this.name = name;
  15. }
  16. getName(){
  17. return this.name;
  18. }
  19. }
  20. `
  21. let ClassPlugin = {
  22. visitor: {
  23. ClassDeclaration(path){
  24. let {node} = path;
  25. let className = node.id.name;
  26. className = t.identifier(className);
  27. //console.log(className);
  28. let funs = t.functionDeclaration(className, [], t.blockStatement([]), false, false);
  29. path.replaceWith(funs);
  30. }
  31. }
  32. }
  33. let d = babel.transform(code, {
  34. plugins: [
  35. ClassPlugin
  36. ]
  37. })
  38. console.log(d.code);

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

  1. const babel = require('babel-core');//babel核心解析库
  2. const t = require('babel-types');//babel类型转化库
  3. /**
  4. * function Jomsou(name){
  5. *
  6. * }
  7. */
  8. let code = `
  9. class Jomsou{
  10. constructor(name){
  11. this.name = name;
  12. }
  13. getName(){
  14. return this.name;
  15. }
  16. }
  17. `
  18. let ClassPlugin = {
  19. visitor: {
  20. ClassDeclaration(path){
  21. let {node} = path;
  22. let className = node.id.name;
  23. className = t.identifier(className);
  24. let classList = node.body.body;
  25. //console.log(className);
  26. let funs = t.functionDeclaration(className, [], t.blockStatement([]), false, false);
  27. let es5func = [];
  28. classList.forEach((item, index)=>{
  29. let body = classList[index].body;
  30. if(item.kind==='constructor')
  31. {
  32. let params = item.params.length?item.params.map(item=>item.name):[];
  33. params = t.identifier(params);
  34. funs = t.functionDeclaration(className, [params], body, false, false);
  35. path.replaceWith(funs);
  36. }else {
  37. let protoObj = t.memberExpression(className, t.identifier('prototype'));
  38. let left = t.memberExpression(protoObj, t.identifier(item.key.name));
  39. let right = t.functionExpression(null, [], body, false, false);
  40. let assign = t.assignmentExpression('=', left, right);
  41. es5func.push(assign);
  42. }
  43. })
  44. if(es5func.length==0)
  45. {
  46. path.replaceWith(funs);
  47. }
  48. else {
  49. es5func.push(funs);
  50. path.replaceWithMultiple(es5func);
  51. }
  52. }
  53. }
  54. }
  55. let d = babel.transform(code, {
  56. plugins: [
  57. ClassPlugin
  58. ]
  59. })
  60. console.log(d.code);

3、实现模块的按需加载

eg:





  1. //babel-plugin-固定的前缀,放在node_module里
  2. //babel-plugin-czq-import
  3. const babel = require('babel-core');//babel核心解析库
  4. const t = require('babel-types');//babel类型转化库
  5. let code = `import {Button, ALert} from 'antd'`;
  6. let importPlugin = {
  7. visitor: {
  8. ImportDeclaration(path){
  9. let {node} = path;
  10. //console.log(node);
  11. let source = node.source.value;
  12. let specifiers = node.specifiers;
  13. if(!t.isImportDefaultSpecifier(specifiers[0])){
  14. specifiers = specifiers.map(specifier=>{
  15. return t.importDeclaration(
  16. [t.importDefaultSpecifier(specifier.local)],
  17. t.stringLiteral(`${source}/lib/${specifier.local.name.toLowerCase()}`)
  18. )
  19. });
  20. path.replaceWithMultiple(specifiers);
  21. }
  22. }
  23. }
  24. }
  25. let r = babel.transform(code, {
  26. plugins: [
  27. importPlugin
  28. ]
  29. })
  30. module.exports = importPlugin;

最后的测试

安装依赖:

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

测试代码:

  1. //test.js
  2. import React from 'react';
  3. import ReactDOM from 'react-dom';
  4. import {Button} from 'antd';

测试:

  1. npx webpack

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

前:

后:

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

项目地址babelPlugin

参考地址:

esprima官网

babel在github上的文档

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

  1. 开发一个简单的babel插件

    前言 对于前端开发而言,babel肯定是再熟悉不过了,工作中肯定会用到.除了用作转换es6和jsx的工具之外,个人感觉babel基于抽象语法树的插件机制,给我们提供了更多的可能.关于babel相关概念 ...

  2. 【babel+小程序】记“编写babel插件”与“通过语法解析替换小程序路由表”的经历

    话不多说先上图,简要说明一下干了些什么事.图可能太模糊,可以点svg看看 背景 最近公司开展了小程序的业务,派我去负责这一块的业务,其中需要处理的一个问题是接入我们web开发的传统架构--模块化开发. ...

  3. Atitit main函数的ast分析  数组参数调用的ast astview解析

    Atitit main函数的ast分析  数组参数调用的ast astview解析 1.1. Xxcls.main(new String[]{"","bb"}) ...

  4. 张小龙谈如何写E-mail软件

    编者语:鼎鼎大名的Foxmail软件制作者,你一定不会陌生吧!本刊第三期特刊还刊登过此君的生活照一张,可谓威风八面.小编此次突发奇想,“死缠烂打”,费了九牛二虎之力,终于约他写了一篇有关写E-mail ...

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

    项目情况 本文接上篇TableML Excel编译/解析工具,本文主要介绍GUI工具的使用,及配置项,如果你想了解此工具更加详细的说明,请阅读上篇文章. 项目地址:https://github.com ...

  6. TableML-GUI篇(C# 编译/解析 Excel/CSV工具)

    项目情况 本文接上篇TableML Excel编译/解析工具,本文主要介绍GUI工具的使用,及配置项,如果你想了解此工具更加详细的说明,请阅读上篇文章. 项目地址:https://github.com ...

  7. 【前端知识体系-JS相关】你真的了解JavaScript编译解析的流程吗?

    1. JS编译解析的流程 1.1 JS运行分三步 语法分析(通篇扫描是否有语法错误),预编译(发生在函数执行的前一刻),解释执行(一行行执行). 1.2 预编译执行分五步 创建AO对象(Activat ...

  8. 快速写一个babel插件

    es6/7/8的出现,给我们带来了很多方便,但是浏览器并不怎么支持,目前chrome应该是支持率最高的,所以为了兼容我们只能把代码转成es5,这应该算是我们最初使用babel的一个缘由,随着业务的开发 ...

  9. (6)webpack使用babel插件的使用

    为什么要使用babel插件? 首先要了解babel插件是干嘛的,随着js的语法规范发展,出现了越来越多的高级语法,但是使用webpack打包的时候,webpack并不能全部理解这些高级语法,需要我们使 ...

随机推荐

  1. js和css实现手机横竖屏预览思路整理

    实现效果,如上图. 首先,实现手机页面在PC端预览, 则先在网上找到一个手机的背景图片,算好大概内间距,用来放预览的页面,我这里是给手机预览页面的尺寸按iphone5的尺寸来的: 一个手机页面在这里预 ...

  2. Java并发面试题

    一.什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速.比如,如果一个线程完成一 ...

  3. nw.js---创建一个点击菜单

    使用nw.js创建一个可点击的菜单: <!doctype html> <html lang="en"> <head> <meta char ...

  4. gitlab+jenkins+tomcat CI/CD 部署

    整个项目的框架为: gitlab的安装与使用(Centos7) gitlab的安装 新建yum源 vim /etc/yum.repos.d/gitlab-ce.repo [gitlab-ce] nam ...

  5. ECharts(中国地图篇)的使用

    代码html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <me ...

  6. Codeforces Round 504

    (交互题真神奇,,,我自己瞎写了一发目测样例都没过去就AC了...) (只出了两题的竟然没掉下蓝名真是可怕) A:我的代码太不美观了,放个同学的(因为我是c++63分的蒟蒻所以根本不知道那些函数怎么用 ...

  7. ConfuserEx壳

    前言: 这几天用Rolan的时候出现了点问题,然后发现了这个非常好用的工具居然只有几百k,打算逆向一下,然后发现了ConfuserEx壳 探索: Rolan是用C#写的,刚开始用EXEinfoPE打开 ...

  8. mybatis07--关联查询一对多

    案例   查询国家的同时,查询出国家下的省会信息! 01.使用单表的连接查询 创建对应的实体类 和数据库表 /** * *国家的实体类 */ public class Country { privat ...

  9. super方法 调用父类的方法

    描述 super() 函数是用于调用父类(超类)的一个方法. super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO).重复 ...

  10. image的srcset属性

    介绍 响应式页面中经常用到根据屏幕密度设置不同的图片.这个时候肯定会用到image标签的srcset属性.srcset属性用于设置不同屏幕密度下,image自动加载不同的图片.用法如下: <im ...