背景
最近在webpack專案裡接入了Vite(dev mode),為開發提效。效果是真的猛。
專案啟動速度提升70%-80%,HMR直接碾壓webpack dev server
為了更加精準的計算收益,就需要將Vite啟動相關的指標進行上報(啟動時間,HMR,頁面載入等等時間)
為此就要通過開發外掛收集這些資訊,然後通過埋點上報sdk上報到資料分析的平臺
遇到的問題
通過查閱官方文件並未找到相關的鉤子直接獲取到這些指標
但在開發的時候新增 --debug
就能很詳細的看到所有資源的處理時間,HMR,詳細的啟動時間等等
{
"scripts": {
"dev": "vite --debug",
}
}
npm run dev
為此只能通過一些hack的手段獲取這些指標了,下面將展開詳細的介紹
期望
通過向目標工程引入外掛,通過特定的回掉函式即可獲取到debug
模式下反饋的各種資訊
準備工作
比較詳細的介紹一下開發步驟
初始化工程
建立外掛目錄
mkdir vite-plugin-monitor
cd vite-plugin-monitor
初始化pkg.json
npm init -y
安裝必要依賴
yarn add -D vite typescript @types/node rimraf
新增必要的兩個指令dev
,build
,配置入口檔案dist/index.js
{
"main": "dist/index.js",
"scripts": {
"dev": "tsc -w -p .",
"build": "rimraf dist && tsc -p ."
}
}
其中dev
環境下新增了-w(--watch)
引數,當檔案有變動時,以便實時的進行更新
rimraf
的作用是替代rm -rf
指令,且是跨平臺的,windows下同樣生效
外掛使用typescript
開發,更有助於外掛後續的維護
其中build
直接使用typescript
提供的預設tsc
指令,對ts直接進行轉換
根目錄建立 tsconfig.json
內容如下
{
"compilerOptions": {
"target": "es2015",
"moduleResolution": "node",
"strict": false,
"declaration": true,
"noUnusedLocals": true,
"esModuleInterop": true,
"outDir": "dist",
"module": "commonjs",
"lib": ["ESNext","DOM"],
"sourceMap": true,
},
"include": ["./src"]
}
在 src
目錄下進行開發,裡面存放我們的原始碼
目錄結構
最終目錄如下
├── package.json
├── src
| ├── index.ts # 外掛入口
| ├── types
| | └── index.ts # 型別定義
| └── utils
| └── index.ts # 工具方法
├── tsconfig.json
簡單外掛示例
根據外掛開發文件,在src/index.ts
檔案下編寫如下簡單的程式碼;
- name:標識外掛的名稱
- apply:標識外掛在哪個時期工作(serve|build),預設都會呼叫
- config:這個鉤子接收原始使用者配置(命令列選項指定的會與配置檔案合併)和一個描述配置環境的變數
import type { Plugin } from 'vite';
export default function Monitor(): Plugin {
return {
name: 'vite-plugin-monitor',
apply: 'serve',
config(userConfig, env) {
console.log(userConfig);
console.log(env)
// 可以做進一步的修改,會自動合入當前的配置
// return
},
};
}
一個列印Vite配置的外掛就搞定了,下面就是測試我們開發的外掛
本地測試外掛
首先是轉換我們的ts
=> js
,執行前面配置的指令yarn dev
,就會看見生成了一個dist目錄,裡面有轉換後的程式碼
接著執行npm link
在全域性生成一個軟連線,指向當前專案
npm link
在一個vite專案裡的執行npm link vite-plugin-monitor
(monitor根據實際情況替換),向目標專案加入此依賴
npm link vite-plugin-monitor
接著就可以在Vite專案的vite.config.js
配置檔案中加入我們的外掛了
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vitePluginMonitor from 'vite-plugin-monitor'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vitePluginMonitor()
]
})
接著通過配置的指令啟動vite
,就能看到我們外掛的列印的配置檔案內容了
由於是通過軟連線的方式引入的外掛,那麼在外掛工程裡的任意更改都會實時生效,也就避免了頻繁的執行yarn add file:localProjectDir
功能開發
有了前文的鋪墊內容,下面就是功能開發
獲取啟動耗時
專案啟動後會在終端中輸出ready in xxms
為此我們們使用Vs Code在原始碼中搜一下這個關鍵字
可以看到此部分程式碼在原始碼中如下
const info = server.config.logger.info
// @ts-ignore
if (global.__vite_start_time) {
// @ts-ignore
const startupDuration = performance.now() - global.__vite_start_time
info(`\n ${chalk.cyan(`ready in ${Math.ceil(startupDuration)}ms.`)}\n`)
}
這個performance.now()
等同於Date.now()
即當前時間,通過global.__vite_start_time
就能獲取到服務啟動時間
我們就從這個info
方法入手,給它重定義一下,通過configureServer
鉤子可以獲取到server例項
index.ts
import type { Plugin } from 'vite';
export default function Monitor(): Plugin {
const startTime = global.__vite_start_time
return {
name: 'vite-plugin-monitor',
apply: 'serve',
configureServer(server) {
const { info } = server.config.logger;
// 攔截info方法的呼叫
server.config.logger.info = function _info(str) {
// 呼叫原info方法
info.apply(this, arguments);
// 通過字串內容進行一個簡單的判斷
if (str.includes('ready in')) {
console.log('startupDuration', Date.now() - startTime)
}
};
},
};
}
啟動一個專案看看效果,成了。
HMR時間獲取
熱更新時,終端中會出現下面的日誌
同理原始碼裡搜一搜,能夠定位出如下內容
config.logger.info(
updates
.map(({ path }) => chalk.green(`hmr update `) + chalk.dim(path))
.join('\n'),
{ clear: true, timestamp: true }
)
暫以列印這個日誌的時間作為HMR開始的時間
let startTime = null
const { info } = server.config.logger;
server.config.logger.info = function _info(str) {
info.apply(this, arguments);
if (str.indexOf('hmr update') >= 0) {
startTime = Date.now()
}
};
觸發HMR時,客戶端會發出一個獲取資源的請求,請求攜帶了一個import引數,我們通過這個引數來標識這個特定的請求
http://localhost:8080/src/pages/home/index.vue?import&t=1632924377207
鉤子中的server例項包含middlewares
屬性可以向上新增自定義的中介軟體處理方法
- 通過URL例項解析
search
引數,然後判斷是否包含import&
- 重定義
end
方法,在資源傳回到客戶端後列印耗時
server.middlewares.use(async (req, res, next) => {
const { search } = new URL(req.url, `http://${req.headers.host}`);
if (
search.indexOf('import&') >= 0
) {
const { end } = res;
res.end = function _end() {
// 在資源返回後列印耗時
end.apply(this, arguments);
console.log(Date.now() - startTime)
};
}
next();
});
事實上通過--debug
啟動服務,能看到在HMR時會列印4個時間
目前方法僅僅得到了vite:hmr
部分的時間,與實際耗時還有一絲絲差異
小結
本篇主要介紹了monitor
外掛開發的背景,要解決的問題,目標以及開發外掛所需的一些列準備工作
功能開發介紹了啟動時間與HMR時間的獲取方式
更加詳細的資訊目前看來只能通過--debug
看到,下一步的計劃就是通過某種手段拿到debug下列印的日誌內容
由於時間關係,這部分hack還沒完成。準備假期抽時間實現一下。下一篇文章將詳細的介紹最終實現。