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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
日志服务 SLS,月写入数据量 50GB 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 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) + '\n');
}
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下的所有内容,但内容由于有彩色打印相关的字符,提取信息比较麻烦


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

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
前端开发 JavaScript 容器
在 vite+vue 中使用@originjs/vite-plugin-federation 模块联邦
【10月更文挑战第25天】模块联邦是一种强大的技术,它允许将不同的微前端模块组合在一起,形成一个统一的应用。在 vite+vue 项目中,使用@originjs/vite-plugin-federation 模块联邦可以实现高效的模块共享和组合。通过本文的介绍,相信你已经了解了如何在 vite+vue 项目中使用@originjs/vite-plugin-federation 模块联邦,包括安装、配置和使用等方面。在实际开发中,你可以根据自己的需求和项目的特点,灵活地使用模块联邦,提高项目的可维护性和扩展性。
|
1月前
|
JavaScript 前端开发
将 Babel 插件应用于实际项目中
【10月更文挑战第25天】如果在应用插件过程中出现问题,可以检查 Babel 配置是否正确、插件的依赖是否安装完整、构建工具的集成是否正确等,逐步排查和解决问题。通过以上步骤,就可以将 Babel 插件成功应用到实际项目中,实现特定的代码转换和功能增强。
|
3月前
vite.config.js中vite.defineConfig is not defined以及创建最新版本的vite项目
本文讨论了在配置Vite项目时遇到的`vite.defineConfig is not defined`错误,这通常是由于缺少必要的导入语句导致的。文章还涉及了如何创建最新版本的Vite项目以及如何处理`configEnv is not defined`的问题。
204 3
vite.config.js中vite.defineConfig is not defined以及创建最新版本的vite项目
|
2月前
|
资源调度 JavaScript 前端开发
如何实现一个类似 vite 的脚手架并发布 npm
本文介绍了如何实现一个类似 Vite 的脚手架工具。通过详细解析和实践,文章分享了从零开始构建脚手架的过程,包括技术选型、开发步骤及发布 NPM 包的完整流程。最终目标是让用户能够通过 `yarn create electron-prokit myapp` 快速搭建 Electron 项目。项目源码可在 GitHub 上获取。
36 5
|
5月前
Vite 项目中如何去集成 Mock 环境 (插件:vite-plugin-mock)
Vite 项目中如何去集成 Mock 环境 (插件:vite-plugin-mock)
197 0
|
7月前
Vue3+Vite+Js项目搭建之二:vite.config.js 构建
Vue3+Vite+Js项目搭建之二:vite.config.js 构建
155 1
|
JavaScript 小程序
VUE3(三十五)vite构建的项目配置使用.env文件
VUE3(三十五)vite构建的项目配置使用.env文件如标题所示:我要在vue3项目使用.env文件。 先介绍一下项目背景,项目使用VUE3.2 + vite2.9 + typescript搭建。 我基本断定,vue3使用.env文件的方法可能和vue2使用.env文件的方法可能是不同,关于vue2项目如何使用.env文件,请移步《VUE2(七)VUE配置env文件使用》
420 1
|
JavaScript
为老的vueCli项目添加vite支持
为老的vueCli项目添加vite支持
145 0
为老的vueCli项目添加vite支持
|
资源调度 JavaScript 前端开发
nuxt2-storybook-vite:环境搭建、基础使用 / nuxt项目组件库
nuxt2-storybook-vite:环境搭建、基础使用 / nuxt项目组件库
344 0
|
缓存 前端开发 JavaScript
前端工具Vite的出现解决了什么?
在 ESM 出现之前,Javascript 是没有一个标准的模块方案。 比如说 `CJS` 是用于 Node 服务端的模块化方案,`AMD` 是用于浏览器的模块化方案。为了解决这个模块共用性问题,出现了 `UMD` 用于兼容这两种模块规范。 鉴于上面共用性问题,实际开发中配置的打包方式,采用的还是 UMD 模式。因为这样可以避免打包而产生的规范问题,并且在 ESM 不能使用的情况下也会选择 UMD。
142 0
前端工具Vite的出现解决了什么?