版本:0.1.3 | 协议:MIT | 依赖:Vite ^5.0.0 || ^6.0.0 || ^7.0.0


写在前面

v0.1.3 是 @meng-xi/vite-plugin 的又一个重要版本。这个版本的核心变化:

  1. 第十个插件 bundleAnalyzer — 构建产物体积分析,支持 JSON/HTML 报告、gzip 计算、阈值告警和构建对比
  2. 两个新工具模块@common/compress(压缩算法)和 @common/path(路径处理)
  3. 工具层增强@common/format@common/fs 新增通用函数,消除插件间重复逻辑

如果你正在使用 v0.1.2,升级到 v0.1.3 无需任何修改——所有变更向后兼容。如果你是第一次接触这个库,这篇文章将从构建产物体积分析这个真实痛点出发,展示第十个插件如何帮你量化构建产物,以及新增的通用工具如何让插件开发更高效。


一、v0.1.2 → v0.1.3 迁移指南

1.1 Breaking Changes

。v0.1.3 完全向后兼容 v0.1.2,所有现有配置无需修改。

1.2 新增能力一览

能力说明是否需要额外配置
bundleAnalyzer 插件构建产物体积分析(JSON/HTML 报告 + 阈值告警)需要新增配置
@common/compress 模块calculateGzipSize gzip 压缩大小计算按需导入
@common/path 模块isNodeModule 模块来源判断按需导入
@common/format 增强escapeHtmlAttrformatFileSizegetExtension按需导入
@common/fs 增强scanDirectorywriteJsonReport + 2 个类型按需导入

1.3 版本号升级

{
	"devDependencies": {
		"@meng-xi/vite-plugin": "^0.1.3"
	}
}

二、从问题出发:bundleAnalyzer 解决了什么

痛点:构建产物体积不可知,优化无据可依

场景:你的 Vite 项目构建完成后,dist/ 目录下有几十个 JS chunk、CSS 文件和静态资源。你隐约感觉产物体积偏大,但不知道:

  • 哪个 chunk 最大?里面包含哪些模块?
  • 哪些模块来自 node_modules?它们占了多少体积?
  • gzip 压缩后实际传输体积是多少?
  • 某个依赖升级后,产物体积是增大了还是减小了?
  • 是否有 chunk 超过了合理的体积阈值?

你可能用 webpack-bundle-analyzer 做过分析,但那是 Webpack 生态的。Vite 生态缺乏一个功能完整的构建产物分析工具。

解法:bundleAnalyzer

import { bundleAnalyzer } from '@meng-xi/vite-plugin'

export default defineConfig({
	plugins: [
		bundleAnalyzer({
			outputFormat: 'both',
			sizeThreshold: 200,
			topModules: 30,
			gzipSize: true,
			compareWith: 'bundle-analysis-prev.json',
			defaultChartType: 'treemap'
		})
	]
})

工作原理

Vite 构建完成 (writeBundle, order: 'post')
    ↓
扫描 dist/ 目录,收集文件信息
    ↓
分析每个 chunk 的模块组成
    ↓
计算原始大小 + gzip 压缩大小(level: 9)
    ↓
生成 Top N 大模块排行
    ↓
按扩展名统计文件类型分布
    ↓
检查体积阈值,生成告警
    ↓
可选:与上次构建报告对比
    ↓
生成 JSON 报告和/或 HTML 可视化报告
可选:自动打开浏览器
输出分析摘要日志

三种报告格式

格式输出文件适用场景
jsonbundle-analysis.json程序化处理、CI/CD 集成、自定义分析工具
htmlbundle-analysis.html人工查看、可视化图表、团队分享
both同时生成两种两者兼顾

HTML 报告可视化

HTML 报告内置三种交互式图表视图:

视图特点适用场景
treemap矩形树状图,面积表示体积占比快速定位大模块
sunburst旭日图,层级嵌套展示模块关系理解模块依赖层次
list列表视图,按体积排序精确查看每个模块的数值

阈值告警机制

bundleAnalyzer({
	sizeThreshold: 100 // KB
})
  • 超过 sizeThreshold 的 chunk → 🟡 普通告警
  • 超过 2 × sizeThreshold 的 chunk → 🔴 严重告警

终端输出示例

