@meng-xi/vite-plugin 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README-en.md +522 -610
  2. package/README.md +496 -584
  3. package/dist/common/compress/index.cjs +1 -0
  4. package/dist/common/compress/index.d.cts +23 -0
  5. package/dist/common/compress/index.d.mts +23 -0
  6. package/dist/common/compress/index.d.ts +23 -0
  7. package/dist/common/compress/index.mjs +1 -0
  8. package/dist/common/format/index.cjs +1 -1
  9. package/dist/common/format/index.d.cts +33 -1
  10. package/dist/common/format/index.d.mts +33 -1
  11. package/dist/common/format/index.d.ts +33 -1
  12. package/dist/common/format/index.mjs +1 -1
  13. package/dist/common/fs/index.cjs +1 -1
  14. package/dist/common/fs/index.d.cts +70 -2
  15. package/dist/common/fs/index.d.mts +70 -2
  16. package/dist/common/fs/index.d.ts +70 -2
  17. package/dist/common/fs/index.mjs +1 -1
  18. package/dist/common/index.cjs +1 -1
  19. package/dist/common/index.d.cts +4 -2
  20. package/dist/common/index.d.mts +4 -2
  21. package/dist/common/index.d.ts +4 -2
  22. package/dist/common/index.mjs +1 -1
  23. package/dist/common/path/index.cjs +1 -0
  24. package/dist/common/path/index.d.cts +22 -0
  25. package/dist/common/path/index.d.mts +22 -0
  26. package/dist/common/path/index.d.ts +22 -0
  27. package/dist/common/path/index.mjs +1 -0
  28. package/dist/index.cjs +1 -1
  29. package/dist/index.d.cts +6 -2
  30. package/dist/index.d.mts +6 -2
  31. package/dist/index.d.ts +6 -2
  32. package/dist/index.mjs +1 -1
  33. package/dist/plugins/bundleAnalyzer/index.cjs +235 -0
  34. package/dist/plugins/bundleAnalyzer/index.d.cts +215 -0
  35. package/dist/plugins/bundleAnalyzer/index.d.mts +215 -0
  36. package/dist/plugins/bundleAnalyzer/index.d.ts +215 -0
  37. package/dist/plugins/bundleAnalyzer/index.mjs +235 -0
  38. package/dist/plugins/compressAssets/index.cjs +1 -0
  39. package/dist/plugins/compressAssets/index.d.cts +132 -0
  40. package/dist/plugins/compressAssets/index.d.mts +132 -0
  41. package/dist/plugins/compressAssets/index.d.ts +132 -0
  42. package/dist/plugins/compressAssets/index.mjs +1 -0
  43. package/dist/plugins/generateRouter/index.cjs +4 -4
  44. package/dist/plugins/generateRouter/index.mjs +1 -1
  45. package/dist/plugins/generateVersion/index.cjs +1 -1
  46. package/dist/plugins/generateVersion/index.mjs +1 -1
  47. package/dist/plugins/htmlInject/index.cjs +7 -7
  48. package/dist/plugins/index.cjs +1 -1
  49. package/dist/plugins/index.d.cts +2 -0
  50. package/dist/plugins/index.d.mts +2 -0
  51. package/dist/plugins/index.d.ts +2 -0
  52. package/dist/plugins/index.mjs +1 -1
  53. package/dist/plugins/loadingManager/index.cjs +1 -1
  54. package/dist/plugins/loadingManager/index.mjs +1 -1
  55. package/package.json +24 -2
