前言
上一篇介紹了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
,傳入了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',
},
};
}
到此攔截日誌的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
通過monitor
與debug
方法中就能拿到原始的日誌和簡單處理後的日誌,在此處加入自定義的埋點監控程式碼即可
一點補充: 在log
為false
的時,並且定義了monitor
或debug
方法,那麼原來的日誌內容都將會被這兩個方法攔截
小結
目前已經能夠完全攔截到debug下的所有內容,但內容由於有彩色列印相關的字元,提取資訊比較麻煩
下一步將對日誌的提取再做一些格式化,確保能夠解析出完整的日誌內容