🚀🚀打包性能分析工具——实现一个收集打包结果信息的Vite插件

655 阅读3分钟

介绍

Vite插件是在Vite构建过程中运行的函数,本质上就是一个功能函数,导出后在Vite plugin中进行调用即可。同时,Vite在整个构建过程中为插件提供了很多钩子,我们可以通过这些钩子来进一步控制我们的插件功能。有了上述准备工作,实现一个Vite插件将十分简单!

钩子名称作用
config修改 Vite 配置。可以同步或异步返回一个对象,或者返回一个 Promise
configResolved在 Vite 配置解析后调用,可以在这里进一步修改配置
configureServer配置开发服务器
transformIndexHtml转换 index.html 内容
resolveId自定义模块路径解析
load自定义加载模块内容
transform转换模块内容
buildStart构建开始时调用
buildEnd构建结束时调用
closeBundle打包完成时调用
handleHotUpdate处理热更新

开始

创建一个js文件

在根目录下,创建一个js文件,这里我将其命名为visualizer.js,在文件内默认导出一个函数,这个函数就是插件的核心逻辑函数。

这里我主要收集了文件大小、打包耗时等变量。

变量定义

let startTime // 打包开始时间
let jsSize = 0, cssSize = 0, imageSize = 0, fontSize = 0, htmlSize = 0 // 打包文件大小
let transformCount = 0 // 文件解析数量
const fileSizeStack = [] // 文件体积信息栈

变量定义好后,函数体内返回一个对象,这个对象包括插件名称和钩子函数,我们所有逻辑都在各个钩子函数内进行实现。

统计打包时间

统计打包时间很简单,只需要在buildStart中记录一下当前时间,这个是打包开始时的时间点,然后在closeBundle中再一次获取当前时间,这是打包结束的时间点,二者相减,再除以1000(毫秒转秒),就能得到整体的打包耗时

buildStart() {
	startTime = Date.now()
},
closeBundle() {
	const endTime = Date.now()
	console.log(`🚀打包耗时:${(endTime - startTime) / 1000}s`);
},

统计打包后的文件大小

打包文件大小统计应该写在generateBundle钩子中,这个钩子在bundle生成后触发,该函数接收两个参数:outputOptions输出产物配置项,outputBundle输出bundle信息。 代码实现

generateBundle(outputOptions, outputBundle) {
			for (const [bundleId, bundle] of Object.entries(outputBundle)) {
				const extname = bundle.fileName.slice(bundle.fileName.lastIndexOf('.'))
				const size = bundle?.code?.length || bundle?.source?.length
				switch (extname) {
					case '.js':
						jsSize += size
						break
					case '.css':
						cssSize += size
						break
					case ".jpg":
					case ".jpeg":
					case ".png":
					case ".gif":
					case ".svg":
						imageSize += size
						break
					case '.html':
						htmlSize += size
						break
					case ".woff":
					case ".woff2":
					case ".ttf":
					case ".otf":
						fontSize += size;
						break;
					default:
						break;
				}
			}
		},

通过switch语句,将不同类型的文件的size做分类收集处理,最后汇总进行展示。

解析过程中,如果我想拿到每个文件的大小和当前已解析文件数量该如何做呢,其实可以通过transform钩子函数进行获取,该函数接收code和id两个参数,分别代表解析的文件的源代码和文件的完整路径(文件指纹)。

这里做文件类型的匹配,node_modules/下的文件忽略。

transform(code, id) {
			transformCount++
			const reg = /\.(vue|ts|js|jsx|tsx|css|scss||sass|styl|less)$/gi
			const nodeModuleReg = /node_modules/gi;
			if (!nodeModuleReg.test(id) && reg.test(id)) {
				fileSizeStack.push({
					id,
					size: code.length
				})
				console.log(id + ': ' + (code.length / 1024).toFixed(2) + 'KB');
				console.log('已解析文件数:', transformCount);
			}
		}

结果展示

loseBundle中展示最终的所有结果,其中,在上一步transform中,我们构建了一个fileSizeStack,用于存放文件的路径和对应的源码体积,用于统计最终的文件体积进行从大到小排序。不过当前fileSizeStack需要进行加工才能完成最终展示。

实现排序工具函数

function sort10Size(fileSizeStack) {
	return (
		fileSizeStack.sort((a, b) => b.size - a.size).slice(0, 10)
	)
}

实现格式转换工具函数

function formatFileSize(fileSizeStack) {
	return (
		fileSizeStack.map(item => `${item.id}: ${(item.size / 1024).toFixed(2)}KB\n`)
	)
}

通过以上两个函数串行调用就可以实现上述需求目标了。

console.log(`📦文件体积排名前十:\n${formatFileSize(sort10Size(fileSizeStack))}`);

不过我还希望最终打包结果在控制台输出的文本可以高亮成其他颜色,以和过程日志进行区分。

引入kolorist工具库,该库主要用于控制node控制台的日志样式。

import { green } from 'kolorist'

// 修改console.log的颜色
export const successColor = function (s) {
	return green(s)
}

现在,closeBundle就可以完整输出以上整理的打包信息了。

closeBundle() {
			const endTime = Date.now()
			console.log(successColor(`📦文件体积排名前十:\n${formatFileSize(sort10Size(fileSizeStack))}`));
			console.log(successColor(`📦打包后js文件大小:${(jsSize / 1024).toFixed(2)}KB, css文件大小:${(cssSize / 1024).toFixed(2)}KB, 图片文件大小:${(imageSize / 1024).toFixed(2)}KB, 字体文件大小:${(fontSize / 1024).toFixed(2)}KB, html文件大小:${(htmlSize / 1024).toFixed(2)}KB`))
			console.log(successColor(`🚀打包耗时:${(endTime - startTime) / 1000}s`));
},

使用

vite.config.ts中引入visualizer文件,并在plugins数组中调用默认导出的函数。

import visualizer from './visualizer';
export default defineConfig({
    // ......
    plugins: [visualizer()]
})

运行 npm run build 执行打包命令,等待打包进程完成。

image.png

看到控制台已经可以正常输出打包文件的一些信息了,之后每次进行本地build都可以进行打包文件信息的展示,可以用于分析打包性能和bundle大小,进而方便进行针对性的打包性能优化。