写一个 JS 调用栈可视化工具 hound-trace

PowerShell
• 阅读 1877

背景

最近在分析一些框架源码,在写笔记的时候,一些函数的调用栈希望用流程图的形式记录下来,打开 http://draw.io 就是一顿操作,画了几个调用栈之后,感觉很麻烦。于是蹲在厕所里的我开始思考了,调用栈既然可以用 console.trace() 打印出来,那是不是也可以把数据记录下来直接画出流程图来?

当然我从不喜欢造轮子,首先熟练的打开 google 操作一波,发现地球之大,竟然没有我想要的工具?没有 JS 调用栈可视化工具?怎么办?在继续用 draw.io 手画和自己造轮子之间我陷入了深思。

当然我最后选择了造轮子,不然就没有这篇文章了。

轮子长这样 —> hound-trace <--

比如说有这样一份代码:

// 轮子
import houndTrace from 'hound-trace-ui';

function f() {}

function e() {}

function d() { f() }

const b = () => { d(); e() };

const c = function () { d() };

function a() { b(); c() }

houndTrace.start();
a();
houndTrace.end();

可视化输出:

写一个 JS  调用栈可视化工具 hound-trace

造轮子的过程

### 首先要取个酷炫的名字

代码写的好不好,工具好不好用,不重要,一定要有酷炫的名字,那就是 hound-trace ,看这个谷歌翻译出来的名字多么清新脱俗,光芒四射。

怎么去拿调用栈信息?

首先想到的是能不能在函数调用前后做点什么?作为 21 世纪的程序员,下手前当然是先 googlestackoverflow 一波,看看能不能轻轻松松当个搬运工。逛了半天,靠谱的答案似乎有这些:

// 偷天换日大法
var old = UIIntentionalStream.instance.loadOlderPosts;
UIIntentionalStream.instance.loadOlderPosts = function() {
    // hook before call
    old();
    // hook after call
};


// 原型拓展大法
Function.prototype.before = function (callback) {
    var that = this;
    return (function() {
        callback.apply(this, arguments);
        return (that.apply(this, arguments));
    });
}

Function.prototype.after = function (callback) {
    var that = this;
    return (function() {
        var result = that.apply(this, arguments);
        callback.apply(this, arguments);
        return (result);
    });
}

var both = test.before(function(a) {
    console.log('Before. Parameter = ', a);
}).after(function(a) {
    console.log('After. Parameter = ', a);
});
both(17);

看起来不错,可是这,我要是看个 react 源码,想拿到调用栈信息。函数调用岂不都要重写个遍?不靠谱,还不如去手画呢。

找啊找啊找,灵光一闪?AST。运行的时候不行,直接改代码不就完了。就这么干?于是就写了个 babel 插件,在代码里下点毒。babel-plugin-hound-trace 这个插件干啥呢?

// ...
module.exports = function (babel) {

    return {
        visitor: {
              // 在我们的代码里面遇到函数声明语句,函数表达式,箭头函数表达式的时候
            // 该插件会注入一些代码
            FunctionDeclaration: (path) => {
                // ...    
            },
            FunctionExpression: expressionHandle.bind(null, babel),
            ArrowFunctionExpression: expressionHandle.bind(null, babel)
        }
    };
};
// ...

比如源码里的函数如下:

function test(a, b, c) {
    const cj = 'cj';
}

下毒之后(经过这个插件处理之后):

function test(a, b, c) {
  let __traceParent__ = window.__traceParent__;
  let __traceOldParent__ = __traceParent__;

  if (window.__trace__) {
    if (!__traceParent__.next) {
      __traceParent__.next = [];
    }

    const current = {
      name: 'test',
      params: [\\"a\\", \\"b\\", \\"c\\"]
    };

    __traceParent__.next.push(current);

    window.__traceParent__ = current;
  }

  const cj = 'cj';
  window.__traceParent__ = __traceOldParent__;
}

注入的代码比较奇怪,因为实现的思路是借助 window 上的全局变量来做这个事情,所以看起来奇怪。(暂时还没想其它好方法)

注入了代码之后就简单了 ,可以看到代码里注入了 window.__trace__ 这个变量用于是否记录该函数,所以 hound-trace 包的代码就自然出来了:


let trace = false;

// 开始记录调用栈
function __NoTraceHook__start() {
    if (trace) { return }

    window.__trace__ = trace = true;

    window.__traceParent__ = {};
}

// 结束记录调用栈
function __NoTraceHook__end(callback) {
    if (!trace) { return }

    window.__trace__ = trace = false;

    callback && callback(window.__traceParent__);
}

export default {
    start: __NoTraceHook__start,
    end: __NoTraceHook__end
};

到这里,就差可视化渲染了,考虑到 UI 层是比较个性的,所以又拆了个包出来 hound-trace-ui 底层调用 hound-trace 的 API ,这样以后就能随意加皮肤了。

hound-trace-ui 其实很简单,就是在 __NoTraceHook__end 的回调里可以拿到调用栈的数据,然后怎么用这个数据就随意了,这个包里使用的是 mermaid 做可视化(因为简单):

// 调用 hound-trace 包代码
import houndTrace from '../../hound-trace/src/index';
// 渲染数据逻辑
import renderCallStack from './renderCallStack';

import './index.css';


function start() {
    houndTrace.start();
}

// 包装底层 API
function end() {
    houndTrace.end(callStack => {
        setTimeout(() => {
              // 调用 mermaid 包渲染数据
            renderCallStack(callStack);
        }, 14);
    });
}

export default {
    start,
    end,
    endAndRenderCallStack: end
};

好吧,就这样世界上又多了一个轮子。

感兴趣的可以去 star ,当然更希望大家给出奇淫技巧一起完善。—> hound-trace <--

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Easter79 Easter79
4年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
4年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Stella981 Stella981
4年前
JVM 字节码指令表
字节码助记符指令含义0x00nop什么都不做0x01aconst\_null将null推送至栈顶0x02iconst\_m1将int型1推送至栈顶0x03iconst\_0将int型0推送至栈顶0x04iconst\_1将int型1推送至栈顶0x05ic
Python进阶者 Python进阶者
2年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
PowerShell
PowerShell
Lv1
时间像奔腾澎湃的急湍,它一去无还,毫不留恋。
文章
5
粉丝
0
获赞
0