粥里有勺糖

vuePress-theme-reco 粥里有勺糖    2018 - 2023
粥里有勺糖 粥里有勺糖

Choose mode

  • dark
  • auto
  • light
关于我
备战春秋
  • 心得总结
  • 校招考点汇总
  • 面经汇总
  • 复习自查
技术笔记
  • 技术教程
  • 模板工程
  • 源码学习
  • 技术概念
  • 个人作品
  • 学习笔记
计算机基础
  • 算法与数据结构
  • 操作系统
  • 计算机网络
  • 设计模式
  • 剑指offer
大前端
  • javascript
  • vue
  • html
  • css
  • 🌏浏览器专题
  • Web性能优化
  • regexp
  • node
面试
  • 问解
  • javascript
  • css
  • 手撕代码
  • 性能优化
  • 综合问题
  • 面经汇总
  • 小程序
手撕代码
  • 数据结构与算法
  • javascript
  • css
个人站点
  • GitHub (opens new window)
  • 博客园 (opens new window)
  • 掘金 (opens new window)
线上作品
  • 轻取(文件收集) (opens new window)
  • 个人图床 (opens new window)
  • 考勤小程序 (opens new window)
  • 时光恋人 (opens new window)
  • 在线简历生成 (opens new window)
留言板
Github (opens new window)
author-avatar

粥里有勺糖

285

文章

40

标签

关于我
备战春秋
  • 心得总结
  • 校招考点汇总
  • 面经汇总
  • 复习自查
技术笔记
  • 技术教程
  • 模板工程
  • 源码学习
  • 技术概念
  • 个人作品
  • 学习笔记
计算机基础
  • 算法与数据结构
  • 操作系统
  • 计算机网络
  • 设计模式
  • 剑指offer
大前端
  • javascript
  • vue
  • html
  • css
  • 🌏浏览器专题
  • Web性能优化
  • regexp
  • node
面试
  • 问解
  • javascript
  • css
  • 手撕代码
  • 性能优化
  • 综合问题
  • 面经汇总
  • 小程序
手撕代码
  • 数据结构与算法
  • javascript
  • css
个人站点
  • GitHub (opens new window)
  • 博客园 (opens new window)
  • 掘金 (opens new window)
线上作品
  • 轻取(文件收集) (opens new window)
  • 个人图床 (opens new window)
  • 考勤小程序 (opens new window)
  • 时光恋人 (opens new window)
  • 在线简历生成 (opens new window)
留言板
Github (opens new window)
  • devlearn

    • 开发教程
    • 实践:利用ArrayBuffer实现预览指定目录下的所有文件的内容
    • 在linux-deepin上使用deepin-wine5完美运行腾讯会议/QQ/微信等此类应用
    • eslint插件开发教程
    • ServerLess之云函数实践-天气API
    • 移动端阻止弹窗下层页面被滑动
    • 小技巧:for of中获取index
    • Git常用的一些基本操作
    • 向页面注入js实现为图片和文字元素添加透明蒙层
    • 实践:使用jsencrypt配合axios实现非对称加密传输的数据
    • 封装dotenv库实现类似Vite加载环境变量的行为
    • 30行代码实现合并指定目录下的所有文件的内容
    • 马上中秋了,把鼠标指针变为小玉兔
    • Node中require与fs.readFile读取JSON文件的对比
    • 使用免费的七牛云OSS(10G)搭建个人的在线图床
    • 分享封装的一些七牛云OSS操作方法
    • 本地配置SSH免密远程登录服务器
    • 工具方法汇总
    • 腾讯云Serverless实践-Node.js服务部署
    • 腾讯云Serverless实践-静态网站托管
    • 为什么'\x1B'.length === 1?\x与\u知识延伸
    • Vite插件开发纪实:vite-plugin-monitor(上)
    • Vite插件开发纪实:vite-plugin-monitor(中)
    • Vite插件开发纪实:vite-plugin-monitor(下)
    • 解决Vite-React项目中js使用jsx语法报错的问题
    • webpack 项目接入Vite的通用方案介绍
    • webpack 项目接入Vite的通用方案介绍-草稿
    • 优雅的处理挂载window上的函数可能不存在的情况
    • Mac上抓包秒通关羊了个羊

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

vuePress-theme-reco 粥里有勺糖    2018 - 2023

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

粥里有勺糖 2021-10-01 技术笔记技术教程

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

# 前言

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

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

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

# 插件效果预览

图片

# --debug做了什么

项目启动指令

vite --debug
1

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

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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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

# 定位打印日志方法

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

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

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

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

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)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

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

图片

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

# 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');
}
1
2
3
4
5

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

exports.log = log;

function log(...args) {
	return process.stderr.write(util.format(...args) + '\n');
}

module.exports = require('./common')(exports);
1
2
3
4
5
6
7

./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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

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

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

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

# debug日志拦截实现

定义插件入参

interface PluginOptions {
    /**
     * 是否在终端中输出原来的日志
     */
    log?: boolean
    /**
     * 默认回调
     */
    monitor?: MonitorCallback
    /**
     * debug回调
     */
    debug?: DebugCallback
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

  • 启用了--debug,传入了monitor或debug方法才重新定义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',
    },
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

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

# 体验插件

插件源码 (opens new window)

安装依赖

yarn add vite-plugin-monitor --dev
1

引入插件,修改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)
      },
    }),
  ],
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

启动指令中添加--debug

vite --debug
1

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

一点补充: 在log为false的时,并且定义了monitor或debug方法,那么原来的日志内容都将会被这两个方法拦截

# 小结

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

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

Edit this page (opens new window)
Last Updated: 2022/5/15 12:46:34