⚠️ [@meng-xi/vite-plugin:bundle-analyzer] 发现 3 个体积告警:
  🟡 chunk "vendor" 超过阈值: 156.3KB > 100KB
  🔴 chunk "app" 严重超过阈值: 312.5KB > 100KB (2x)
  🟡 chunk "utils" 超过阈值: 128.7KB > 100KB

构建对比:量化每次变更的影响

bundleAnalyzer({
	compareWith: 'bundle-analysis-prev.json'
})

启用后,插件会加载上次构建的报告,逐模块对比体积变化:

趋势含义
increased体积增大
decreased体积减小
unchanged体积不变
added本次新增的模块
removed本次移除的模块

终端输出示例

ℹ️ [@meng-xi/vite-plugin:bundle-analyzer] 构建对比: 3 个增大, 2 个减小, 1 个新增, 0 个移除

分析摘要日志

构建完成后自动输出关键指标:

✅ [@meng-xi/vite-plugin:bundle-analyzer] 产物分析完成: 12 个 chunk, 总体积: 1.19MB (gzip: 384.21KB), 分析耗时: 156ms
  入口: 2 | 代码块: 7 | 资源: 3
体积 Top 5 模块:
  1. 234.57KB (node_modules) node_modules/lodash/lodash.js
  2. 156.23KB (source) src/components/Dashboard.vue
  3. 89.45KB (node_modules) node_modules/axios/index.js
  4. 67.89KB (source) src/utils/api.ts
  5. 45.12KB (source) src/views/Home.vue

三、十大内置插件一览

插件解决的问题一句话描述引入版本
buildProgress构建无进度反馈终端可视化构建进度条0.0.6
bundleAnalyzer构建产物体积不可知体积分析 + JSON/HTML 报告 + 阈值告警 + 对比0.1.3
compressAssets构建产物体积大gzip / brotli 压缩 + 报告0.1.2
copyFile静态资源全量复制智能文件复制(增量 + 并发)0.0.6
faviconManager图标管理繁琐图标注入 + 文件复制一体化0.0.9
generateRouteruni-app 路由手动维护pages.json 自动生成路由配置0.0.6
generateVersion版本号管理缺失多格式版本号生成与注入0.0.6
htmlInjectHTML 内容注入缺乏统一方案构建时 HTML 内容注入,支持条件/模板/安全过滤0.1.1
loadingManager白屏体验差全局 Loading 状态管理0.0.9
versionUpdateChecker用户无法感知版本更新运行时版本更新检测与提示0.1.0

四、通用工具层完善:消除重复逻辑

v0.1.3 的一个重要改进是将插件间的重复逻辑提取为通用工具模块。这些工具不仅被内置插件使用,也可以被你直接导入使用。

4.1 为什么要提取?

在 v0.1.2 中,compressAssetsbundleAnalyzer 都需要:

  • 计算文件的 gzip 压缩大小 → 各自实现了一遍
  • 递归扫描目录、收集文件信息 → 各自实现了一遍
  • 将字节数格式化为可读的文件大小 → 各自实现了一遍
  • 获取文件扩展名 → 各自实现了一遍
  • 判断模块是否来自 node_modules → 各自实现了一遍
  • 将分析结果写入 JSON 文件 → 各自实现了一遍

v0.1.3 将这些重复逻辑统一提取到 @common 工具层,确保单一实现、多处复用

4.2 @common/compress — 压缩算法(新增)

import { calculateGzipSize } from '@meng-xi/vite-plugin/common/compress'

const buffer = Buffer.from('some content to compress')
const gzipSize = await calculateGzipSize(buffer)
console.log(`gzip 压缩后: ${gzipSize} 字节`)

const stringData = 'another long string...'
const size = await calculateGzipSize(stringData)
函数参数返回说明
calculateGzipSizedata: Buffer | stringPromise<number>计算 gzip 压缩后大小,使用 level: 9 压缩

设计决策:使用最高压缩级别(level: 9)而非默认的 level: 6,因为分析场景追求的是估算网络传输的最小体积,而非压缩速度。

4.3 @common/path — 路径处理(新增)

import { isNodeModule } from '@meng-xi/vite-plugin/common/path'