@@ -0,0 +1,235 @@
1
+ import{createPluginFactory as x,BasePlugin as b}from"../../factory/index.mjs";import m from"node:fs";import f from"node:path";import{calculateGzipSize as v}from"../../common/compress/index.mjs";import{isNodeModule as z}from"../../common/path/index.mjs";import{scanDirectory as S,writeJsonReport as w,writeFileContent as $}from"../../common/fs/index.mjs";import{formatFileSize as c}from"../../common/format/index.mjs";import"../../logger/index.mjs";import"../../common/object/index.mjs";import"../../shared/vite-plugin.DcExl6jd.mjs";import"node:zlib";import"node:stream/promises";import"node:stream";import"fs";import"path";import"crypto";async function F(r,s={}){return S(r,{includeExtensions:s.includeExtensions,excludePatterns:s.excludePatterns})}function A(r){const s=r.reduce((a,n)=>a+n.size,0),e=new Map;for(const a of r){const n=a.extension||"(no ext)",t=e.get(n)||{count:0,totalSize:0};t.count++,t.totalSize+=a.size,e.set(n,t)}return Array.from(e.entries()).map(([a,{count:n,totalSize:t}])=>({extension:a,count:n,totalSize:t,percentage:s>0?Number((t/s*100).toFixed(1)):0})).sort((a,n)=>n.totalSize-a.totalSize)}function M(r,s){const e=[],a=s*1024;for(const n of r){n.size>a&&e.push({level:"chunk",name:n.name,sizeKB:Number((n.size/1024).toFixed(1)),thresholdKB:s,message:`Chunk "${n.name}" \u8D85\u8FC7\u9608\u503C: ${(n.size/1024).toFixed(1)}KB > ${s}KB`});for(const t of n.modules)t.size>a&&e.push({level:"module",name:t.id,sizeKB:Number((t.size/1024).toFixed(1)),thresholdKB:s,message:`\u6A21\u5757 "${t.id}" \u8D85\u8FC7\u9608\u503C: ${(t.size/1024).toFixed(1)}KB > ${s}KB`})}return e}function C(r,s,e){const a=[];for(const n of r)for(const t of n.modules)e&&t.isNodeModule||a.push(t);return a.sort((n,t)=>t.size-n.size).slice(0,s)}async function k(r,s,e={}){const{gzipSize:a=!0,excludeNodeModules:n=!1}=e,t=[];for(const o of s){const d=f.relative(r,o.filePath),l=d.replace(/\\/g,"/"),i=o.extension;let u="chunk";i===".html"?u="entry":[".js",".mjs",".cjs",".css",".html"].includes(i)||(u="asset");let h=0;if(a)try{const y=await m.promises.readFile(o.filePath);h=await v(y)}catch{h=0}const p=z(d),g={id:l,size:o.size,gzipSize:h,chunks:[l],imports:[],isEntry:u==="entry",isNodeModule:p};n&&p||t.push({name:l,size:o.size,gzipSize:h,modules:[g],type:u,fileCount:1})}return t.sort((o,d)=>d.size-o.size)}async function B(r,s){const e=Date.now(),a=await F(r,{includeExtensions:s.includeExtensions,excludePatterns:s.excludePatterns}),n=await k(r,a,{gzipSize:s.gzipSize,excludeNodeModules:s.excludeNodeModules}),t=s.excludeNodeModules?a.filter(p=>!z(f.relative(r,p.filePath))):a,o=A(t),d=C(n,s.topModules,s.excludeNodeModules),l=M(n,s.sizeThreshold),i=n.reduce((p,g)=>p+g.size,0),u=n.reduce((p,g)=>p+g.gzipSize,0),h=Date.now()-e;return{timestamp:new Date().toISOString(),totalSize:i,totalGzipSize:u,chunks:n,topModules:d,fileTypeDistribution:o,warnings:l,comparisonDiffs:[],analysisTime:h}}async function D(r){try{const s=f.isAbsolute(r)?r:f.resolve(process.cwd(),r);if(!await m.promises.access(s,m.constants.F_OK).then(()=>!0).catch(()=>!1))return null;const e=await m.promises.readFile(s,"utf-8");return JSON.parse(e)}catch{return null}}function E(r,s){const e=[],a=new Map;for(const t of s.chunks)a.set(t.name,t.size);const n=new Map;for(const t of r.chunks)n.set(t.name,t.size);for(const[t,o]of n){const d=a.get(t);if(d===void 0)e.push({name:t,previousSize:-1,currentSize:o,diff:o,diffPercentage:100,trend:"added"});else{const l=o-d,i=d>0?Number((l/d*100).toFixed(1)):0;e.push({name:t,previousSize:d,currentSize:o,diff:l,diffPercentage:i,trend:l>0?"increased":l<0?"decreased":"unchanged"})}}for(const[t,o]of a)n.has(t)||e.push({name:t,previousSize:o,currentSize:-1,diff:-o,diffPercentage:-100,trend:"removed"});return e.sort((t,o)=>Math.abs(o.diff)-Math.abs(t.diff))}async function T(r,s,e){const a=f.join(r,`${s}.json`),n={timestamp:e.timestamp,summary:{totalSize:e.totalSize,totalGzipSize:e.totalGzipSize,totalSizeFormatted:c(e.totalSize),totalGzipSizeFormatted:c(e.totalGzipSize),chunkCount:e.chunks.length,warningCount:e.warnings.length,analysisTime:e.analysisTime},chunks:e.chunks.map(t=>({name:t.name,size:t.size,sizeFormatted:c(t.size),gzipSize:t.gzipSize,gzipSizeFormatted:c(t.gzipSize),type:t.type,fileCount:t.fileCount,modules:t.modules.map(o=>({id:o.id,size:o.size,sizeFormatted:c(o.size),gzipSize:o.gzipSize,gzipSizeFormatted:c(o.gzipSize),isEntry:o.isEntry,isNodeModule:o.isNodeModule,imports:o.imports}))})),topModules:e.topModules.map(t=>({id:t.id,size:t.size,sizeFormatted:c(t.size),gzipSize:t.gzipSize,gzipSizeFormatted:c(t.gzipSize),isNodeModule:t.isNodeModule})),fileTypeDistribution:e.fileTypeDistribution,warnings:e.warnings,comparisonDiffs:e.comparisonDiffs};return await w(a,n),a}async function R(r,s,e,a={}){const n=f.join(r,`${s}.html`),t=a.defaultChartType||"treemap",o=e.chunks.map(p=>({name:p.name,size:p.size,gzipSize:p.gzipSize,type:p.type})),d=e.fileTypeDistribution,l=e.warnings,i=e.comparisonDiffs,u=e.topModules.slice(0,10),h=P(e,o,d,l,i,u,t);return await $(n,h),n}function P(r,s,e,a,n,t,o){const d=c(r.totalSize),l=c(r.totalGzipSize);return`<!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Bundle Analysis Report</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; line-height: 1.6; }
10
+ .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
11
+ .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 12px; margin-bottom: 24px; }
12
+ .header h1 { font-size: 24px; margin-bottom: 8px; }
13
+ .header .meta { font-size: 14px; opacity: 0.9; }
14
+ .summary-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
15
+ .card { background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
16
+ .card .label { font-size: 13px; color: #888; margin-bottom: 4px; }
17
+ .card .value { font-size: 24px; font-weight: 700; color: #333; }
18
+ .card .sub { font-size: 12px; color: #aaa; margin-top: 2px; }
19
+ .section { background: white; border-radius: 10px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
20
+ .section h2 { font-size: 18px; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #f0f0f0; }
21
+ .chart-tabs { display: flex; gap: 8px; margin-bottom: 16px; }
22
+ .chart-tab { padding: 6px 16px; border: 1px solid #ddd; border-radius: 6px; background: white; cursor: pointer; font-size: 13px; transition: all 0.2s; }
23
+ .chart-tab.active { background: #667eea; color: white; border-color: #667eea; }
24
+ .chart-container { min-height: 400px; position: relative; }
25
+ .treemap { display: flex; flex-wrap: wrap; gap: 2px; }
26
+ .treemap-item { border-radius: 4px; padding: 8px; color: white; font-size: 11px; overflow: hidden; display: flex; flex-direction: column; justify-content: center; align-items: center; cursor: pointer; transition: opacity 0.2s; min-width: 40px; min-height: 40px; }
27
+ .treemap-item:hover { opacity: 0.85; }
28
+ .treemap-item .name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
29
+ .treemap-item .size { font-size: 10px; opacity: 0.9; }
30
+ table { width: 100%; border-collapse: collapse; font-size: 13px; }
31
+ th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid #f0f0f0; }
32
+ th { background: #fafafa; font-weight: 600; color: #555; position: sticky; top: 0; }
33
+ tr:hover { background: #f8f9ff; }
34
+ .bar { height: 8px; border-radius: 4px; background: #667eea; transition: width 0.3s; }
35
+ .bar-bg { width: 100%; height: 8px; border-radius: 4px; background: #f0f0f0; }
36
+ .warning { background: #fff8e1; border-left: 4px solid #ffc107; padding: 12px 16px; border-radius: 0 6px 6px 0; margin-bottom: 8px; font-size: 13px; }
37
+ .warning.critical { background: #ffebee; border-left-color: #f44336; }
38
+ .diff-positive { color: #f44336; }
39
+ .diff-negative { color: #4caf50; }
40
+ .diff-added { color: #2196f3; }
41
+ .diff-removed { color: #9e9e9e; }
42
+ .diff-unchanged { color: #bbb; }
43
+ .pie-chart { display: flex; align-items: center; gap: 24px; flex-wrap: wrap; }
44
+ .pie-svg { flex-shrink: 0; }
45
+ .pie-legend { flex: 1; min-width: 200px; }
46
+ .legend-item { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; font-size: 13px; }
47
+ .legend-color { width: 12px; height: 12px; border-radius: 3px; flex-shrink: 0; }
48
+ .empty-state { text-align: center; padding: 40px; color: #aaa; }
49
+ @media (max-width: 768px) { .container { padding: 12px; } .summary-cards { grid-template-columns: repeat(2, 1fr); } }
50
+ </style>
51
+ </head>
52
+ <body>
53
+ <div class="container">
54
+ <div class="header">
55
+ <h1>Bundle Analysis Report</h1>
56
+ <div class="meta">Generated: ${r.timestamp} | Analysis time: ${r.analysisTime}ms</div>
57
+ </div>
58
+
59
+ <div class="summary-cards">
60
+ <div class="card">
61
+ <div class="label">Total Size</div>
62
+ <div class="value">${d}</div>
63
+ <div class="sub">gzip: ${l}</div>
64
+ </div>
65
+ <div class="card">
66
+ <div class="label">Chunks</div>
67
+ <div class="value">${r.chunks.length}</div>
68
+ <div class="sub">entry: ${r.chunks.filter(i=>i.type==="entry").length} | chunk: ${r.chunks.filter(i=>i.type==="chunk").length} | asset: ${r.chunks.filter(i=>i.type==="asset").length}</div>
69
+ </div>
70
+ <div class="card">
71
+ <div class="label">Warnings</div>
72
+ <div class="value">${a.length}</div>
73
+ <div class="sub">${a.length>0?"threshold exceeded":"all within limits"}</div>
74
+ </div>
75
+ <div class="card">
76
+ <div class="label">File Types</div>
77
+ <div class="value">${e.length}</div>
78
+ <div class="sub">extensions detected</div>
79
+ </div>
80
+ </div>
81
+
82
+ ${a.length>0?`
83
+ <div class="section">
84
+ <h2>Warnings</h2>
85
+ ${a.map(i=>`<div class="warning ${i.sizeKB>i.thresholdKB*2?"critical":""}"><strong>${i.level.toUpperCase()}</strong>: ${i.message}</div>`).join("")}
86
+ </div>
87
+ `:""}
88
+
89
+ <div class="section">
90
+ <h2>Size Distribution</h2>
91
+ <div class="chart-tabs">
92
+ <button class="chart-tab ${o==="treemap"?"active":""}" onclick="switchChart('treemap')">Treemap</button>
93
+ <button class="chart-tab ${o==="sunburst"?"active":""}" onclick="switchChart('sunburst')">Sunburst</button>
94
+ <button class="chart-tab ${o==="list"?"active":""}" onclick="switchChart('list')">List</button>
95
+ </div>
96
+ <div class="chart-container" id="chartContainer"></div>
97
+ </div>
98
+
99
+ <div class="section">
100
+ <h2>File Type Distribution</h2>
101
+ <div class="pie-chart">
102
+ <div class="pie-svg" id="pieChart"></div>
103
+ <div class="pie-legend" id="pieLegend"></div>
104
+ </div>
105
+ </div>
106
+
107
+ <div class="section">
108
+ <h2>Top Modules</h2>
109
+ <table>
110
+ <thead><tr><th>#</th><th>Module</th><th>Size</th><th>gzip</th><th>Type</th></tr></thead>
111
+ <tbody>
112
+ ${t.map((i,u)=>`<tr><td>${u+1}</td><td title="${i.id}">${i.id.length>60?i.id.slice(0,57)+"...":i.id}</td><td>${c(i.size)}</td><td>${c(i.gzipSize)}</td><td>${i.isNodeModule?"node_modules":"source"}</td></tr>`).join("")}
113
+ </tbody>
114
+ </table>
115
+ </div>
116
+
117
+ ${n.length>0?`
118
+ <div class="section">
119
+ <h2>Comparison with Previous Build</h2>
120
+ <table>
121
+ <thead><tr><th>Name</th><th>Previous</th><th>Current</th><th>Diff</th><th>Trend</th></tr></thead>
122
+ <tbody>
123
+ ${n.slice(0,20).map(i=>{const u=i.trend==="increased"?"diff-positive":i.trend==="decreased"?"diff-negative":i.trend==="added"?"diff-added":i.trend==="removed"?"diff-removed":"diff-unchanged",h=i.trend==="increased"?"&#9650;":i.trend==="decreased"?"&#9660;":i.trend==="added"?"&#10010;":i.trend==="removed"?"&#10006;":"&#9644;";return`<tr><td>${i.name}</td><td>${i.previousSize>=0?c(i.previousSize):"-"}</td><td>${i.currentSize>=0?c(i.currentSize):"-"}</td><td class="${u}">${i.diff>=0?"+":""}${c(Math.abs(i.diff))} (${i.diffPercentage}%)</td><td class="${u}">${h} ${i.trend}</td></tr>`}).join("")}
124
+ </tbody>
125
+ </table>
126
+ </div>
127
+ `:""}
128
+
129
+ <div class="section">
130
+ <h2>All Chunks</h2>
131
+ <table>
132
+ <thead><tr><th>Name</th><th>Size</th><th>gzip</th><th>Type</th><th>Proportion</th></tr></thead>
133
+ <tbody>
134
+ ${r.chunks.map(i=>{const u=r.totalSize>0?i.size/r.totalSize*100:0;return`<tr><td title="${i.name}">${i.name.length>50?i.name.slice(0,47)+"...":i.name}</td><td>${c(i.size)}</td><td>${c(i.gzipSize)}</td><td>${i.type}</td><td><div class="bar-bg"><div class="bar" style="width:${u}%"></div></div><span style="font-size:11px;color:#888;">${u.toFixed(1)}%</span></td></tr>`}).join("")}
135
+ </tbody>
136
+ </table>
137
+ </div>
138
+ </div>
139
+
140
+ <script>
141
+ const chartData = ${JSON.stringify(s)};
142
+ const totalSize = ${r.totalSize};
143
+ const distributionData = ${JSON.stringify(e)};
144
+ const COLORS = ['#667eea','#764ba2','#f093fb','#f5576c','#4facfe','#00f2fe','#43e97b','#fa709a','#fee140','#a18cd1','#fbc2eb','#a6c1ee','#ffecd2','#fcb69f','#ff9a9e','#fad0c4'];
145
+
146
+ function switchChart(type) {
147
+ document.querySelectorAll('.chart-tab').forEach(t => t.classList.remove('active'));
148
+ event.target.classList.add('active');
149
+ renderChart(type);
150
+ }
151
+
152
+ function renderChart(type) {
153
+ const container = document.getElementById('chartContainer');
154
+ if (type === 'treemap') renderTreemap(container);
155
+ else if (type === 'sunburst') renderSunburst(container);
156
+ else renderList(container);
157
+ }
158
+
159
+ function renderTreemap(container) {
160
+ const items = chartData.map((d, i) => {
161
+ const pct = totalSize > 0 ? (d.size / totalSize * 100) : 0;
162
+ const flex = Math.max(pct * 3, 2);
163
+ return '<div class="treemap-item" style="flex:' + flex + ' ' + flex + ' 0;background:' + COLORS[i % COLORS.length] + ';" title="' + d.name + ': ${c(0)}'.replace('0B', formatBytes(d.size)) + '"><span class="name">' + d.name.split('/').pop() + '</span><span class="size">' + formatBytes(d.size) + '</span></div>';
164
+ });
165
+ container.innerHTML = '<div class="treemap">' + items.join('') + '</div>';
166
+ }
167
+
168
+ function renderSunburst(container) {
169
+ const cx = 200, cy = 200, r1 = 80, r2 = 160;
170
+ let paths = '';
171
+ let startAngle = 0;
172
+ const sorted = [...chartData].sort((a, b) => b.size - a.size);
173
+ sorted.forEach((d, i) => {
174
+ const angle = totalSize > 0 ? (d.size / totalSize) * Math.PI * 2 : 0;
175
+ const endAngle = startAngle + angle;
176
+ const x1 = cx + r1 * Math.cos(startAngle);
177
+ const y1 = cy + r1 * Math.sin(startAngle);
178
+ const x2 = cx + r2 * Math.cos(startAngle);
179
+ const y2 = cy + r2 * Math.sin(startAngle);
180
+ const x3 = cx + r2 * Math.cos(endAngle);
181
+ const y3 = cy + r2 * Math.sin(endAngle);
182
+ const x4 = cx + r1 * Math.cos(endAngle);
183
+ const y4 = cy + r1 * Math.sin(endAngle);
184
+ const largeArc = angle > Math.PI ? 1 : 0;
185
+ paths += '<path d="M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2 + 'A' + r2 + ',' + r2 + ' 0 ' + largeArc + ',1 ' + x3 + ',' + y3 + 'L' + x4 + ',' + y4 + 'A' + r1 + ',' + r1 + ' 0 ' + largeArc + ',0 ' + x1 + ',' + y1 + '" fill="' + COLORS[i % COLORS.length] + '" opacity="0.85" style="cursor:pointer" title="' + d.name + ': ' + formatBytes(d.size) + '"/>';
186
+ startAngle = endAngle;
187
+ });
188
+ container.innerHTML = '<div style="text-align:center"><svg width="400" height="400" viewBox="0 0 400 400">' + paths + '<circle cx="' + cx + '" cy="' + cy + '" r="' + (r1 - 1) + '" fill="white"/><text x="' + cx + '" y="' + cy + '" text-anchor="middle" dy="-6" font-size="14" font-weight="600">${d}</text><text x="' + cx + '" y="' + cy + '" text-anchor="middle" dy="14" font-size="11" fill="#888">total</text></svg></div>';
189
+ }
190
+
191
+ function renderList(container) {
192
+ const rows = [...chartData].sort((a, b) => b.size - a.size).map((d, i) => {
193
+ const pct = totalSize > 0 ? (d.size / totalSize * 100).toFixed(1) : '0.0';
194
+ return '<tr><td>' + (i+1) + '</td><td title="' + d.name + '">' + d.name + '</td><td>' + formatBytes(d.size) + '</td><td>' + formatBytes(d.gzipSize) + '</td><td>' + d.type + '</td><td><div class="bar-bg"><div class="bar" style="width:' + pct + '%"></div></div>' + pct + '%</td></tr>';
195
+ });
196
+ container.innerHTML = '<table><thead><tr><th>#</th><th>Name</th><th>Size</th><th>gzip</th><th>Type</th><th>Proportion</th></tr></thead><tbody>' + rows.join('') + '</tbody></table>';
197
+ }
198
+
199
+ function formatBytes(bytes) {
200
+ if (bytes < 1024) return bytes + 'B';
201
+ if (bytes < 1048576) return (bytes / 1024).toFixed(1) + 'KB';
202
+ return (bytes / 1048576).toFixed(2) + 'MB';
203
+ }
204
+
205
+ function renderPieChart() {
206
+ const svg = document.getElementById('pieChart');
207
+ const legend = document.getElementById('pieLegend');
208
+ const cx = 100, cy = 100, r = 90;
209
+ let paths = '';
210
+ let startAngle = -Math.PI / 2;
211
+ const items = distributionData.slice(0, 8);
212
+ const otherPct = distributionData.slice(8).reduce((s, d) => s + d.percentage, 0);
213
+ if (otherPct > 0) items.push({ extension: '(other)', count: 0, totalSize: 0, percentage: otherPct });
214
+
215
+ items.forEach((d, i) => {
216
+ const angle = (d.percentage / 100) * Math.PI * 2;
217
+ const endAngle = startAngle + angle;
218
+ const x1 = cx + r * Math.cos(startAngle);
219
+ const y1 = cy + r * Math.sin(startAngle);
220
+ const x2 = cx + r * Math.cos(endAngle);
221
+ const y2 = cy + r * Math.sin(endAngle);
222
+ const largeArc = angle > Math.PI ? 1 : 0;
223
+ paths += '<path d="M' + cx + ',' + cy + 'L' + x1 + ',' + y1 + 'A' + r + ',' + r + ' 0 ' + largeArc + ',1 ' + x2 + ',' + y2 + 'Z" fill="' + COLORS[i % COLORS.length] + '" opacity="0.85"/>';
224
+ startAngle = endAngle;
225
+ });
226
+
227
+ svg.innerHTML = '<svg width="200" height="200" viewBox="0 0 200 200">' + paths + '</svg>';
228
+ legend.innerHTML = items.map((d, i) => '<div class="legend-item"><div class="legend-color" style="background:' + COLORS[i % COLORS.length] + '"></div><span>' + d.extension + ' (' + d.percentage.toFixed(1) + '%)</span></div>').join('');
229
+ }
230
+
231
+ renderChart('${o}');
232
+ renderPieChart();
233
+ <\/script>
234
+ </body>
235
+ </html>`}class N extends b{analysisResult=null;getDefaultOptions(){return{outputFormat:"json",outputFile:"bundle-analysis",openAnalyzer:!1,sizeThreshold:100,topModules:20,compareWith:null,gzipSize:!0,excludeNodeModules:!1,excludePatterns:[],includeExtensions:[],defaultChartType:"treemap"}}validateOptions(){this.validator.field("outputFormat").enum(["json","html","both"]).field("openAnalyzer").boolean().field("sizeThreshold").number().minValue(0).field("topModules").number().minValue(1).field("gzipSize").boolean().field("excludeNodeModules").boolean().field("defaultChartType").enum(["treemap","sunburst","list"]).validate()}getPluginName(){return"bundle-analyzer"}getEnforce(){return"post"}addPluginHooks(s){s.writeBundle={order:"post",handler:async()=>{await this.safeExecute(()=>this.runAnalysis(),"\u5206\u6790\u6784\u5EFA\u4EA7\u7269")}}}async runAnalysis(){if(!this.viteConfig)return;const s=this.viteConfig.build.outDir;this.analysisResult=await B(s,this.options),this.options.compareWith&&await this.performComparison(),this.logSummary(),this.logWarnings(),await this.generateReports(s),this.options.openAnalyzer&&(this.options.outputFormat==="html"||this.options.outputFormat==="both")&&await this.openHtmlReport(s)}async performComparison(){if(!this.analysisResult||!this.options.compareWith)return;const s=await D(this.options.compareWith);if(!s){this.logger.info(`\u672A\u627E\u5230\u5BF9\u6BD4\u62A5\u544A: ${this.options.compareWith}\uFF0C\u8DF3\u8FC7\u5BF9\u6BD4\u5206\u6790`);return}const e=E(this.analysisResult,s);if(this.analysisResult.comparisonDiffs=e,e.length>0){const a=e.filter(d=>d.trend==="increased").length,n=e.filter(d=>d.trend==="decreased").length,t=e.filter(d=>d.trend==="added").length,o=e.filter(d=>d.trend==="removed").length;this.logger.info(`\u6784\u5EFA\u5BF9\u6BD4: ${a} \u4E2A\u589E\u5927, ${n} \u4E2A\u51CF\u5C0F, ${t} \u4E2A\u65B0\u589E, ${o} \u4E2A\u79FB\u9664`)}}async generateReports(s){if(!this.analysisResult)return;const{outputFormat:e,outputFile:a}=this.options;if(e==="json"||e==="both"){const n=await T(s,a,this.analysisResult);this.logger.info(`JSON \u62A5\u544A\u5DF2\u751F\u6210: ${n}`)}if(e==="html"||e==="both"){const n=await R(s,a,this.analysisResult,{defaultChartType:this.options.defaultChartType});this.logger.info(`HTML \u62A5\u544A\u5DF2\u751F\u6210: ${n}`)}}async openHtmlReport(s){const e=`${s}/${this.options.outputFile}.html`;try{const{exec:a}=await import("node:child_process"),n=process.platform;a(n==="win32"?`start "" "${e}"`:n==="darwin"?`open "${e}"`:`xdg-open "${e}"`),this.logger.info(`\u5DF2\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u62A5\u544A: ${e}`)}catch{this.logger.warn(`\u65E0\u6CD5\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u8BF7\u624B\u52A8\u6253\u5F00: ${e}`)}}logSummary(){if(!this.analysisResult)return;const{chunks:s,totalSize:e,totalGzipSize:a,analysisTime:n}=this.analysisResult;this.logger.success(`\u4EA7\u7269\u5206\u6790\u5B8C\u6210: ${s.length} \u4E2A chunk, \u603B\u4F53\u79EF: ${c(e)} (gzip: ${c(a)}), \u5206\u6790\u8017\u65F6: ${n}ms`);const t=s.filter(l=>l.type==="entry").length,o=s.filter(l=>l.type==="chunk").length,d=s.filter(l=>l.type==="asset").length;if((t>0||o>0||d>0)&&this.logger.info(` \u5165\u53E3: ${t} | \u4EE3\u7801\u5757: ${o} | \u8D44\u6E90: ${d}`),this.analysisResult.topModules.length>0){this.logger.info("\u4F53\u79EF Top 5 \u6A21\u5757:");const l=this.analysisResult.topModules.slice(0,5);for(let i=0;i<l.length;i++){const u=l[i],h=u.isNodeModule?"node_modules":"source";this.logger.info(` ${i+1}. ${c(u.size)} (${h}) ${u.id}`)}}}logWarnings(){if(!(!this.analysisResult||this.analysisResult.warnings.length===0)){this.logger.warn(`\u53D1\u73B0 ${this.analysisResult.warnings.length} \u4E2A\u4F53\u79EF\u544A\u8B66:`);for(const s of this.analysisResult.warnings){const e=s.sizeKB>s.thresholdKB*2?"\u{1F534}":"\u{1F7E1}";this.logger.warn(` ${e} ${s.message}`)}}}getResult(){return this.analysisResult}}const O=x(N);export{O as bundleAnalyzer};
@@ -0,0 +1 @@
1
+ "use strict";const factory_index=require("../../factory/index.cjs"),node_zlib=require("node:zlib"),a=require("node:fs"),promises=require("node:stream/promises"),a$1=require("node:path"),common_fs_index=require("../../common/fs/index.cjs"),common_format_index=require("../../common/format/index.cjs");require("../../logger/index.cjs"),require("../../common/object/index.cjs"),require("../../shared/vite-plugin.Bcg6RW2N.cjs"),require("fs"),require("path"),require("crypto");function _interopDefaultCompat(e){return e&&typeof e=="object"&&"default"in e?e.default:e}const a__default=_interopDefaultCompat(a$1);async function compressFileGzip(e,t,i){const s=(await a.promises.stat(e)).size,r=node_zlib.createGzip({level:i});await promises.pipeline(a.createReadStream(e),r,a.createWriteStream(t));const o=(await a.promises.stat(t)).size;return{file:e,originalSize:s,compressedSize:o,ratio:s>0?Number(((1-o/s)*100).toFixed(1)):0,algorithm:"gzip"}}async function compressFileBrotli(e,t,i){const s=(await a.promises.stat(e)).size,r=node_zlib.createBrotliCompress({params:{[node_zlib.constants.BROTLI_PARAM_QUALITY]:i}});await promises.pipeline(a.createReadStream(e),r,a.createWriteStream(t));const o=(await a.promises.stat(t)).size;return{file:e,originalSize:s,compressedSize:o,ratio:s>0?Number(((1-o/s)*100).toFixed(1)):0,algorithm:"brotli"}}async function compressFile(e,t,i,s){const r=e+(t==="gzip"?".gz":".br");return t==="gzip"?compressFileGzip(e,r,i):compressFileBrotli(e,r,s)}function shouldCompressFile(e,t,i,s){const r=e.replace(/\\/g,"/");if(i<s.threshold||s.excludeExtensions.length>0&&s.excludeExtensions.includes(t)||s.includeExtensions.length>0&&!s.includeExtensions.includes(t))return!1;if(s.excludePaths.length>0)for(const o of s.excludePaths){const l=o.replace(/\\/g,"/");if(r.startsWith(l)||r.includes(l))return!1}return!(t===".gz"||t===".br")}async function scanDirectory(e,t){return(await common_fs_index.scanDirectory(e,{filter:(i,s,r)=>{const o=a__default.relative(e,i);return shouldCompressFile(o,s,r,t)}})).map(i=>({filePath:i.filePath,relativePath:a__default.relative(e,i.filePath),size:i.size,ext:i.extension}))}function buildSummary(e,t){const i=e.reduce((o,l)=>o+l.originalSize,0),s=e.reduce((o,l)=>o+l.compressedSize,0),r=i>0?Number(((1-s/i)*100).toFixed(1)):0;return{totalFiles:e.length,totalOriginalSize:i,totalCompressedSize:s,totalRatio:r,gzipFiles:e.filter(o=>o.algorithm==="gzip").length,brotliFiles:e.filter(o=>o.algorithm==="brotli").length,executionTime:t,stats:e}}async function writeReport(e,t,i){if(!t)return;const s=a__default.isAbsolute(t)?t:a__default.join(e,t),r={timestamp:new Date().toISOString(),summary:{totalFiles:i.totalFiles,totalOriginalSize:i.totalOriginalSize,totalCompressedSize:i.totalCompressedSize,totalRatio:i.totalRatio,gzipFiles:i.gzipFiles,brotliFiles:i.brotliFiles,executionTime:i.executionTime},files:i.stats.map(o=>({file:o.file,originalSize:o.originalSize,compressedSize:o.compressedSize,ratio:o.ratio,algorithm:o.algorithm}))};await common_fs_index.writeJsonReport(s,r)}async function deleteOriginalFiles(e){const t=[...new Set(e.map(i=>i.file))];for(const i of t)try{await a.promises.unlink(i)}catch{}}class y extends factory_index.BasePlugin{allStats=[];summary=null;getDefaultOptions(){return{algorithm:"gzip",threshold:1024,deleteOriginalFile:!1,includeExtensions:[".js",".css",".html",".svg",".json",".xml",".txt"],excludeExtensions:[],excludePaths:[],compressionLevel:9,brotliQuality:11,reportOutput:"compress-report.json",parallelLimit:10}}validateOptions(){this.validator.field("algorithm").enum(["gzip","brotli","both"]).field("threshold").number().minValue(0).field("deleteOriginalFile").boolean().field("includeExtensions").array().field("excludeExtensions").array().field("excludePaths").array().field("compressionLevel").number().minValue(1).maxValue(9).field("brotliQuality").number().minValue(1).maxValue(11).field("reportOutput").custom(t=>t===!1||typeof t=="string","reportOutput \u5FC5\u987B\u4E3A false \u6216\u5B57\u7B26\u4E32\u8DEF\u5F84").field("parallelLimit").number().minValue(1).maxValue(50).validate()}getPluginName(){return"compress-assets"}getEnforce(){return"post"}addPluginHooks(t){t.writeBundle=async()=>{await this.safeExecute(()=>this.compressAllFiles(),"\u538B\u7F29\u6784\u5EFA\u4EA7\u7269")}}async compressAllFiles(){if(!this.viteConfig)return;const t=this.viteConfig.build.outDir,i=Date.now();this.logger.info(`\u5F00\u59CB\u626B\u63CF\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55: ${t}`);const s=await scanDirectory(t,this.options);if(s.length===0){this.logger.info("\u672A\u627E\u5230\u9700\u8981\u538B\u7F29\u7684\u6587\u4EF6");return}this.logger.info(`\u53D1\u73B0 ${s.length} \u4E2A\u5F85\u538B\u7F29\u6587\u4EF6`),this.allStats=[];const r=this.options.algorithm==="both"?["gzip","brotli"]:[this.options.algorithm];for(const l of r){const u=await common_fs_index.runWithConcurrency(s,async n=>compressFile(n.filePath,l,this.options.compressionLevel,this.options.brotliQuality),this.options.parallelLimit);this.allStats.push(...u)}const o=Date.now()-i;this.summary=buildSummary(this.allStats,o),this.options.deleteOriginalFile&&(await deleteOriginalFiles(this.allStats),this.logger.info("\u5DF2\u5220\u9664\u539F\u59CB\u6587\u4EF6\uFF0C\u4EC5\u4FDD\u7559\u538B\u7F29\u7248\u672C")),this.options.reportOutput&&(await writeReport(t,this.options.reportOutput,this.summary),this.logger.info(`\u538B\u7F29\u62A5\u544A\u5DF2\u751F\u6210: ${this.options.reportOutput}`)),this.logSummary()}logSummary(){if(!this.summary)return;const{totalFiles:t,totalOriginalSize:i,totalCompressedSize:s,totalRatio:r,executionTime:o}=this.summary;this.logger.success(`\u538B\u7F29\u5B8C\u6210: ${t} \u4E2A\u6587\u4EF6`,`\u539F\u59CB\u4F53\u79EF: ${common_format_index.formatFileSize(i)} \u2192 \u538B\u7F29\u540E: ${common_format_index.formatFileSize(s)}\uFF0C\u538B\u7F29\u7387: ${r}%\uFF0C\u8017\u65F6: ${o}ms`);const l=[...this.allStats].sort((u,n)=>n.ratio-u.ratio).slice(0,5);if(l.length>0){this.logger.info("\u538B\u7F29\u7387 Top 5:");for(const u of l)this.logger.info(` ${u.algorithm.toUpperCase().padEnd(6)} ${u.ratio}% ${common_format_index.formatFileSize(u.originalSize)} \u2192 ${common_format_index.formatFileSize(u.compressedSize)}`)}}getStats(){return[...this.allStats]}getSummary(){return this.summary}}const compressAssets=factory_index.createPluginFactory(y);exports.compressAssets=compressAssets;
@@ -0,0 +1,132 @@
1
+ import { BasePluginOptions, PluginFactory } from '../../factory/index.cjs';
2
+ import 'vite';
3
+ import '../../shared/vite-plugin.CLr0ttuO.cjs';
4
+ import '../../shared/vite-plugin.DRRlWY8P.cjs';
5
+
6
+ /**
7
+ * 压缩算法类型
8
+ *
9
+ * @typedef {('gzip' | 'brotli' | 'both')} CompressAlgorithm
10
+ * @description 支持的压缩算法:
11
+ * - `gzip`: 使用 gzip 压缩,输出 `.gz` 文件
12
+ * - `brotli`: 使用 brotli 压缩,输出 `.br` 文件
13
+ * - `both`: 同时生成 gzip 和 brotli 两种压缩文件
14
+ */
15
+ type CompressAlgorithm = 'gzip' | 'brotli' | 'both';
16
+ /**
17
+ * 单个文件的压缩统计信息
18
+ *
19
+ * @interface CompressStats
20
+ * @description 记录单个文件经过压缩后的详细统计数据
21
+ */
22
+ interface CompressStats {
23
+ /** 原始文件路径 */
24
+ file: string;
25
+ /** 原始文件大小(字节) */
26
+ originalSize: number;
27
+ /** 压缩后文件大小(字节) */
28
+ compressedSize: number;
29
+ /** 压缩率百分比(0-100),如 75.3 表示压缩后体积减少 75.3% */
30
+ ratio: number;
31
+ /** 使用的压缩算法 */
32
+ algorithm: 'gzip' | 'brotli';
33
+ }
34
+ /**
35
+ * 压缩操作的汇总统计信息
36
+ *
37
+ * @interface CompressSummary
38
+ * @description 包含整个压缩操作的总体统计数据,用于报告生成和日志输出
39
+ */
40
+ interface CompressSummary {
41
+ /** 压缩的文件总数 */
42
+ totalFiles: number;
43
+ /** 所有文件的原始大小总和(字节) */
44
+ totalOriginalSize: number;
45
+ /** 所有文件的压缩后大小总和(字节) */
46
+ totalCompressedSize: number;
47
+ /** 总体压缩率百分比 */
48
+ totalRatio: number;
49
+ /** 使用 gzip 压缩的文件数量 */
50
+ gzipFiles: number;
51
+ /** 使用 brotli 压缩的文件数量 */
52
+ brotliFiles: number;
53
+ /** 压缩操作总耗时(毫秒) */
54
+ executionTime: number;
55
+ /** 每个文件的详细压缩统计 */
56
+ stats: CompressStats[];
57
+ }
58
+ /**
59
+ * 构建产物压缩插件的配置选项
60
+ *
61
+ * @interface CompressAssetsOptions
62
+ * @extends {BasePluginOptions}
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * compressAssets({
67
+ * algorithm: 'both',
68
+ * threshold: 2048,
69
+ * compressionLevel: 9,
70
+ * brotliQuality: 11,
71
+ * reportOutput: 'compress-report.json'
72
+ * })
73
+ * ```
74
+ */
75
+ interface CompressAssetsOptions extends BasePluginOptions {
76
+ /** 压缩算法,支持 gzip、brotli 或同时使用两者 */
77
+ algorithm?: CompressAlgorithm;
78
+ /** 最小压缩阈值(字节),小于此大小的文件将被跳过 */
79
+ threshold?: number;
80
+ /** 是否在压缩后删除原始文件,仅保留压缩版本 */
81
+ deleteOriginalFile?: boolean;
82
+ /** 需要压缩的文件扩展名列表,如 ['.js', '.css', '.html'] */
83
+ includeExtensions?: string[];
84
+ /** 需要排除的文件扩展名列表,优先级高于 includeExtensions */
85
+ excludeExtensions?: string[];
86
+ /** 需要排除的路径前缀列表,匹配到的路径下的文件将跳过压缩 */
87
+ excludePaths?: string[];
88
+ /** gzip 压缩级别,范围 1-9,1 最快压缩率最低,9 最慢压缩率最高 */
89
+ compressionLevel?: number;
90
+ /** brotli 崩质量参数,范围 1-11,1 最快质量最低,11 最慢质量最高 */
91
+ brotliQuality?: number;
92
+ /** 压缩报告输出路径,设为 false 则不生成报告 */
93
+ reportOutput?: string | false;
94
+ /** 并发压缩的最大文件数,控制同时进行压缩操作的文件数量 */
95
+ parallelLimit?: number;
96
+ }
97
+
98
+ /**
99
+ * 创建构建产物压缩插件
100
+ *
101
+ * @function compressAssets
102
+ * @param {Partial<CompressAssetsOptions>} [options] - 插件配置选项
103
+ * @returns {Plugin} Vite 插件实例
104
+ *
105
+ * @description 在 Vite 构建完成后自动压缩输出目录中的文件,
106
+ * 支持 gzip 和 brotli 两种压缩算法,可配置压缩级别、文件过滤规则、
107
+ * 并发数量等参数,并生成压缩统计报告。
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * // vite.config.ts
112
+ * import { compressAssets } from '@meng-xi/vite-plugin'
113
+ *
114
+ * export default defineConfig({
115
+ * plugins: [
116
+ * compressAssets({
117
+ * algorithm: 'both',
118
+ * threshold: 2048,
119
+ * deleteOriginalFile: false,
120
+ * compressionLevel: 9,
121
+ * brotliQuality: 11,
122
+ * reportOutput: 'compress-report.json',
123
+ * parallelLimit: 10
124
+ * })
125
+ * ]
126
+ * })
127
+ * ```
128
+ */
129
+ declare const compressAssets: PluginFactory<CompressAssetsOptions, CompressAssetsOptions>;
130
+
131
+ export { compressAssets };
132
+ export type { CompressAlgorithm, CompressAssetsOptions, CompressStats, CompressSummary };
@@ -0,0 +1,132 @@
1
+ import { BasePluginOptions, PluginFactory } from '../../factory/index.mjs';
2
+ import 'vite';
3
+ import '../../shared/vite-plugin.CLr0ttuO.mjs';
4
+ import '../../shared/vite-plugin.DRRlWY8P.mjs';
5
+
6
+ /**
7
+ * 压缩算法类型
8
+ *
9
+ * @typedef {('gzip' | 'brotli' | 'both')} CompressAlgorithm
10
+ * @description 支持的压缩算法:
11
+ * - `gzip`: 使用 gzip 压缩,输出 `.gz` 文件
12
+ * - `brotli`: 使用 brotli 压缩,输出 `.br` 文件
13
+ * - `both`: 同时生成 gzip 和 brotli 两种压缩文件
14
+ */
15
+ type CompressAlgorithm = 'gzip' | 'brotli' | 'both';
16
+ /**
17
+ * 单个文件的压缩统计信息
18
+ *
19
+ * @interface CompressStats
20
+ * @description 记录单个文件经过压缩后的详细统计数据
21
+ */
22
+ interface CompressStats {
23
+ /** 原始文件路径 */
24
+ file: string;
25
+ /** 原始文件大小(字节) */
26
+ originalSize: number;
27
+ /** 压缩后文件大小(字节) */
28
+ compressedSize: number;
29
+ /** 压缩率百分比(0-100),如 75.3 表示压缩后体积减少 75.3% */
30
+ ratio: number;
31
+ /** 使用的压缩算法 */
32
+ algorithm: 'gzip' | 'brotli';
33
+ }
34
+ /**
35
+ * 压缩操作的汇总统计信息
36
+ *
37
+ * @interface CompressSummary
38
+ * @description 包含整个压缩操作的总体统计数据,用于报告生成和日志输出
39
+ */
40
+ interface CompressSummary {
41
+ /** 压缩的文件总数 */
42
+ totalFiles: number;
43
+ /** 所有文件的原始大小总和(字节) */
44
+ totalOriginalSize: number;
45
+ /** 所有文件的压缩后大小总和(字节) */
46
+ totalCompressedSize: number;
47
+ /** 总体压缩率百分比 */
48
+ totalRatio: number;
49
+ /** 使用 gzip 压缩的文件数量 */
50
+ gzipFiles: number;
51
+ /** 使用 brotli 压缩的文件数量 */
52
+ brotliFiles: number;
53
+ /** 压缩操作总耗时(毫秒) */
54
+ executionTime: number;
55
+ /** 每个文件的详细压缩统计 */
56
+ stats: CompressStats[];
57
+ }
58
+ /**
59
+ * 构建产物压缩插件的配置选项
60
+ *
61
+ * @interface CompressAssetsOptions
62
+ * @extends {BasePluginOptions}
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * compressAssets({
67
+ * algorithm: 'both',
68
+ * threshold: 2048,
69
+ * compressionLevel: 9,
70
+ * brotliQuality: 11,
71
+ * reportOutput: 'compress-report.json'
72
+ * })
73
+ * ```
74
+ */
75
+ interface CompressAssetsOptions extends BasePluginOptions {
76
+ /** 压缩算法,支持 gzip、brotli 或同时使用两者 */
77
+ algorithm?: CompressAlgorithm;
78
+ /** 最小压缩阈值(字节),小于此大小的文件将被跳过 */
79
+ threshold?: number;
80
+ /** 是否在压缩后删除原始文件,仅保留压缩版本 */
81
+ deleteOriginalFile?: boolean;
82
+ /** 需要压缩的文件扩展名列表,如 ['.js', '.css', '.html'] */
83
+ includeExtensions?: string[];
84
+ /** 需要排除的文件扩展名列表,优先级高于 includeExtensions */
85
+ excludeExtensions?: string[];
86
+ /** 需要排除的路径前缀列表,匹配到的路径下的文件将跳过压缩 */
87
+ excludePaths?: string[];
88
+ /** gzip 压缩级别,范围 1-9,1 最快压缩率最低,9 最慢压缩率最高 */
89
+ compressionLevel?: number;
90
+ /** brotli 崩质量参数,范围 1-11,1 最快质量最低,11 最慢质量最高 */
91
+ brotliQuality?: number;
92
+ /** 压缩报告输出路径,设为 false 则不生成报告 */
93
+ reportOutput?: string | false;
94
+ /** 并发压缩的最大文件数,控制同时进行压缩操作的文件数量 */
95
+ parallelLimit?: number;
96
+ }
97
+
98
+ /**
99
+ * 创建构建产物压缩插件
100
+ *
101
+ * @function compressAssets
102
+ * @param {Partial<CompressAssetsOptions>} [options] - 插件配置选项
103
+ * @returns {Plugin} Vite 插件实例
104
+ *
105
+ * @description 在 Vite 构建完成后自动压缩输出目录中的文件,
106
+ * 支持 gzip 和 brotli 两种压缩算法,可配置压缩级别、文件过滤规则、
107
+ * 并发数量等参数,并生成压缩统计报告。
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * // vite.config.ts
112
+ * import { compressAssets } from '@meng-xi/vite-plugin'
113
+ *
114
+ * export default defineConfig({
115
+ * plugins: [
116
+ * compressAssets({
117
+ * algorithm: 'both',
118
+ * threshold: 2048,
119
+ * deleteOriginalFile: false,
120
+ * compressionLevel: 9,
121
+ * brotliQuality: 11,
122
+ * reportOutput: 'compress-report.json',
123
+ * parallelLimit: 10
124
+ * })
125
+ * ]
126
+ * })
127
+ * ```
128
+ */
129
+ declare const compressAssets: PluginFactory<CompressAssetsOptions, CompressAssetsOptions>;
130
+
131
+ export { compressAssets };
132
+ export type { CompressAlgorithm, CompressAssetsOptions, CompressStats, CompressSummary };
@@ -0,0 +1,132 @@
1
+ import { BasePluginOptions, PluginFactory } from '../../factory/index.js';
2
+ import 'vite';
3
+ import '../../shared/vite-plugin.CLr0ttuO.js';
4
+ import '../../shared/vite-plugin.DRRlWY8P.js';
5
+
6
+ /**
7
+ * 压缩算法类型
8
+ *
9
+ * @typedef {('gzip' | 'brotli' | 'both')} CompressAlgorithm
10
+ * @description 支持的压缩算法:
11
+ * - `gzip`: 使用 gzip 压缩,输出 `.gz` 文件
12
+ * - `brotli`: 使用 brotli 压缩,输出 `.br` 文件
13
+ * - `both`: 同时生成 gzip 和 brotli 两种压缩文件
14
+ */
15
+ type CompressAlgorithm = 'gzip' | 'brotli' | 'both';
16
+ /**
17
+ * 单个文件的压缩统计信息
18
+ *
19
+ * @interface CompressStats
20
+ * @description 记录单个文件经过压缩后的详细统计数据
21
+ */
22
+ interface CompressStats {
23
+ /** 原始文件路径 */
24
+ file: string;
25
+ /** 原始文件大小(字节) */
26
+ originalSize: number;
27
+ /** 压缩后文件大小(字节) */
28
+ compressedSize: number;
29
+ /** 压缩率百分比(0-100),如 75.3 表示压缩后体积减少 75.3% */
30
+ ratio: number;
31
+ /** 使用的压缩算法 */
32
+ algorithm: 'gzip' | 'brotli';
33
+ }
34
+ /**
35
+ * 压缩操作的汇总统计信息
36
+ *
37
+ * @interface CompressSummary
38
+ * @description 包含整个压缩操作的总体统计数据,用于报告生成和日志输出
39
+ */
40
+ interface CompressSummary {
41
+ /** 压缩的文件总数 */
42
+ totalFiles: number;
43
+ /** 所有文件的原始大小总和(字节) */
44
+ totalOriginalSize: number;
45
+ /** 所有文件的压缩后大小总和(字节) */
46
+ totalCompressedSize: number;
47
+ /** 总体压缩率百分比 */
48
+ totalRatio: number;
49
+ /** 使用 gzip 压缩的文件数量 */
50
+ gzipFiles: number;
51
+ /** 使用 brotli 压缩的文件数量 */
52
+ brotliFiles: number;
53
+ /** 压缩操作总耗时(毫秒) */
54
+ executionTime: number;
55
+ /** 每个文件的详细压缩统计 */
56
+ stats: CompressStats[];
57
+ }
58
+ /**
59
+ * 构建产物压缩插件的配置选项
60
+ *
61
+ * @interface CompressAssetsOptions
62
+ * @extends {BasePluginOptions}
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * compressAssets({
67
+ * algorithm: 'both',
68
+ * threshold: 2048,
69
+ * compressionLevel: 9,
70
+ * brotliQuality: 11,
71
+ * reportOutput: 'compress-report.json'
72
+ * })
73
+ * ```
74
+ */
75
+ interface CompressAssetsOptions extends BasePluginOptions {
76
+ /** 压缩算法,支持 gzip、brotli 或同时使用两者 */
77
+ algorithm?: CompressAlgorithm;
78
+ /** 最小压缩阈值(字节),小于此大小的文件将被跳过 */
79
+ threshold?: number;
80
+ /** 是否在压缩后删除原始文件,仅保留压缩版本 */
81
+ deleteOriginalFile?: boolean;
82
+ /** 需要压缩的文件扩展名列表,如 ['.js', '.css', '.html'] */
83
+ includeExtensions?: string[];
84
+ /** 需要排除的文件扩展名列表,优先级高于 includeExtensions */
85
+ excludeExtensions?: string[];
86
+ /** 需要排除的路径前缀列表,匹配到的路径下的文件将跳过压缩 */
87
+ excludePaths?: string[];
88
+ /** gzip 压缩级别,范围 1-9,1 最快压缩率最低,9 最慢压缩率最高 */
89
+ compressionLevel?: number;
90
+ /** brotli 崩质量参数,范围 1-11,1 最快质量最低,11 最慢质量最高 */
91
+ brotliQuality?: number;
92
+ /** 压缩报告输出路径,设为 false 则不生成报告 */
93
+ reportOutput?: string | false;
94
+ /** 并发压缩的最大文件数,控制同时进行压缩操作的文件数量 */
95
+ parallelLimit?: number;
96
+ }
97
+
98
+ /**
99
+ * 创建构建产物压缩插件
100
+ *
101
+ * @function compressAssets
102
+ * @param {Partial<CompressAssetsOptions>} [options] - 插件配置选项
103
+ * @returns {Plugin} Vite 插件实例
104
+ *
105
+ * @description 在 Vite 构建完成后自动压缩输出目录中的文件,
106
+ * 支持 gzip 和 brotli 两种压缩算法,可配置压缩级别、文件过滤规则、
107
+ * 并发数量等参数,并生成压缩统计报告。
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * // vite.config.ts
112
+ * import { compressAssets } from '@meng-xi/vite-plugin'
113
+ *
114
+ * export default defineConfig({
115
+ * plugins: [
116
+ * compressAssets({
117
+ * algorithm: 'both',
118
+ * threshold: 2048,
119
+ * deleteOriginalFile: false,
120
+ * compressionLevel: 9,
121
+ * brotliQuality: 11,
122
+ * reportOutput: 'compress-report.json',
123
+ * parallelLimit: 10
124
+ * })
125
+ * ]
126
+ * })
127
+ * ```
128
+ */
129
+ declare const compressAssets: PluginFactory<CompressAssetsOptions, CompressAssetsOptions>;
130
+
131
+ export { compressAssets };
132
+ export type { CompressAlgorithm, CompressAssetsOptions, CompressStats, CompressSummary };