前言

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 yarnyarn add --dev vite-plugin-stats-html

在 vite里配置 (vite.config.js)

// esimport { visualizer } from "vite-plugin-stats-html";// or// cjsconst { 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 AnalyzerSource Map Explorer等工具来剖析打包产物,对于vite构建的我的项目中,罕用的也有rollup-plugin-visualizer等工具,生成一个交互式的依赖关系图表,用于展现应用程序的依赖关系和模块大小,帮忙咱们可视化剖析打包产物,从而理解应用程序的资源占用状况和依赖关系。

通过打包产物剖析,咱们能够理解以下内容:

  1. 打包产物的大小:理解应用程序的资源占用状况,从而优化应用程序的性能和用户体验。
  2. 打包产物的依赖关系:理解应用程序的模块化水平,从而使得应用程序更加易于保护和扩大。
  3. 打包产物的性能:理解应用程序的性能瓶颈,从而优化应用程序的性能和用户体验。
  4. 打包产物的品质:理解应用程序的代码标准、可读性、可维护性等方面,从而进步应用程序的品质和可靠性。

二、Vite插件开发常识筹备

在编写插件之前,咱们必须先具备Vite插件开发前的常识筹备。

2.1 Vite构建机制

Vite 应用 Rollup 作为生产环境的构建工具。Rollup 会对我的项目中的代码进行动态剖析,找出所有的模块依赖关系。这个过程中,Vite 会解决各种资源文件,如 CSS、图片等,并将它们转换为适合的模块。所以正如Vite官网上所形容,咱们能够失去论断,vite插件,它具备兼容rollup插件的钩子和领有本人的独有的钩子。

2.2 Vite插件初探

为了编写这个Vite插件,咱们须要理解Vite插件的编写形式。Vite插件是一个JavaScript模块,它导出一个函数,这个函数承受一个参数,这个参数是一个Vite插件API对象。通过查阅rollup的插件开发文档,咱们发现generateBundle 钩子是用于在生成最终包的阶段进行额定的解决。该钩子能够获取到以下信息:

  1. outputOptions:输入选项对象,蕴含了输入文件的门路、格局等信息。
  2. bundle:打包生成的代码对象,蕴含了多个模块的信息,能够用来进一步剖析和解决代码。
  3. 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    }}                                                

初步看看代码,咱们能够看到它蕴含了所有模块信息的对象,它蕴含了多个属性和办法,用于形容和解决打包生成的代码。这些都是前面咱们对插件开发及其重要的 属性和办法。属性和办法比方比拟重要的:

  1. bundle[file]:一个模块的形容对象,其中 file 示意模块的文件名。该对象蕴含了模块的代码、依赖关系和其余信息。
  2. Object.keys(bundle):获取所有打包的文件名数组。
  3. bundle[file].code:获取某个模块的代码字符串。
  4. bundle[file].isEntry:一个布尔值,示意该模块是否是入口模块。
  5. bundle[file].facadeModuleId:一个字符串,示意该模块的 ID。
  6. 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