isNodeModule('node_modules/lodash/index.js') // true
isNodeModule('src/utils/helper.ts') // false
isNodeModule('\0some-virtual-module') // true  — Rollup 内部虚拟模块
isNodeModule('virtual:import-meta-env') // true  — 虚拟模块前缀
函数参数返回说明
isNodeModulemoduleId: stringboolean判断模块是否来自 node_modules,含虚拟模块检测

检测规则

  1. 路径包含 node_modules → 第三方依赖
  2. \0 开头 → Rollup 内部虚拟模块(如 \0module-id
  3. virtual: 开头 → 虚拟模块前缀(如 virtual:import-meta-env

4.4 @common/format(增强)

新增三个工具函数:

import { escapeHtmlAttr, formatFileSize, getExtension } from '@meng-xi/vite-plugin/common/format'

escapeHtmlAttr('hello "world"') // 'hello &quot;world&quot;'
escapeHtmlAttr('<script>') // '&lt;script&gt;'

formatFileSize(512) // '512B'
formatFileSize(1536) // '1.5KB'
formatFileSize(2461726) // '2.35MB'

getExtension('dist/app.js') // '.js'
getExtension('dist/style.CSS') // '.css'
函数参数返回说明
escapeHtmlAttrstr: stringstring转义 HTML 属性值中的特殊字符,防止 XSS 注入
formatFileSizebytes: numberstring字节数格式化为可读文件大小(xB / x.xKB / x.xxMB)
getExtensionfilePath: stringstring获取文件扩展名,返回小写(含点号,如 .js

formatFileSize 转换规则

字节范围输出格式示例
< 1KBxB512B
< 1MBx.xKB1.5KB
≥ 1MBx.xxMB2.35MB

4.5 @common/fs(增强)

新增两个工具函数和两个类型:

import { scanDirectory, writeJsonReport } from '@meng-xi/vite-plugin/common/fs'
import type { ScannedFile, ScanDirectoryOptions } from '@meng-xi/vite-plugin/common/fs'

const jsFiles = await scanDirectory('dist', { includeExtensions: ['.js'] })

const allFiles = await scanDirectory('dist', {
	excludePatterns: ['node_modules', '.map'],
	filter: (filePath, ext, size) => size > 1024
})

await writeJsonReport('dist/report.json', { timestamp: Date.now(), stats: [] })
await writeJsonReport('dist/report.json', data, 4)
函数 / 类型说明
scanDirectory递归扫描目录,支持按扩展名、路径模式和自定义过滤函数过滤
writeJsonReport将数据序列化为 JSON 并写入文件,默认缩进 2 空格
ScannedFile扫描文件信息接口(filePathsizeextension
ScanDirectoryOptions目录扫描选项接口(includeExtensionsexcludePatternsfilter

scanDirectory 过滤优先级

1. excludePatterns — 排除的路径模式(支持通配符前缀和子串匹配)
2. includeExtensions — 包含的扩展名(列表非空时生效)
3. filter — 自定义过滤函数(最终过滤)

五、详细 API 文档

5.1 通用配置(BasePluginOptions)

所有插件均继承自 BasePluginOptions,拥有以下通用配置:

选项类型默认值说明
enabledbooleantrue是否启用插件
verbosebooleantrue是否启用日志输出
errorStrategy'throw' | 'log' | 'ignore''throw'错误处理策略

5.2 bundleAnalyzer — 构建产物体积分析

在 Vite 构建(writeBundle)完成后自动扫描输出目录,分析构建产物的体积分布。

选项类型默认值说明
outputFormat'json' | 'html' | 'both''json'报告输出格式
outputFilestring'bundle-analysis'报告输出文件名(不含扩展名)
openAnalyzerbooleanfalse是否在生成 HTML 报告后自动打开浏览器
sizeThresholdnumber100体积告警阈值(KB)
topModulesnumber20Top N 大模块排行数量
compareWithstring | nullnull用于对比的历史报告路径
gzipSizebooleantrue是否计算 gzip 大小
excludeNodeModulesbooleanfalse是否排除 node_modules 中的模块
excludePatternsstring[][]需要排除的文件路径模式列表
includeExtensionsstring[][]需要包含的文件扩展名列表,为空则包含所有
defaultChartType'treemap' | 'sunburst' | 'list''treemap'HTML 报告中图表的默认展示形式

导出类型BundleAnalyzerOptionsBundleAnalysisResultBundleOutputFormatChunkStatsModuleStatsFileTypeDistributionSizeWarningComparisonDiff

BundleAnalysisResult

属性类型描述
timestampstring分析时间戳(ISO 格式)
totalSizenumber构建产物总大小(字节)
totalGzipSizenumbergzip 总大小(字节)
chunksChunkStats[]chunk 统计列表
topModulesModuleStats[]Top N 大模块
fileTypeDistributionFileTypeDistribution[]文件类型分布统计
warningsSizeWarning[]体积阈值告警列表
comparisonDiffsComparisonDiff[]构建对比差异列表
analysisTimenumber分析耗时(毫秒)

ChunkStats

属性类型描述
namestringchunk 名称
sizenumber原始大小(字节)
gzipSizenumbergzip 压缩大小
modulesModuleStats[]包含的模块列表
type'entry' | 'chunk' | 'asset'chunk 类型
fileCountnumber包含的文件数量

ModuleStats

属性类型描述
idstring模块标识符
sizenumber模块原始大小(字节)
gzipSizenumber模块 gzip 压缩后大小(字节)
chunksstring[]所属 chunk 名称列表
importsstring[]依赖模块 ID 列表
isEntryboolean是否为入口模块
isNodeModuleboolean是否来自 node_modules

FileTypeDistribution

属性类型描述
extensionstring文件扩展名(如 .js
countnumber该类型的文件数量
totalSizenumber该类型的总大小(字节)
percentagenumber该类型的总体积占比(0-100)

SizeWarning

属性类型描述
level'module' | 'chunk'告警级别
namestring告警目标名称
sizeKBnumber实际大小(KB)
thresholdKBnumber阈值大小(KB)
messagestring告警消息

ComparisonDiff

属性类型描述
namestring模块/chunk 名称
previousSizenumber上次构建大小
currentSizenumber本次构建大小
diffnumber体积变化量
diffPercentagenumber变化百分比
trend'increased' | 'decreased' | 'unchanged' | 'added' | 'removed'变化趋势

5.3 compressAssets — 构建产物压缩

选项类型默认值说明
algorithm'gzip' | 'brotli' | 'both''gzip'压缩算法
thresholdnumber1024最小压缩阈值(字节)
deleteOriginalFilebooleanfalse压缩后是否删除原始文件
includeExtensionsstring[]['.js', '.css', '.html', '.svg', '.json', '.xml', '.txt']包含的扩展名
excludeExtensionsstring[][]排除的扩展名(优先级高于 include)
excludePathsstring[][]排除的路径前缀
compressionLevelnumber9gzip 压缩级别(1-9)
brotliQualitynumber11brotli 质量参数(1-11)
reportOutputstring | false'compress-report.json'报告输出路径,false 不生成
parallelLimitnumber10并发压缩的最大文件数

导出类型CompressAssetsOptionsCompressAlgorithmCompressStatsCompressSummary

5.4 buildProgress — 构建进度条

选项类型默认值说明
widthnumber30进度条宽度(字符数)
format'bar' | 'spinner' | 'minimal''bar'显示格式
completeCharstring'█'已完成部分填充字符
incompleteCharstring'░'未完成部分填充字符
clearOnCompletebooleantrue完成后是否清除进度条
showModuleNamebooleantrue是否显示当前模块名称
themeProgressTheme-自定义颜色主题

导出类型BuildProgressOptionsProgressFormatBuildPhaseProgressTheme

5.5 copyFile — 文件复制

选项类型默认值说明
sourceDirstring-源目录路径(必填)
targetDirstring-目标目录路径(必填)
overwritebooleantrue是否覆盖同名文件
recursivebooleantrue是否递归复制子目录
incrementalbooleantrue是否启用增量复制

导出类型CopyFileOptions

5.6 faviconManager — 图标管理

选项类型默认值说明
basestring-图标文件基础路径
urlstring-图标完整 URL
linkstring-自定义完整 link 标签
iconsHtmlTagDescriptor[]-自定义图标数组
copyOptionsobject-图标文件复制配置

导出类型FaviconManagerOptions

5.7 generateRouter — 路由配置生成

选项类型默认值说明
pagesJsonPathstring'src/pages.json'pages.json 文件路径
outputPathstring'src/router.config.ts'输出文件路径
outputFormat'ts' | 'js''ts'输出文件格式
nameStrategy'path' | 'camelCase' | 'pascalCase' | 'custom''camelCase'路由名称策略
customNameGenerator(path: string) => string-自定义名称生成函数
includeSubPackagesbooleantrue是否包含子包路由
watchbooleantrue是否监听 pages.json 变化
metaMappingRecord<string, string>{...}页面 style 到 meta 的映射
exportTypesbooleantrue是否导出类型定义
preserveRouteChangesbooleantrue是否保留用户修改

导出类型GenerateRouterOptionsRouteConfigRouteMetaUniAppPagesJsonUniAppPageConfigUniAppTabBarConfigOutputFormatNameStrategy

5.8 generateVersion — 版本号生成

选项类型默认值说明
format'timestamp' | 'date' | 'datetime' | 'semver' | 'hash' | 'custom''timestamp'版本号格式
customFormatstring-自定义格式模板
semverBasestring'1.0.0'语义化版本基础值
outputType'file' | 'define' | 'both''file'输出类型
outputFilestring'version.json'输出文件路径
defineNamestring'__APP_VERSION__'注入的全局变量名
hashLengthnumber8哈希长度(1-32)
prefixstring-版本号前缀
suffixstring-版本号后缀
extraRecord<string, any>-附加信息

导出类型GenerateVersionOptions

5.9 htmlInject — HTML 内容注入

选项类型默认值说明
rulesHtmlInjectRule[]-注入规则(必填)
targetFilestring'index.html'目标文件匹配
securitySecurityConfig-安全过滤配置
templateVarsRecord<string, string>-全局模板变量
logInjectionbooleanfalse是否记录注入日志

导出类型HtmlInjectOptionsHtmlInjectRuleInjectPositionInjectConditionSecurityConfigSelectorMatch

5.10 loadingManager — Loading 状态管理

选项类型默认值说明
defaultVisiblebooleantrue默认是否可见
autoHideOnstring'DOMContentLoaded'自动隐藏时机
spinnerTypestring'circle'Spinner 类型
globalNamestring'__LOADING_MANAGER__'全局变量名

导出类型LoadingManagerOptions

5.11 versionUpdateChecker — 版本更新检测

选项类型默认值说明
versionSource'define' | 'file' | 'auto''auto'版本来源
checkIntervalnumber300000检查间隔(毫秒)
promptStyle'modal' | 'banner' | 'toast''modal'提示样式
checkOnVisibilityChangebooleantrue标签页切回时是否检查
enableInDevbooleanfalse开发环境是否启用

导出类型VersionUpdateCheckerOptions


六、bundleAnalyzer + compressAssets 联合实战

这两个插件天然互补:bundleAnalyzer 帮你发现体积问题,compressAssets 帮你解决传输问题。

6.1 完整配置

import { defineConfig } from 'vite'
import { bundleAnalyzer, compressAssets, buildProgress, generateVersion } from '@meng-xi/vite-plugin'

export default defineConfig({
	plugins: [
		buildProgress({ format: 'bar' }),
		generateVersion({ format: 'datetime', outputType: 'both' }),

		bundleAnalyzer({
			outputFormat: 'both',
			outputFile: 'bundle-analysis',
			sizeThreshold: 200,
			topModules: 30,
			gzipSize: true,
			compareWith: 'bundle-analysis-prev.json',
			defaultChartType: 'treemap'
		}),

		compressAssets({
			algorithm: 'both',
			threshold: 1024,
			reportOutput: 'compress-report.json',
			parallelLimit: 10
		})
	]
})

6.2 CI/CD 集成:体积回归检测

import { bundleAnalyzer } from '@meng-xi/vite-plugin'

export default defineConfig({
	plugins: [
		bundleAnalyzer({
			outputFormat: 'json',
			sizeThreshold: 100,
			gzipSize: true,
			compareWith: 'bundle-analysis-prev.json'
		})
	]
})

在 CI 脚本中:

# 构建
npm run build

# 检查是否有体积告警(从 JSON 报告中提取 warnings 数组)
node -e "
  const report = require('./dist/bundle-analysis.json');
  if (report.warnings.length > 0) {
    console.error('体积告警:', report.warnings.map(w => w.message).join('; '));
    process.exit(1);
  }
"

6.3 uni-app 条件启用

import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import { bundleAnalyzer, compressAssets } from './uni_modules/vite-plugin/js_sdk/index.mjs'

export default defineConfig({
	plugins: [
		uni(),

		bundleAnalyzer({
			outputFormat: 'json',
			sizeThreshold: 100,
			gzipSize: true,
			enabled: process.env.UNI_PLATFORM === 'h5' && process.env.VITE_USER_NODE_ENV === 'production'
		}),

		compressAssets({
			algorithm: 'both',
			threshold: 1024,
			reportOutput: 'compress-report.json',
			enabled: process.env.UNI_PLATFORM === 'h5' && process.env.VITE_USER_NODE_ENV === 'production'
		})
	]
})

七、通用工具模块速查

7.1 导入方式

// 全量导入
import { calculateGzipSize, isNodeModule, formatFileSize, scanDirectory } from '@meng-xi/vite-plugin/common'

// 按模块导入
import { calculateGzipSize } from '@meng-xi/vite-plugin/common/compress'
import { isNodeModule } from '@meng-xi/vite-plugin/common/path'
import { escapeHtmlAttr, formatFileSize, getExtension } from '@meng-xi/vite-plugin/common/format'
import { scanDirectory, writeJsonReport } from '@meng-xi/vite-plugin/common/fs'

7.2 完整子路径导出映射

入口路径导出内容
@meng-xi/vite-plugin全量导出(框架 + 插件 + 工具)
@meng-xi/vite-plugin/plugins10 个内置插件工厂函数 + 类型
@meng-xi/vite-plugin/plugins/build-progressbuildProgress + 类型
@meng-xi/vite-plugin/plugins/bundle-analyzerbundleAnalyzer + 类型
@meng-xi/vite-plugin/plugins/compress-assetscompressAssets + 类型
@meng-xi/vite-plugin/plugins/copy-filecopyFile + 类型
@meng-xi/vite-plugin/plugins/favicon-managerfaviconManager + 类型
@meng-xi/vite-plugin/plugins/generate-routergenerateRouter + 类型
@meng-xi/vite-plugin/plugins/generate-versiongenerateVersion + 类型
@meng-xi/vite-plugin/plugins/html-injecthtmlInject + 类型
@meng-xi/vite-plugin/plugins/loading-managerloadingManager + 类型
@meng-xi/vite-plugin/plugins/version-update-checkerversionUpdateChecker + 类型
@meng-xi/vite-plugin/factoryBasePlugin、createPluginFactory、PluginWithInstance
@meng-xi/vite-plugin/loggerLogger
@meng-xi/vite-plugin/common全部公共工具
@meng-xi/vite-plugin/common/compresscalculateGzipSize
@meng-xi/vite-plugin/common/format日期格式化、模板解析、命名转换、文件大小格式化
@meng-xi/vite-plugin/common/fs文件读写、复制、目录扫描、JSON 报告、并发控制
@meng-xi/vite-plugin/common/htmlHTML 注入工具
@meng-xi/vite-plugin/common/objectdeepMerge
@meng-xi/vite-plugin/common/pathisNodeModule
@meng-xi/vite-plugin/common/script回调包装、XSS 检测、标识符验证
@meng-xi/vite-plugin/common/validationValidator + 验证工具函数

八、自定义插件开发:复用通用工具

v0.1.3 新增的通用工具模块不仅服务于内置插件,你也可以在自定义插件中直接使用,避免重复造轮子。

8.1 示例:自定义产物分析插件

import { BasePlugin, createPluginFactory } from '@meng-xi/vite-plugin/factory'
import { scanDirectory, writeJsonReport } from '@meng-xi/vite-plugin/common/fs'
import { calculateGzipSize } from '@meng-xi/vite-plugin/common/compress'
import { formatFileSize, getExtension } from '@meng-xi/vite-plugin/common/format'
import { isNodeModule } from '@meng-xi/vite-plugin/common/path'
import type { Plugin } from 'vite'

interface AssetStatsOptions {
	outputFile?: string
	excludePatterns?: string[]
}

class AssetStatsPlugin extends BasePlugin<AssetStatsOptions> {
	protected getPluginName() {
		return 'asset-stats'
	}

	protected getDefaultOptions() {
		return { outputFile: 'asset-stats.json', excludePatterns: [] }
	}

	protected addPluginHooks(plugin: Plugin): void {
		plugin.writeBundle = {
			order: 'post',
			handler: async () => {
				const outDir = this.viteConfig?.build.outDir
				if (!outDir) return

				const files = await scanDirectory(outDir, {
					excludePatterns: this.options.excludePatterns
				})

				const stats = []
				for (const file of files) {
					const gzipSize = await calculateGzipSize(file.filePath)
					stats.push({
						path: file.filePath,
						extension: getExtension(file.filePath),
						size: formatFileSize(file.size),
						gzipSize: formatFileSize(gzipSize),
						isNodeModule: isNodeModule(file.filePath)
					})
				}

				await writeJsonReport(this.options.outputFile!, { stats })
				this.logger.success(`产物统计完成: ${files.length} 个文件`)
			}
		}
	}
}

export const assetStats = createPluginFactory(AssetStatsPlugin)

8.2 你自动获得的能力

工具来源避免的重复实现
scanDirectory@common/fs递归目录扫描 + 文件过滤
calculateGzipSize@common/compressgzip 压缩流处理
formatFileSize@common/format字节数格式化逻辑
getExtension@common/format扩展名提取 + 小写转换
isNodeModule@common/pathnode_modules + 虚拟模块检测
writeJsonReport@common/fsJSON 序列化 + 文件写入 + 错误处理

九、架构演进:从 v0.1.2 到 v0.1.3

9.1 v0.1.2 的架构问题

v0.1.2 的九个插件中,compressAssets 内部实现了 gzip 压缩计算、目录扫描、JSON 报告生成等逻辑,这些逻辑在其他场景中也需要使用,但无法复用。

9.2 v0.1.3 的架构改进

v0.1.2                                    v0.1.3
┌──────────────────────────┐              ┌──────────────────────────────────┐
│ 9 个内置插件              │              │ 10 个内置插件                     │
│ + bundleAnalyzer(新增)  │     ──→     │ + bundleAnalyzer                  │
│ 各自实现压缩/扫描/格式化  │              │ 复用 @common 工具层               │
├──────────────────────────┤              ├──────────────────────────────────┤
│ 框架层                    │              │ 框架层                            │
│ BasePlugin · Validator    │              │ BasePlugin · Validator · Logger   │
├──────────────────────────┤              ├──────────────────────────────────┤
│ 工具层(6 个模块)        │              │ 工具层(8 个模块)                │
│ fs · format · html        │              │ + compress(新增)                │
│ object · script           │              │ + path(新增)                    │
│ validation                │              │ fs · format · html · object       │
│                           │              │ script · validation               │
└──────────────────────────┘              └──────────────────────────────────┘

9.3 工具层完整矩阵

模块函数/类型使用者
common/compresscalculateGzipSizebundleAnalyzercompressAssets
common/pathisNodeModulebundleAnalyzer
common/formatformatFileSizegetExtensionescapeHtmlAttrbundleAnalyzercompressAssets
common/fsscanDirectorywriteJsonReportbundleAnalyzercompressAssets
common/htmlinjectBeforeTaginjectHeadAndBodyfaviconManagerloadingManagerversionUpdateChecker
common/objectdeepMergeBasePlugin
common/scriptmakeCallbackcontainsScriptTagvalidateIdentifierNameversionUpdateCheckerloadingManager
common/validationValidator + 验证工具函数所有插件

十、路线图

短期

  • bundleAnalyzer 支持 Webpack 兼容模式
  • 构建产物趋势图(多版本体积变化折线图)
  • 插件配置预设(web-appuni-appssr

中期

  • 插件间事件总线
  • 可视化配置生成器
  • 社区插件市场

长期

成为 Vite 插件开发的标准框架——定义最佳实践,让社区以统一方式构建、分享和组合插件。


本文基于 @meng-xi/vite-plugin@0.1.3 版本撰写,所有代码示例均来自实际源码。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