Vite插件開發紀實:vite-plugin-monitor(上)


背景

最近在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還沒完成。准備假期抽時間實現一下。下一篇文章將詳細的介紹最終實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM