如何开发一个 Vite 插件?以 vite-plugin-monitor 为例
背景
最近在webpack项目里接入了Vite(dev mode),为开发提效。效果是真的猛。
项目启动速度提升70%-80%,HMR直接碾压webpack dev server
为了更加精准的计算收益,就需要将Vite启动相关的指标进行上报(启动时间,HMR,页面加载等等时间)
为此就要通过开发插件收集这些信息,然后通过埋点上报sdk上报到数据分析的平台
遇到的问题
通过查阅官方文档[1]并未找到相关的钩子直接获取到这些指标
但在开发的时候添加 --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在源码[2]中搜一下这个关键字
可以看到此部分代码在源码中如下
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还没完成。准备假期抽时间实现一下。下一篇文章将详细的介绍最终实现。
仓库源码[3]
参考资料
官方文档: https://vitejs.dev/guide/api-plugin.html
[2]源码: https://github1s.com/vitejs/vite
[3]仓库源码: https://github.com/ATQQ/vite-plugin-monitor