• Vite插件开发纪实:vite-plugin-monitor(下)


    前言

    上一篇介绍了Vite启动,HMR等时间的获取。

    但各阶段详细的耗时信息,只能通过debug的日志获取

    本文就实现一下debug日志的拦截

    插件效果预览

    图片

    --debug做了什么

    项目启动指令

    vite --debug
    

    在源码中搜索 --debug,可以在vite/packages/vite/bin/vite.js文件中定位到目标代码

    const debugIndex = process.argv.findIndex((arg) => /^(?:-d|--debug)$/.test(arg))
    
    if (debugIndex > 0) {
      let value = process.argv[debugIndex + 1]
      if (!value || value.startsWith('-')) {
        value = 'vite:*'
      } else {
        // support debugging multiple flags with comma-separated list
        value = value
          .split(',')
          .map((v) => `vite:${v}`)
          .join(',')
      }
      process.env.DEBUG = value
    }
    

    可以看到如果使用了--debug或者-d参数,process.env上挂载DEBUG变量标识开启了Debug

    定位打印日志方法

    debug下每条日志都是以vite:label开头,比如

    vite:load 1ms   [fs] /src/router/routes/index.ts
    

    全局搜一下vite:load就定位到了如下的代码,可以看到createDebugger是返回了一个可以打印日志的方法

    import {
      createDebugger,
    } from '../utils'
    const debugLoad = createDebugger('vite:load')
    const isDebug = !!process.env.DEBUG
    // ..code
    isDebug && debugLoad(`${timeFrom(loadStart)} [fs] ${prettyUrl}`)
    

    createDebugger 的源码如下,其返回一个自定函数,简单捋一下就能看出,负责打印的方法是log(msg,...args)

    import debug from 'debug'
    
    export function createDebugger(
      namespace: ViteDebugScope,
      options: DebuggerOptions = {}
    ): debug.Debugger['log'] {
      const log = debug(namespace)
      const { onlyWhenFocused } = options
      const focus =
        typeof onlyWhenFocused === 'string' ? onlyWhenFocused : namespace
      return (msg: string, ...args: any[]) => {
        if (filter && !msg.includes(filter)) {
          return
        }
        if (onlyWhenFocused && !DEBUG?.includes(focus)) {
          return
        }
        log(msg, ...args)
      }
    }
    

    其中log实例通过debug方法创建,但这个debug方法是一个第三方的库visionmedia/debug

    图片

    这个方库虽小,能在Vite中被用上想必也不简单,在线查看源码

    debug方法源码分析

    入口文件比较简单,这里直接去看./node.js中的逻辑

    if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) {
    	module.exports = require('./browser.js');
    } else {
    	module.exports = require('./node.js');
    }
    

    这部分代码一共只有264行,关键代码如下

    exports.log = log;
    
    function log(...args) {
    	return process.stderr.write(util.format(...args) + '
    ');
    }
    
    module.exports = require('./common')(exports);
    

    ./common.js中部分代码

    function setup(env) {
    	createDebug.debug = createDebug;
    	createDebug.default = createDebug;
    
    	function createDebug(namespace) {
    		function debug(...args) {
    			const self = debug;
    			const logFn = self.log || createDebug.log;
    			logFn.apply(self, args);
    		}
    		return debug;
    	}
    	return createDebug;
    }
    
    module.exports = setup;
    

    到此能够确定日志的打印都是通过process.stderr.write方法输出的内容

    这个方法的好处就是,输出内容不会直接换行

    那么我们在插件中重新定义一下这个方法就能拦截到打印的内容

    debug日志拦截实现

    定义插件入参

    interface PluginOptions {
        /**
         * 是否在终端中输出原来的日志
         */
        log?: boolean
        /**
         * 默认回调
         */
        monitor?: MonitorCallback
        /**
         * debug回调
         */
        debug?: DebugCallback
    }
    

    直接在调用插件方法的时候进行write方法重写,具体实现逻辑如下

    • 启用了--debug,传入了monitordebug方法才重新定义write方法
    • 将获取到的日志信息做简单解析,通过monitor方法传递给外部
    • 原始参数传递给外部的debug方法

    其中解析出的几个参数几个参数与原日志内容对应关系如下

    图片

    import type { Plugin } from 'vite';
    import type { PluginOptions } from './types';
    
    export default function Monitor(ops: PluginOptions = {}): Plugin {
      const { log, monitor, debug } = ops;
      // 如果debug方法且启动时添加了--debug参数
      if ((typeof debug === 'function' || typeof monitor === 'function') && process.env.DEBUG) {
        const { write } = process.stderr;
        Object.defineProperty(process.stderr, 'write', {
          get() {
            return function _write(...argv) {
    
              // log为true才执行原来的打印逻辑
              if (log && typeof argv[0] === 'string') {
                process.stdout.write(argv[0]);
              }
              const originStr = argv[0];
    
              // 解析日志的label与打印的时间信息
              const tag = (originStr.match(/vite:(.*?)s/) || [])[1];
              const time1 = (originStr.replace(/+d+ms/, '').match(/(d+)ms/) || [])[1];
              const time2 = (originStr.match(/+(d+)ms/) || [])[1];
              const time = +(time1 || 0) + +(time2 || 0);
    
    
              if (tag && monitor) {
                monitor(tag, time, {
                  time1: +(time1 || 0),
                  time2: +(time2 || 0),
                  originValue: originStr,
                });
              }
    
              if (debug) {
                debug(...argv);
              }
            };
          },
        });
      }
      return {
        name: 'vite-plugin-monitor',
        apply: 'serve',
        },
      };
    }
    

    到此拦截日志的feature就完成了,最初定下目标也已完成

    体验插件

    插件源码

    安装依赖

    yarn add vite-plugin-monitor --dev
    

    引入插件,修改vite.config.js文件

    import { defineConfig } from 'vite'
    import vitePluginMonitor from 'vite-plugin-monitor'
    
    export default defineConfig({
      plugins: [
        vitePluginMonitor({
          // log: false,
          monitor(label, time, originData) {
            const { time1, time2, originValue } = originVal
            console.log(originValue)
            console.log(label, time1, time2, `${time}ms`)
          },
          debug(str) {
            // 打印完整日志
            // process.stdout.write(str)
          },
        }),
      ],
    })
    

    启动指令中添加--debug

    vite --debug
    

    通过monitordebug方法中就能拿到原始的日志和简单处理后的日志,在此处加入自定义的埋点监控代码即可

    一点补充:logfalse的时,并且定义了monitordebug方法,那么原来的日志内容都将会被这两个方法拦截

    小结

    目前已经能够完全拦截到debug下的所有内容,但内容由于有彩色打印相关的字符,提取信息比较麻烦

    下一步将对日志的提取再做一些格式化,确保能够解析出完整的日志内容

    "你的指尖,拥有改变世界的力量! " 欢迎关注我的个人博客:https://sugarat.top
  • 相关阅读:
    jquery 图片播放器插件(支持自己设定时间,自己设定是否自动播放)
    ie6下bug集合(二)li之间空隙bug
    大小不固定的图片和多行文字的垂直水平居中
    解决IE6下 position的fixed定位问题
    C# 编写不安全代码
    委托和事件的使用
    如何删除win7桌面的库和家庭组图标
    gcc g++ 区别
    Java 访问注册表
    C# 通过反射类动态调用DLL方法
  • 原文地址:https://www.cnblogs.com/roseAT/p/15361014.html
Copyright © 2020-2023  润新知