前言
NPM 地址:vite-plugin-stats-html
源码地址:vite-plugin-stats-html
在本文中,咱们将解说如何从 0 开始编写一个 Vite 打包产物剖析插件工具, 在你的 vite 构建的我的项目里,执行打包命令后,能生成一个打包产物剖析报告 html 页面,这个网页会从多个角度展现打包产物的细节信息,从资源文件信息到第三方依赖等等,该报告蕴含以下内容(如下图):
- 我的项目门路,打包一共破费工夫,打包日期
- 打包总体积,
JS
文件体积,CSS
文件体积,以及饼状图显示的打包产物占比 - 打包出的文件数量,入口点指定的所有模块及其依赖项,产物中援用的第三方依赖的数量
- 同时咱们也提供了相似
WebpackBundleAnalyzer/ rollup-plugin-visualizer
的工具,能够帮忙咱们更好的理解产物内容,可视化的依赖的援用关系 - 表格展现打包的出的文件,蕴含文件类型,文件大小,外面援用第三方依赖数量
装置体验
// npm
npm install --save-dev vite-plugin-stats-html
// or via yarn
yarn add --dev vite-plugin-stats-html
在 vite 里配置 (vite.config.js)
// es
import {visualizer} from "vite-plugin-stats-html";
// or
// cjs
const {visualizer} = require("vite-plugin-stats-html");
module.exports = {plugins: [visualizer()],
};
通过在 vite 构建的我的项目中执行我的项目打包命令
npm run build
// or
yarn build
...
在打包完结后会在我的项目根目录下主动生成一个 stats.html
文件,你能够在浏览器关上查看生成的报告。
本文目录
(一)打包产物剖析的意义
(二)Vite 插件开发常识筹备
(三)Vite 插件性能开发合成
(四)打包公布
(五)Vite 插件总结
本插件开发次要参考以下插件:
(1) perfsee 性能剖析平台
(2) rollup-plugin-visualizer
一、打包产物剖析的意义
作为前端人,优化和性能始终是绕不开的话题,对前端打包产物进行剖析是十分必要的环节,前端应用程序的打包产物通常蕴含了代码、款式、图片、字体等资源,这些资源的大小和依赖关系都会对应用程序的性能和用户体验产生影响。
在 Webpack
构建的我的项目理论开发中,咱们能够应用例如 Webpack Bundle Analyzer
、Source Map Explorer
等工具来剖析打包产物,对于 vite 构建的我的项目中,罕用的也有 rollup-plugin-visualizer
等工具,生成一个交互式的依赖关系图表,用于展现应用程序的依赖关系和模块大小,帮忙咱们可视化剖析打包产物,从而理解应用程序的资源占用状况和依赖关系。
通过打包产物剖析,咱们能够理解以下内容:
- 打包产物的大小:理解应用程序的资源占用状况,从而优化应用程序的性能和用户体验。
- 打包产物的依赖关系:理解应用程序的模块化水平,从而使得应用程序更加易于保护和扩大。
- 打包产物的性能:理解应用程序的性能瓶颈,从而优化应用程序的性能和用户体验。
- 打包产物的品质:理解应用程序的代码标准、可读性、可维护性等方面,从而进步应用程序的品质和可靠性。
二、Vite 插件开发常识筹备
在编写插件之前,咱们必须先具备 Vite 插件开发前的常识筹备。
2.1 Vite 构建机制
Vite
应用 Rollup
作为生产环境的构建工具。Rollup
会对我的项目中的代码进行动态剖析,找出所有的模块依赖关系。这个过程中,Vite 会解决各种资源文件,如 CSS
、图片等,并将它们转换为适合的模块。所以正如 Vite 官网上所形容,咱们能够失去论断,vite 插件,它具备兼容 rollup 插件的钩子和领有本人的独有的钩子。
2.2 Vite 插件初探
为了编写这个 Vite
插件,咱们须要理解 Vite
插件的编写形式。Vite
插件是一个 JavaScript
模块,它导出一个函数,这个函数承受一个参数,这个参数是一个 Vite 插件 API 对象。通过查阅 rollup
的插件开发文档,咱们发现generateBundle
钩子是用于在生成最终包的阶段进行额定的解决。该钩子能够获取到以下信息:
outputOptions
:输入选项对象,蕴含了输入文件的门路、格局等信息。bundle
:打包生成的代码对象,蕴含了多个模块的信息,能够用来进一步剖析和解决代码。isWrite
: 一个布尔值,用于判断以后是否是写入文件的操作,若为false
则示意只是在生成代码而不是写入文件。
上面是一个简略的 Vite 插件示例:
export default function visualizer() {
return {
name: 'visualizer',
async generateBundle(outputOptions, bundle) {fs.writeFileSync(path.join("./", 'bundle.txt'), bundle);
},
};
}
在这个示例中,咱们编写了一个名为 visualizer
的插件,让它在生成打包产物时输入 bundle
的内容,并把内容通过 Node.js
中的一个文件系统模块,用于同步地将数据写入文件 bundle.txt
输入到我的项目根目录下。
2.3 bundle 参数到底蕴含哪些货色
咱们能够通过 Vite
创立一个我的项目,在页面中轻易写点货色,比方援用 ElementUI
, 将这个插件增加到Vite
的配置文件中,来启用它,看看能生成具体 bundle.txt
什么内容。
{
"assets/index-e8828b4c.css": {
"fileName": "assets/index-e8828b4c.css",
"name": "index.css",
"needsCodeReference": false,
"source": "@charset \"UTF-8\";:root..."
"type": "asset"
},
"assets/index-60dd1a96.js": {"exports": [],
"facadeModuleId": "E:/cao/my-test-2023/index.html",
"isDynamicEntry": false,
"isEntry": true,
"isImplicitEntry": false,
"moduleIds": ["\u0000vite/modulepreload-polyfill", ...],
"name": "index",
"type": "chunk",
"dynamicImports": [],
"fileName": "assets/index-60dd1a96.js",
"implicitlyLoadedBefore": [],
"importedBindings": {},
"imports": [],
"modules": {},
"referencedFiles": [],
"viteMetadata": {"importedAssets": {},
"importedCss": {}},
"code": "(function(){const ...",
"map": null
}
}
初步看看代码,咱们能够看到它蕴含了所有模块信息的对象,它蕴含了多个属性和办法,用于形容和解决打包生成的代码。这些都是前面咱们对插件开发及其重要的 属性和办法。属性和办法比方比拟重要的:
bundle[file]
:一个模块的形容对象,其中file
示意模块的文件名。该对象蕴含了模块的代码、依赖关系和其余信息。Object.keys(bundle)
:获取所有打包的文件名数组。bundle[file].code
:获取某个模块的代码字符串。bundle[file].isEntry
:一个布尔值,示意该模块是否是入口模块。bundle[file].facadeModuleId
:一个字符串,示意该模块的 ID。bundle[file].modules
:一个字符串数组,示意该模块依赖的所有模块的 ID。
咱们开发打包产物剖析插件,实质上就是对 bundle 信息的解剖和组合成咱们须要的信息
接下来咱们正儿八经写插件的性能了
三、Vite 插件性能开发合成
咱们通过 Vite 创立一个 create-vite-extra 疾速创立一个 library 模板我的项目
而后进行革新一下目录创立 plugin 目录里编写咱们的外围代码性能
├── plugin // 服务端源代码
│ ├── buildTree.js // 将依赖转换树
│ ├── createHtml.js // 导出的产物剖析报告 html 模板
│ ├── index.js // 插件外围代码
│ ├── mapper.js // 模块映射关系
├── package.json // package.json
├── .gitignore // git 疏忽项
└── vite.config.js // vite 配置文件
3.1 打包开始工夫和打包完结工夫统计
3.1.1 打包开始工夫
通过查阅文档,咱们能够晓得 rollup 有个 buildStart 钩子,index.js
这里咱们记录打包开始工夫
function visualizer(options = {}) {
let startTime;
return {
name: "visualizer",
buildStart() {startTime = Date.now();
},
3.1.2 计算打包时长
联合打包完结工夫,计算打包时长
time: (Date.now() - startTime) / 1000 + "s",
3.2 统计各文件类型和大小
咱们通过遍历 bundle 去记录各种文件大小和总体积
for (const [bundleId, bundle] of Object.entries(outputBundle)) {let type = path.extname(bundle.fileName).slice(1);
let size = bundle?.code?.length || bundle?.source?.length;
switch (type) {
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;
}
3.4 统计第三方依赖
统计文件中的第三方依赖数量
const dependencyCount = Object.keys(bundle.modules ?? []).length;
3.5 把打包文件生成依赖树须要的数据结构
因为咱们最初咱们要通过 echarts treemap
去展现可视化的打包产物树,所以咱们这里去遍历bundle.modules
,并从新拼装modules
数据,并转换为构建正确的依赖关系。
这里次要就是参考rollup-plugin-visualizer
里的源码生成依赖树
const modules = await Promise.all(Object.entries(bundle.modules).map(([id, { renderedLength, code}]) =>
ModuleLengths({id, renderedLength, code})
)
);
tree = buildTree(bundleId, modules, mapper);
3.6 把数据嵌入咱们模板页面
咱们通过建设一个字符串 Html 页面模板, 这里咱们不便解决数据和缩小款式应用,通过 CDN 的形式咱们能够很容易地应用 \`ElementUI 和 Vue 的形式写咱们的 Html 页面(如下),咱们开始编写咱们的页面 UI
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- import CSS -->
<script src="https://unpkg.com/vue@2"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.2.2/dist/echarts.min.js"></script>
</head>
<body>
<div id="app">
</div>
</body>
<!-- import Vue before Element -->
<script>
new Vue({
el: '#app',
data: function() {return {}
}
})
</script>
</html>
咱们这里次要用到了 elementUI
的表格,以及 echarts
的饼状图和 treeMap 图,例如饼状图的拼装数据,在咱们的 DOM 中展现饼状图
setPieChart(){
// 基于筹备好的 dom,初始化 echarts 实例
var myChart = echarts.init(document.getElementById('pie'))
// 绘制图表
myChart.setOption({
title: {text: 'Bundle Overview',},
tooltip: {trigger: 'item',},
legend: {
orient: 'vertical',
left: 'left',
top: '30%',
},
series: [
{
name: 'Bundle Overview',
type: 'pie',
radius: '50%',
data: [{ value: ${allData.bundleObj.jsSize}, name: 'JS' },
{value: ${allData.bundleObj.cssSize}, name: 'CSS' },
{value: ${allData.bundleObj.imageSize}, name: 'Image' },
{value: ${allData.bundleObj.htmlSize}, name: 'Font' },
{value: ${allData.bundleObj.fontSize}, name: 'Html' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
})
},
最初面咱们通过 NOde 的文件能力把页面写进去
const html = createHtml(outputBundlestats);
await fs.writeFileSync(path.join("./", outputFile), html);
这样咱们的插件就开发完了 \~
四、打包公布
4.1 引入插件的形式
咱们回顾一下,咱们前端在我的项目中援用一个三方模块的时候通常是 ESM,CJS,UMD 等,支流的:
4.1.1 ESM
ESM 是 ESModlule,是 ECMASCript 本人的模块体系,是 Javascript 提出的实现一个规范模块零碎的计划。是编译的时候运行。如咱们在 vite.config.js
应用 ESM 引入 vite 如下:
import {defineConfig} from "vite";
4.1.2 CJS
cjs 是 commonds 的缩写,被加载的时候运行,具备缓存。在第一次被加载时,会残缺运行整个文件并输入一个对象,拷贝 (浅拷贝)在内存中。下次加载文件时,间接从内存中取值。次要用于服务端。
导出
const obj = {a: 1);
module.exports = obj;
引入
const obj = require('"/test.js");
4.2 初探 vite 构建
因为整个插件我的项目咱们是 \`Vite 来搭建的,所以咱们从 vite 官网上能够看到,只有配置 build.lib,就能够打包成咱们须要的库
咱们在 vite.config.js 配置
import {defineConfig} from "vite";
export default defineConfig({
build: {
target: "modules",
lib: {
entry: "./plugin/index.js",
name: "vite-plugin-stats-html",
fileName: "vite-plugin-stats-html",
formats: ["es", "cjs", "umd"],
},
},
});
咱们在 package.json 配置
"type": "module",
"files": ["dist"],
"main": "./dist/vite-plugin-stats-html.cjs",
"module": "./dist/vite-plugin-stats-html.js",
"exports": {
".": {
"import": "./dist/vite-plugin-stats-html.js",
"require": "./dist/vite-plugin-stats-html.cjs"
}
},
执行打包命令,即可生成打包后的 dist 文件
\
编写 README,最初公布到 npm,这个步骤,在这里就不做更多的讲述了,咱们能够尝试通过 装置到咱们我的项目中测试一下
四、Vite 插件总结
从 0 开始编写一个 Vite 打包产物剖析插件须要理解 Vite 的打包机制、Vite 插件的编写形式、两头文件的读取形式以及打包产物的分析方法。当然你也能够通过本人的想法把更多打包产物维度进行剖析,开发成插件,更好地理解咱们的代码的性能和品质,从而优化咱们的应用程序。当然写这个插件比拟仓促,后续也能够进行拓展,比方 treeMap 依赖关系图,目前 UI 还是不太好看,还有一些兼容性的问题可能会呈现。
github 我的项目地址:vite-plugin-stats-html