vite-plugin-resource-waste 1.0.4 → 1.0.6
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.
- package/README.md +4 -2
- package/dist/html-MLVSEXMF.js +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -5
- package/package.json +2 -4
- package/dist/html-W5TFGDIJ.js +0 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
**vite-plugin-resource-waste** 在 `vite build` 时自动分析这些问题,把「浪费」量化为 **传输体积(KB)+ 解析耗时(ms)**,并给出可执行的修复建议。
|
|
17
17
|
|
|
18
|
-
> 本插件分析的是
|
|
18
|
+
> 本插件分析的是 **构建冗余、导入模式、未使用 import、不可达代码、缓存策略、重复依赖** 等多维浪费。
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
| 类别 | 检测内容 | 典型场景 |
|
|
47
47
|
|---|---|---|
|
|
48
48
|
| **import-pattern** | lodash/moment/antd 全量导入、barrel 文件 | tree-shake 失效,体积虚高 |
|
|
49
|
+
| **dead-import** | 已 import 但未使用的本地模块(含页面/组件) | 路由注释了但 import 未删、组件引入未使用 |
|
|
49
50
|
| **unreachable-component** | 不在 module graph 中的页面/组件 | 遗留代码、已下线活动页 |
|
|
50
51
|
| **static-unused-js** | 过多 export、独立 async chunk 风险 | 工具库 export 膨胀、误 prefetch |
|
|
51
52
|
| **cache-miss** | 产物文件名缺少 content hash | 字体/图片回访重复下载 |
|
|
@@ -71,7 +72,6 @@ npm install vite-plugin-resource-waste -D
|
|
|
71
72
|
# pnpm add vite-plugin-resource-waste -D
|
|
72
73
|
# yarn add vite-plugin-resource-waste -D
|
|
73
74
|
```
|
|
74
|
-
|
|
75
75
|
---
|
|
76
76
|
|
|
77
77
|
## 快速开始
|
|
@@ -138,6 +138,7 @@ resourceWaste({
|
|
|
138
138
|
```ts
|
|
139
139
|
resourceWaste({
|
|
140
140
|
scanImportPatterns: true, // 扫描 lodash 全量导入等
|
|
141
|
+
scanUnusedImports: true, // 扫描未使用的本地静态 import
|
|
141
142
|
scanUnreachable: true, // 扫描不可达页面/组件
|
|
142
143
|
scanCacheStrategy: true, // 扫描产物 cache 策略
|
|
143
144
|
scanDuplicateDeps: true, // 扫描重复依赖版本
|
|
@@ -180,6 +181,7 @@ resourceWaste({
|
|
|
180
181
|
| `failOnThreshold` | `boolean` | `false` | 超过阈值是否中断构建 |
|
|
181
182
|
| `htmlReport` | `boolean` | `true` | 是否生成 HTML 可视化报告 |
|
|
182
183
|
| `scanImportPatterns` | `boolean` | `true` | 是否扫描低效 import 模式 |
|
|
184
|
+
| `scanUnusedImports` | `boolean` | `true` | 是否扫描未使用的本地静态 import |
|
|
183
185
|
| `scanUnreachable` | `boolean` | `true` | 是否扫描不可达源文件/组件 |
|
|
184
186
|
| `scanCacheStrategy` | `boolean` | `true` | 是否分析产物缓存策略 |
|
|
185
187
|
| `scanDuplicateDeps` | `boolean` | `true` | 是否检测重复依赖版本 |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var n={"static-unused-js":"\u9759\u6001\u672A\u4F7F\u7528 JS","unreachable-component":"\u4E0D\u53EF\u8FBE\u7EC4\u4EF6/\u9875\u9762","cache-miss":"\u7F13\u5B58\u7B56\u7565\u98CE\u9669","duplicate-dependency":"\u91CD\u590D\u4F9D\u8D56","import-pattern":"\u5BFC\u5165\u6A21\u5F0F\u6D6A\u8D39","dead-import":"\u672A\u4F7F\u7528 import","prefetch-waste":"\u9884\u52A0\u8F7D\u6D6A\u8D39"},i={high:"\u9AD8",medium:"\u4E2D",low:"\u4F4E"},p='<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Resource Waste Report</title><style>body{font:14px/1.5 system-ui,sans-serif;margin:20px;color:#1e293b}table{border-collapse:collapse;width:100%;margin-top:12px}td,th{border:1px solid #cbd5e1;padding:8px;text-align:left;vertical-align:top}th{background:#f1f5f9}.badge{font-size:12px;font-weight:600}.cards{display:flex;flex-wrap:wrap;gap:12px;margin:12px 0}.card{border:1px solid #cbd5e1;border-radius:8px;padding:10px 14px;min-width:120px}.muted{color:#64748b;font-size:13px}code{font-size:12px;background:#f1f5f9;padding:2px 4px}</style></head><body><h1>\u524D\u7AEF\u8D44\u6E90\u6D6A\u8D39\u5206\u6790\u62A5\u544A</h1><p class="muted">{{META}}</p><div class="cards">{{SUMMARY}}</div><table><thead><tr><th>\u7EA7\u522B</th><th>\u7C7B\u522B</th><th>\u95EE\u9898</th><th>\u6587\u4EF6</th><th>\u4F20\u8F93</th><th>\u89E3\u6790</th><th>\u5EFA\u8BAE</th></tr></thead><tbody>{{ROWS}}</tbody></table><p class="muted">{{LEGEND}}</p></body></html>';function l(e){let{summary:s,issues:d}=e,o=d.map(t=>`<tr><td class="badge">${i[t.severity]}</td><td>${n[t.category]}</td><td><b>${a(t.title)}</b><br/><span class="muted">${a(t.detail)}</span></td><td><code>${a(t.file??"-")}</code></td><td>${t.cost.transferKb>0?t.cost.transferKb+" KB":"-"}</td><td>${t.cost.parseMsEstimate>0?t.cost.parseMsEstimate+" ms":"-"}</td><td>${a(t.suggestion)}</td></tr>`).join(""),c=r("\u95EE\u9898\u603B\u6570",s.totalIssues)+r("\u6D6A\u8D39\u4F20\u8F93",s.totalWasteTransferKb+" KB")+r("\u6D6A\u8D39\u89E3\u6790",s.totalParseMsEstimate+" ms")+r("\u9AD8\u5371",s.bySeverity.high);return p.replace("{{META}}",`\u751F\u6210: ${e.generatedAt} \xB7 \u6A21\u5F0F: ${e.mode}`).replace("{{SUMMARY}}",c).replace("{{ROWS}}",o||'<tr><td colspan="7">\u672A\u68C0\u6D4B\u5230\u8D44\u6E90\u6D6A\u8D39\u95EE\u9898</td></tr>').replace("{{LEGEND}}","\u6784\u5EFA\u671F\u9759\u6001\u5206\u6790\uFF1Btree-shake \u6709\u6548\u65F6\u672A\u4F7F\u7528 export \u4E0D\u4E00\u5B9A\u589E\u5927\u9996\u5305\u3002")}function r(e,s){return `<div class="card"><div class="muted">${e}</div><div><b>${s}</b></div></div>`}function a(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}export{l as renderHtmlReport};
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { Plugin } from 'vite';
|
|
|
7
7
|
/** 问题严重级别:高 / 中 / 低 */
|
|
8
8
|
type IssueSeverity = 'high' | 'medium' | 'low';
|
|
9
9
|
/** 问题分类,对应不同分析器产出的 issue 类型 */
|
|
10
|
-
type IssueCategory = 'static-unused-js' | 'unreachable-component' | 'cache-miss' | 'duplicate-dependency' | 'import-pattern' | 'prefetch-waste';
|
|
10
|
+
type IssueCategory = 'static-unused-js' | 'unreachable-component' | 'cache-miss' | 'duplicate-dependency' | 'import-pattern' | 'dead-import' | 'prefetch-waste';
|
|
11
11
|
/** 插件用户可配置项 */
|
|
12
12
|
interface ResourceWastePluginOptions {
|
|
13
13
|
/** 源码根目录,默认 src */
|
|
@@ -22,6 +22,8 @@ interface ResourceWastePluginOptions {
|
|
|
22
22
|
htmlReport?: boolean;
|
|
23
23
|
/** 是否扫描 import 模式问题 */
|
|
24
24
|
scanImportPatterns?: boolean;
|
|
25
|
+
/** 是否扫描未使用的本地静态 import */
|
|
26
|
+
scanUnusedImports?: boolean;
|
|
25
27
|
/** 是否扫描不可达文件 */
|
|
26
28
|
scanUnreachable?: boolean;
|
|
27
29
|
/** 是否分析产物 cache 策略 */
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import R from'fs';import b from'path';var N={root:"src",reportDir:"resource-waste",wasteThresholdKb:300,failOnThreshold:false,htmlReport:true,scanImportPatterns:true,scanUnreachable:true,scanCacheStrategy:true,scanDuplicateDeps:true,include:[],exclude:["**/*.spec.*","**/*.test.*","**/__tests__/**","**/node_modules/**"],silent:false},j=[".ts",".tsx",".js",".jsx",".vue",".svelte"],z=[".js",".css",".woff",".woff2",".ttf",".png",".jpg",".jpeg",".gif",".webp",".svg",".ico"],K=/\.[a-f0-9]{8,}\.(js|css|woff2?|ttf|png|jpe?g|gif|webp|svg|ico)$/i,T=[[/import\s+_\s+from\s+['"]lodash['"]/,"lodash","high",68],[/import\s+\*\s+as\s+\w+\s+from\s+['"]lodash['"]/,"lodash","high",68],[/import\s+\w+\s+from\s+['"]moment['"]/,"moment","high",65],[/import\s+\*\s+as\s+\w+\s+from\s+['"]antd['"]/,"antd","high",200],[/import\s+\*\s+as\s+\w+\s+from\s+['"]@ant-design\/icons['"]/,"@ant-design/icons","medium",80],[/import\s+['"][^'"]+\.css['"]\s*;\s*\/\/\s*unused/i,"css","medium",10]],k={lodash:"\u6539\u4E3A import debounce from 'lodash-es/debounce' \u6216\u6309\u9700\u5BFC\u5165",moment:"\u6539\u7528 dayjs \u6216 date-fns \u6309\u9700\u5BFC\u5165",antd:"\u6539\u7528 antd \u6309\u9700\u5BFC\u5165\u6216 unplugin-vue-components","@ant-design/icons":"\u6539\u4E3A\u6309\u9700\u5BFC\u5165\u5177\u4F53\u56FE\u6807",css:"\u79FB\u9664\u672A\u4F7F\u7528\u7684 CSS \u526F\u4F5C\u7528 import"};function m(e){return e.split(b.sep).join("/")}function B(e){try{return R.readFileSync(e,"utf-8")}catch{return ""}}function D(e){try{let t=R.statSync(e);return Math.round(t.size/1024*100)/100}catch{return 0}}function y(e){return Math.round(e*.45*10)/10}function W(e){let t=m(e),i=t.lastIndexOf("node_modules/");if(i===-1)return null;let n=t.slice(0,i+13),s=t.slice(i+13).split("/");if(s[0]?.startsWith("@")&&s[1]){let r=`${s[0]}/${s[1]}`,a=_(b.join(n,r));return {name:r,version:a}}if(s[0]){let r=s[0],a=_(b.join(n,r));return {name:r,version:a}}return null}function _(e){try{let t=b.join(e,"package.json");return JSON.parse(R.readFileSync(t,"utf-8")).version}catch{return}}function I(e,t){return b.isAbsolute(t)?t:b.resolve(e,t)}function g(e,t){return `${e}-${t}`}function w(e){R.mkdirSync(e,{recursive:true});}function S(e,t){return m(b.relative(e,t))}function L(e){if(!e.options.scanCacheStrategy)return [];let t=[],i=0;for(let o of e.bundleFiles){if(o.type!=="asset")continue;let s=ne(o.fileName);if(!z.includes(s))continue;let r=Math.round(o.size/1024*100)/100;if(!K.test(o.fileName)){let c=/\.(woff2?|ttf|otf)$/i.test(o.fileName),u=r>100;t.push({id:g("cache-miss",i++),category:"cache-miss",severity:u||c?"high":"medium",title:"\u4EA7\u7269\u6587\u4EF6\u540D\u7F3A\u5C11 content hash",file:o.fileName,detail:`\u9759\u6001\u8D44\u6E90 ${o.fileName} (${r} KB) \u672A\u5305\u542B content hash\uFF0C\u6D4F\u89C8\u5668\u96BE\u4EE5\u5B89\u5168\u957F\u671F\u7F13\u5B58\uFF0C\u56DE\u8BBF\u7528\u6237\u53EF\u80FD\u91CD\u590D\u4E0B\u8F7D\u3002`,suggestion:c?"\u914D\u7F6E vite build.assetsInlineLimit \u4E0E rollup assetFileNames \u542B [hash]\uFF1BCDN \u8BBE\u7F6E immutable \u7F13\u5B58\u3002":"\u5728 vite.config \u4E2D\u8BBE\u7F6E build.rollupOptions.output.assetFileNames \u5305\u542B [hash] \u5360\u4F4D\u7B26\u3002",cost:{transferKb:r,parseMsEstimate:c?y(r*.1):0,description:u?`\u56DE\u8BBF\u7528\u6237\u53EF\u80FD\u91CD\u590D\u4F20\u8F93 ${r} KB`:"\u7F13\u5B58\u547D\u4E2D\u7387\u4F4E\u5BFC\u81F4\u91CD\u590D\u9A8C\u8BC1\u6210\u672C"},metadata:{sizeKb:r,extension:s,hasContentHash:false}});}}let n=e.bundleFiles.filter(o=>o.fileName.endsWith(".html"));for(let o of n)t.push({id:g("cache-info",i++),category:"cache-miss",severity:"low",title:"HTML \u5165\u53E3\u5E94\u4F7F\u7528\u77ED\u7F13\u5B58",file:o.fileName,detail:`${o.fileName} \u662F HTML \u5165\u53E3\uFF0C\u901A\u5E38\u5E94\u914D\u7F6E\u8F83\u77ED max-age \u6216 no-cache\uFF0C\u4EE5\u786E\u4FDD\u7528\u6237\u83B7\u53D6\u6700\u65B0\u8D44\u6E90\u5F15\u7528\u3002`,suggestion:"\u90E8\u7F72\u65F6\u4E3A HTML \u8BBE\u7F6E Cache-Control: no-cache\uFF1BJS/CSS \u7B49\u5E26 hash \u8D44\u6E90\u8BBE\u7F6E\u957F\u671F\u7F13\u5B58\u3002",cost:{transferKb:Math.round(o.size/1024*100)/100,parseMsEstimate:0,description:"\u914D\u7F6E\u5EFA\u8BAE\uFF0C\u975E\u6D6A\u8D39"},metadata:{informational:true}});return t}function ne(e){let t=e.lastIndexOf(".");return t===-1?"":e.slice(t).toLowerCase()}function U(e){if(!e.options.scanDuplicateDeps)return [];let t=new Map,i=0,n=[];for(let o of e.bundleFiles)if(o.type==="chunk")for(let s of o.modules??[]){let r=W(s);if(!r?.version)continue;t.has(r.name)||t.set(r.name,new Map);let a=t.get(r.name);a.set(r.version,(a.get(r.version)??0)+1);}for(let[o,s]of t){if(s.size<=1)continue;let r=[...s.entries()],a=r.reduce((u,[,f])=>u+f,0),c=Math.max(5,Math.round(a*3));n.push({id:g("duplicate-dep",i++),category:"duplicate-dependency",severity:"medium",title:`\u91CD\u590D\u4F9D\u8D56\u7248\u672C: ${o}`,file:o,detail:`\u4F9D\u8D56 ${o} \u5728\u4EA7\u7269\u4E2D\u51FA\u73B0\u591A\u4E2A\u7248\u672C: ${r.map(([u,f])=>`${u}(${f}\u6B21)`).join(", ")}\u3002`,suggestion:`\u4F7F\u7528 package.json overrides / pnpm.overrides \u7EDF\u4E00 ${o} \u7248\u672C\uFF0C\u907F\u514D\u91CD\u590D\u6253\u5305\u4E0E\u7F13\u5B58\u5931\u6548\u3002`,cost:{transferKb:c,parseMsEstimate:y(c),description:`\u9884\u4F30\u91CD\u590D\u4F20\u8F93\u7EA6 ${c} KB`},metadata:{packageName:o,versions:Object.fromEntries(r)}});}return n}function H(e){let t=[],i=0;for(let[n,o]of e.parsedModules){if(n.includes("node_modules")||n.startsWith("\0")||!o.code)continue;let s=[...o.code.matchAll(/export\s+(?:const|function|class|type|interface)\s+(\w+)/g)];if(s.length!==0&&s.length>=8&&o.importers.size<=1){let r=Math.round(o.code.length/1024*.3);t.push({id:g("unused-export",i++),category:"static-unused-js",severity:"low",title:"\u7591\u4F3C\u8FC7\u591A\u672A\u4F7F\u7528 export",file:n,detail:`${n} \u5BFC\u51FA\u4E86 ${s.length} \u4E2A\u7B26\u53F7\uFF0C\u4F46\u4EC5\u88AB ${o.importers.size} \u4E2A\u6A21\u5757\u5F15\u7528\uFF0C\u53EF\u80FD\u5B58\u5728\u672A\u4F7F\u7528\u7684 export\u3002`,suggestion:"\u4F7F\u7528 Knip \u6216 ESLint \u8FDB\u4E00\u6B65\u786E\u8BA4\uFF0C\u79FB\u9664\u672A\u4F7F\u7528\u7684 export \u4EE5\u6539\u5584 tree-shaking\u3002",cost:{transferKb:r,parseMsEstimate:y(r),description:"\u4FDD\u5B88\u4F30\u8BA1\uFF0C\u9700\u7ED3\u5408\u9759\u6001\u5206\u6790\u5DE5\u5177\u786E\u8BA4"},metadata:{exportCount:s.length,importerCount:o.importers.size}});}}return t}function q(e,t){let i=m(e);for(let n of t)if(n.includes("*.spec.")&&/\.spec\.(tsx?|jsx?)$/.test(i)||n.includes("*.test.")&&/\.test\.(tsx?|jsx?)$/.test(i)||n.includes("__tests__")&&i.includes("__tests__")||n.includes("node_modules")&&i.includes("node_modules"))return true;return false}function J(e,t,i){let n=[],o=new Set(t.map(r=>r.toLowerCase()));function s(r){let a;try{a=R.readdirSync(r,{withFileTypes:!0});}catch{return}for(let c of a){let u=b.join(r,c.name),f=m(b.relative(e,u));if(c.isDirectory()){if(q(f+"/",i))continue;s(u);continue}if(!c.isFile()||q(f,i))continue;let l=b.extname(c.name).toLowerCase();o.has(l)&&n.push(m(u));}}return s(e),n}async function V(e){if(!e.options.scanImportPatterns)return [];let t=[],i=0;for(let n of e.sourceFiles){let o=B(n);if(!o)continue;let s=S(e.projectRoot,n);for(let[u,f,l,p]of T){if(!u.test(o))continue;let d=Math.round(p*.35);t.push({id:g("import-pattern",i++),category:"import-pattern",severity:l,title:`\u5168\u91CF/\u4F4E\u6548\u5BFC\u5165: ${f}`,file:s,detail:`${s} \u5B58\u5728\u53EF\u80FD\u5BFC\u81F4 tree-shake \u5931\u6548\u7684\u5BFC\u5165\uFF08${f}\uFF09\u3002`,suggestion:k[f]??"\u6539\u4E3A\u6309\u9700\u5BFC\u5165",cost:{transferKb:d,parseMsEstimate:y(d),description:`\u9884\u4F30 gzip \u540E\u7EA6 ${d} KB`},metadata:{packageName:f}});}let r=/export\s+default/.test(o),a=/export\s+(const|function|class|type|interface|\{)/.test(o);/export\s+\*\s+from/.test(o)&&t.push({id:g("import-pattern",i++),category:"import-pattern",severity:"medium",title:"Barrel \u6587\u4EF6\u53EF\u80FD\u5BFC\u81F4 tree-shake \u5931\u6548",file:s,detail:`${s} \u4F7F\u7528\u4E86 export * from \u6A21\u5F0F\uFF0C\u5BB9\u6613\u5BFC\u81F4\u672A\u4F7F\u7528\u7684\u6A21\u5757\u88AB\u4E00\u5E76\u6253\u5305\u3002`,suggestion:"\u6539\u4E3A\u663E\u5F0F\u5177\u540D\u5BFC\u51FA\uFF0C\u6216\u76F4\u63A5\u4ECE\u6E90\u6587\u4EF6\u6309\u9700\u5BFC\u5165\u3002",cost:{transferKb:15,parseMsEstimate:y(15),description:"Barrel \u6587\u4EF6\u95F4\u63A5\u5F15\u5165\u7684\u5197\u4F59\u4F53\u79EF\u96BE\u4EE5\u7CBE\u786E\u91CF\u5316\uFF0C\u6B64\u4E3A\u4FDD\u5B88\u4F30\u8BA1"}}),!r&&!a&&s.includes("/components/")&&o.trim().length<20&&t.push({id:g("static-unused",i++),category:"static-unused-js",severity:"low",title:"\u7591\u4F3C\u7A7A\u7EC4\u4EF6\u6587\u4EF6",file:s,detail:`${s} \u51E0\u4E4E\u6CA1\u6709\u6709\u6548\u5185\u5BB9\uFF0C\u53EF\u80FD\u662F\u9057\u7559\u6587\u4EF6\u3002`,suggestion:"\u786E\u8BA4\u662F\u5426\u4ECD\u9700\u8981\uFF0C\u4E0D\u9700\u8981\u5219\u5220\u9664\u3002",cost:{transferKb:0,parseMsEstimate:0,description:"\u7EF4\u62A4\u6210\u672C\u6D6A\u8D39"}});}return t}async function M(e){let t=b.resolve(e.projectRoot,e.options.root??"src"),i=[...j,...(e.options.include??[]).map(n=>b.extname(n)).filter(Boolean)];return J(t,i,e.options.exclude??[])}function X(e){let t=new Set,i=[...e.entries];for(;i.length;){let n=i.shift();if(t.has(n))continue;t.add(n);let o=e.parsedModules.get(n);if(o)for(let s of o.importedIds)t.has(s)||i.push(s);}return t}function Q(e){if(!e.options.scanUnreachable)return [];let t=[],i=X(e),n=0,o=new Set;for(let s of i){if(s.startsWith("\0")||s.includes("virtual:"))continue;let r=b.isAbsolute(s)?m(s):m(b.resolve(e.projectRoot,s));o.add(r);}for(let s of e.sourceFiles){let r=m(s);if(e.entries.some(h=>m(b.resolve(h))===r))continue;let c=[...o].some(h=>h===r||r.endsWith(h)||h.endsWith(r)),u=e.parsedModules.get(r)??e.parsedModules.get(s),f=u&&u.importers.size>0;if(c||f)continue;let l=D(s),p=S(e.projectRoot,s),d=/pages?|views?|components?|routes?/i.test(p)&&/\.(vue|tsx|jsx)$/.test(p);t.push({id:g("unreachable",n++),category:"unreachable-component",severity:d?"high":"medium",title:d?"\u4E0D\u53EF\u8FBE\u9875\u9762/\u7EC4\u4EF6":"\u4E0D\u53EF\u8FBE\u6E90\u6587\u4EF6",file:p,detail:`${p} \u672A\u51FA\u73B0\u5728\u4EFB\u4F55\u6784\u5EFA\u5165\u53E3\u7684 module graph \u4E2D\uFF0C\u5C5E\u4E8E\u9759\u6001\u4E0D\u53EF\u8FBE\u4EE3\u7801\u3002`,suggestion:d?"\u82E5\u4E1A\u52A1\u5DF2\u4E0B\u7EBF\uFF0C\u5220\u9664\u8BE5\u9875\u9762/\u7EC4\u4EF6\u53CA\u5173\u8054\u9759\u6001\u8D44\u6E90\u3002":"\u786E\u8BA4\u662F\u5426\u4ECD\u88AB\u52A8\u6001\u8DEF\u5F84\u5F15\u7528\uFF1B\u82E5\u65E0\uFF0C\u8BF7\u5220\u9664\u6216\u79FB\u51FA src\u3002",cost:{transferKb:l,parseMsEstimate:y(l),description:`\u6587\u4EF6\u672C\u4F53 ${l} KB\uFF1B\u82E5\u88AB glob \u626B\u5165\u53EF\u80FD\u4EA7\u751F\u66F4\u5927 chunk \u6D6A\u8D39`},metadata:{isPageOrComponent:d}});}return t}function Y(e){let t=[],i=0,n=e.bundleFiles.filter(a=>a.isEntry),o=new Set(e.bundleFiles.filter(a=>a.type==="chunk").map(a=>a.fileName)),s=new Set,r=n.map(a=>a.fileName);for(;r.length;){let a=r.shift();if(s.has(a))continue;s.add(a);let c=e.bundleFiles.find(u=>u.fileName===a);if(c)for(let u of c.imports??[])o.has(u)&&!s.has(u)&&r.push(u);}for(let a of e.bundleFiles){if(a.type!=="chunk"||a.isEntry||s.has(a.fileName))continue;let c=Math.round(a.size/1024*100)/100,u=a.fileName.includes("legacy")||a.fileName.includes("admin");t.push({id:g("orphan-chunk",i++),category:"static-unused-js",severity:"low",title:"\u72EC\u7ACB\u5F02\u6B65 chunk\uFF08\u672A\u88AB\u9759\u6001\u5F15\u7528\uFF09",file:a.fileName,detail:`\u4EA7\u7269 chunk ${a.fileName} (${c} KB) \u4EC5\u901A\u8FC7\u52A8\u6001 import \u52A0\u8F7D\uFF0C\u672C\u8EAB\u4E0D\u662F\u6D6A\u8D39\uFF0C\u4F46\u82E5\u88AB\u5168\u7AD9 prefetch \u5219\u4F1A\u4EA7\u751F\u52A0\u8F7D\u6D6A\u8D39\u3002`,suggestion:"\u68C0\u67E5\u662F\u5426\u5BF9\u8BE5 chunk \u914D\u7F6E\u4E86\u4E0D\u5FC5\u8981\u7684 prefetch/preload\u3002",cost:{transferKb:0,parseMsEstimate:0,description:"\u672C\u8EAB\u4E3A\u6309\u9700\u52A0\u8F7D\uFF1B\u6D6A\u8D39\u53D6\u51B3\u4E8E prefetch \u7B56\u7565"},metadata:{chunkSizeKb:c,dynamicOnly:true,suspiciousPrefetch:u}});}return t}async function C(e){let t=await Promise.all([V(e),Promise.resolve(Q(e)),Promise.resolve(Y(e)),Promise.resolve(L(e)),Promise.resolve(U(e)),Promise.resolve(H(e))]);return ae(t.flat())}function ae(e){let t=new Set;return e.filter(i=>{let n=`${i.category}:${i.file}:${i.title}`;return t.has(n)?false:(t.add(n),true)})}function $(e){let t={high:0,medium:1,low:2};return [...e].sort((i,n)=>{let o=t[i.severity]-t[n.severity];return o!==0?o:n.cost.transferKb-i.cost.transferKb})}function Z(e,t,i,n,o){let s=m(t),r=e.get(s)??{id:s,importers:new Set,importedIds:new Set,dynamicImporters:new Set,isEntry:false,code:void 0};r.isEntry=r.isEntry||o,i&&(r.code=i);for(let a of n)r.importedIds.add(m(a));e.set(s,r);for(let a of n){let c=m(a),u=e.get(c)??{id:c,importers:new Set,importedIds:new Set,dynamicImporters:new Set,isEntry:false};u.importers.add(s),e.set(c,u);}}function ee(e){let t=[];for(let[i,n]of Object.entries(e))if(n.type==="chunk"){let o=n;t.push({fileName:i,type:"chunk",size:Buffer.byteLength(o.code,"utf-8"),modules:Object.keys(o.modules??{}),isEntry:o.isEntry??false,imports:o.imports,dynamicImports:o.dynamicImports});}else {let s=n.source,r=typeof s=="string"?Buffer.byteLength(s,"utf-8"):s instanceof Uint8Array?s.byteLength:0;t.push({fileName:i,type:"asset",size:r});}return t}function F(e,t){let i=ue(),n={high:0,medium:0,low:0},o=0,s=0;for(let r of e)i[r.category]++,n[r.severity]++,!r.metadata?.informational&&(o+=r.cost.transferKb,s+=r.cost.parseMsEstimate);return {generatedAt:new Date().toISOString(),projectRoot:t.projectRoot,mode:t.mode,outDir:t.outDir,summary:{totalIssues:e.length,totalWasteTransferKb:Math.round(o*100)/100,totalParseMsEstimate:Math.round(s*10)/10,byCategory:i,bySeverity:n},issues:e}}function ue(){return {"static-unused-js":0,"unreachable-component":0,"cache-miss":0,"duplicate-dependency":0,"import-pattern":0,"prefetch-waste":0}}function te(e,t){let{summary:i,issues:n}=e,o=n.slice(0,5);if(console.log(`
|
|
1
|
+
import E from'fs';import S from'path';var T={root:"src",reportDir:"resource-waste",wasteThresholdKb:300,failOnThreshold:false,htmlReport:true,scanImportPatterns:true,scanUnusedImports:true,scanUnreachable:true,scanCacheStrategy:true,scanDuplicateDeps:true,include:[],exclude:["**/*.spec.*","**/*.test.*","**/__tests__/**","**/node_modules/**"],silent:false},_=[".ts",".tsx",".js",".jsx",".vue",".svelte"],W=[".js",".css",".woff",".woff2",".ttf",".png",".jpg",".jpeg",".gif",".webp",".svg",".ico"],D=/\.[a-f0-9]{8,}\.(js|css|woff2?|ttf|png|jpe?g|gif|webp|svg|ico)$/i,L=[[/import\s+_\s+from\s+['"]lodash['"]/,"lodash","high",68],[/import\s+\*\s+as\s+\w+\s+from\s+['"]lodash['"]/,"lodash","high",68],[/import\s+\w+\s+from\s+['"]moment['"]/,"moment","high",65],[/import\s+\*\s+as\s+\w+\s+from\s+['"]antd['"]/,"antd","high",200],[/import\s+\*\s+as\s+\w+\s+from\s+['"]@ant-design\/icons['"]/,"@ant-design/icons","medium",80],[/import\s+['"][^'"]+\.css['"]\s*;\s*\/\/\s*unused/i,"css","medium",10]],U={lodash:"\u6539\u4E3A import debounce from 'lodash-es/debounce' \u6216\u6309\u9700\u5BFC\u5165",moment:"\u6539\u7528 dayjs \u6216 date-fns \u6309\u9700\u5BFC\u5165",antd:"\u6539\u7528 antd \u6309\u9700\u5BFC\u5165\u6216 unplugin-vue-components","@ant-design/icons":"\u6539\u4E3A\u6309\u9700\u5BFC\u5165\u5177\u4F53\u56FE\u6807",css:"\u79FB\u9664\u672A\u4F7F\u7528\u7684 CSS \u526F\u4F5C\u7528 import"};function p(e){return e.split(S.sep).join("/")}function M(e){try{return E.readFileSync(e,"utf-8")}catch{return ""}}function P(e){try{let t=E.statSync(e);return Math.round(t.size/1024*100)/100}catch{return 0}}function y(e){return Math.round(e*.45*10)/10}function q(e){let t=p(e),n=t.lastIndexOf("node_modules/");if(n===-1)return null;let o=t.slice(0,n+13),s=t.slice(n+13).split("/");if(s[0]?.startsWith("@")&&s[1]){let i=`${s[0]}/${s[1]}`,a=H(S.join(o,i));return {name:i,version:a}}if(s[0]){let i=s[0],a=H(S.join(o,i));return {name:i,version:a}}return null}function H(e){try{let t=S.join(e,"package.json");return JSON.parse(E.readFileSync(t,"utf-8")).version}catch{return}}function w(e,t){return S.isAbsolute(t)?t:S.resolve(e,t)}function h(e,t){return `${e}-${t}`}function j(e){E.mkdirSync(e,{recursive:true});}function R(e,t){return p(S.relative(e,t))}function V(e){if(!e.options.scanCacheStrategy)return [];let t=[],n=0;for(let r of e.bundleFiles){if(r.type!=="asset")continue;let s=de(r.fileName);if(!W.includes(s))continue;let i=Math.round(r.size/1024*100)/100;if(!D.test(r.fileName)){let c=/\.(woff2?|ttf|otf)$/i.test(r.fileName),u=i>100;t.push({id:h("cache-miss",n++),category:"cache-miss",severity:u||c?"high":"medium",title:"\u4EA7\u7269\u6587\u4EF6\u540D\u7F3A\u5C11 content hash",file:r.fileName,detail:`\u9759\u6001\u8D44\u6E90 ${r.fileName} (${i} KB) \u672A\u5305\u542B content hash\uFF0C\u6D4F\u89C8\u5668\u96BE\u4EE5\u5B89\u5168\u957F\u671F\u7F13\u5B58\uFF0C\u56DE\u8BBF\u7528\u6237\u53EF\u80FD\u91CD\u590D\u4E0B\u8F7D\u3002`,suggestion:c?"\u914D\u7F6E vite build.assetsInlineLimit \u4E0E rollup assetFileNames \u542B [hash]\uFF1BCDN \u8BBE\u7F6E immutable \u7F13\u5B58\u3002":"\u5728 vite.config \u4E2D\u8BBE\u7F6E build.rollupOptions.output.assetFileNames \u5305\u542B [hash] \u5360\u4F4D\u7B26\u3002",cost:{transferKb:i,parseMsEstimate:c?y(i*.1):0,description:u?`\u56DE\u8BBF\u7528\u6237\u53EF\u80FD\u91CD\u590D\u4F20\u8F93 ${i} KB`:"\u7F13\u5B58\u547D\u4E2D\u7387\u4F4E\u5BFC\u81F4\u91CD\u590D\u9A8C\u8BC1\u6210\u672C"},metadata:{sizeKb:i,extension:s,hasContentHash:false}});}}let o=e.bundleFiles.filter(r=>r.fileName.endsWith(".html"));for(let r of o)t.push({id:h("cache-info",n++),category:"cache-miss",severity:"low",title:"HTML \u5165\u53E3\u5E94\u4F7F\u7528\u77ED\u7F13\u5B58",file:r.fileName,detail:`${r.fileName} \u662F HTML \u5165\u53E3\uFF0C\u901A\u5E38\u5E94\u914D\u7F6E\u8F83\u77ED max-age \u6216 no-cache\uFF0C\u4EE5\u786E\u4FDD\u7528\u6237\u83B7\u53D6\u6700\u65B0\u8D44\u6E90\u5F15\u7528\u3002`,suggestion:"\u90E8\u7F72\u65F6\u4E3A HTML \u8BBE\u7F6E Cache-Control: no-cache\uFF1BJS/CSS \u7B49\u5E26 hash \u8D44\u6E90\u8BBE\u7F6E\u957F\u671F\u7F13\u5B58\u3002",cost:{transferKb:Math.round(r.size/1024*100)/100,parseMsEstimate:0,description:"\u914D\u7F6E\u5EFA\u8BAE\uFF0C\u975E\u6D6A\u8D39"},metadata:{informational:true}});return t}function de(e){let t=e.lastIndexOf(".");return t===-1?"":e.slice(t).toLowerCase()}function J(e){if(!e.options.scanDuplicateDeps)return [];let t=new Map,n=0,o=[];for(let r of e.bundleFiles)if(r.type==="chunk")for(let s of r.modules??[]){let i=q(s);if(!i?.version)continue;t.has(i.name)||t.set(i.name,new Map);let a=t.get(i.name);a.set(i.version,(a.get(i.version)??0)+1);}for(let[r,s]of t){if(s.size<=1)continue;let i=[...s.entries()],a=i.reduce((u,[,g])=>u+g,0),c=Math.max(5,Math.round(a*3));o.push({id:h("duplicate-dep",n++),category:"duplicate-dependency",severity:"medium",title:`\u91CD\u590D\u4F9D\u8D56\u7248\u672C: ${r}`,file:r,detail:`\u4F9D\u8D56 ${r} \u5728\u4EA7\u7269\u4E2D\u51FA\u73B0\u591A\u4E2A\u7248\u672C: ${i.map(([u,g])=>`${u}(${g}\u6B21)`).join(", ")}\u3002`,suggestion:`\u4F7F\u7528 package.json overrides / pnpm.overrides \u7EDF\u4E00 ${r} \u7248\u672C\uFF0C\u907F\u514D\u91CD\u590D\u6253\u5305\u4E0E\u7F13\u5B58\u5931\u6548\u3002`,cost:{transferKb:c,parseMsEstimate:y(c),description:`\u9884\u4F30\u91CD\u590D\u4F20\u8F93\u7EA6 ${c} KB`},metadata:{packageName:r,versions:Object.fromEntries(i)}});}return o}function G(e){let t=[],n=0;for(let[o,r]of e.parsedModules){if(o.includes("node_modules")||o.startsWith("\0")||!r.code)continue;let s=[...r.code.matchAll(/export\s+(?:const|function|class|type|interface)\s+(\w+)/g)];if(s.length!==0&&s.length>=8&&r.importers.size<=1){let i=Math.round(r.code.length/1024*.3);t.push({id:h("unused-export",n++),category:"static-unused-js",severity:"low",title:"\u7591\u4F3C\u8FC7\u591A\u672A\u4F7F\u7528 export",file:o,detail:`${o} \u5BFC\u51FA\u4E86 ${s.length} \u4E2A\u7B26\u53F7\uFF0C\u4F46\u4EC5\u88AB ${r.importers.size} \u4E2A\u6A21\u5757\u5F15\u7528\uFF0C\u53EF\u80FD\u5B58\u5728\u672A\u4F7F\u7528\u7684 export\u3002`,suggestion:"\u4F7F\u7528 Knip \u6216 ESLint \u8FDB\u4E00\u6B65\u786E\u8BA4\uFF0C\u79FB\u9664\u672A\u4F7F\u7528\u7684 export \u4EE5\u6539\u5584 tree-shaking\u3002",cost:{transferKb:i,parseMsEstimate:y(i),description:"\u4FDD\u5B88\u4F30\u8BA1\uFF0C\u9700\u7ED3\u5408\u9759\u6001\u5206\u6790\u5DE5\u5177\u786E\u8BA4"},metadata:{exportCount:s.length,importerCount:r.importers.size}});}}return t}function X(e,t){let n=p(e);for(let o of t)if(o.includes("*.spec.")&&/\.spec\.(tsx?|jsx?)$/.test(n)||o.includes("*.test.")&&/\.test\.(tsx?|jsx?)$/.test(n)||o.includes("__tests__")&&n.includes("__tests__")||o.includes("node_modules")&&n.includes("node_modules"))return true;return false}function Z(e,t,n){let o=[],r=new Set(t.map(i=>i.toLowerCase()));function s(i){let a;try{a=E.readdirSync(i,{withFileTypes:!0});}catch{return}for(let c of a){let u=S.join(i,c.name),g=p(S.relative(e,u));if(c.isDirectory()){if(X(g+"/",n))continue;s(u);continue}if(!c.isFile()||X(g,n))continue;let l=S.extname(c.name).toLowerCase();r.has(l)&&o.push(p(u));}}return s(e),o}async function Y(e){if(!e.options.scanImportPatterns)return [];let t=[],n=0;for(let o of e.sourceFiles){let r=M(o);if(!r)continue;let s=R(e.projectRoot,o);for(let[u,g,l,m]of L){if(!u.test(r))continue;let f=Math.round(m*.35);t.push({id:h("import-pattern",n++),category:"import-pattern",severity:l,title:`\u5168\u91CF/\u4F4E\u6548\u5BFC\u5165: ${g}`,file:s,detail:`${s} \u5B58\u5728\u53EF\u80FD\u5BFC\u81F4 tree-shake \u5931\u6548\u7684\u5BFC\u5165\uFF08${g}\uFF09\u3002`,suggestion:U[g]??"\u6539\u4E3A\u6309\u9700\u5BFC\u5165",cost:{transferKb:f,parseMsEstimate:y(f),description:`\u9884\u4F30 gzip \u540E\u7EA6 ${f} KB`},metadata:{packageName:g}});}let i=/export\s+default/.test(r),a=/export\s+(const|function|class|type|interface|\{)/.test(r);/export\s+\*\s+from/.test(r)&&t.push({id:h("import-pattern",n++),category:"import-pattern",severity:"medium",title:"Barrel \u6587\u4EF6\u53EF\u80FD\u5BFC\u81F4 tree-shake \u5931\u6548",file:s,detail:`${s} \u4F7F\u7528\u4E86 export * from \u6A21\u5F0F\uFF0C\u5BB9\u6613\u5BFC\u81F4\u672A\u4F7F\u7528\u7684\u6A21\u5757\u88AB\u4E00\u5E76\u6253\u5305\u3002`,suggestion:"\u6539\u4E3A\u663E\u5F0F\u5177\u540D\u5BFC\u51FA\uFF0C\u6216\u76F4\u63A5\u4ECE\u6E90\u6587\u4EF6\u6309\u9700\u5BFC\u5165\u3002",cost:{transferKb:15,parseMsEstimate:y(15),description:"Barrel \u6587\u4EF6\u95F4\u63A5\u5F15\u5165\u7684\u5197\u4F59\u4F53\u79EF\u96BE\u4EE5\u7CBE\u786E\u91CF\u5316\uFF0C\u6B64\u4E3A\u4FDD\u5B88\u4F30\u8BA1"}}),!i&&!a&&s.includes("/components/")&&r.trim().length<20&&t.push({id:h("static-unused",n++),category:"static-unused-js",severity:"low",title:"\u7591\u4F3C\u7A7A\u7EC4\u4EF6\u6587\u4EF6",file:s,detail:`${s} \u51E0\u4E4E\u6CA1\u6709\u6709\u6548\u5185\u5BB9\uFF0C\u53EF\u80FD\u662F\u9057\u7559\u6587\u4EF6\u3002`,suggestion:"\u786E\u8BA4\u662F\u5426\u4ECD\u9700\u8981\uFF0C\u4E0D\u9700\u8981\u5219\u5220\u9664\u3002",cost:{transferKb:0,parseMsEstimate:0,description:"\u7EF4\u62A4\u6210\u672C\u6D6A\u8D39"}});}return t}async function z(e){let t=S.resolve(e.projectRoot,e.options.root??"src"),n=[..._,...(e.options.include??[]).map(o=>S.extname(o)).filter(Boolean)];return Z(t,n,e.options.exclude??[])}function ee(e){let t=new Set,n=[...e.entries];for(;n.length;){let o=n.shift();if(t.has(o))continue;t.add(o);let r=e.parsedModules.get(o);if(r)for(let s of r.importedIds)t.has(s)||n.push(s);}return t}function he(e){let t=new Set;for(let n of e.bundleFiles)if(!(n.type!=="chunk"||!n.modules))for(let o of n.modules)t.add(p(o));return t}function ye(e,t){if(t.size===0)return false;let n=p(e);for(let o of t)if(o===n||o.endsWith(n)||n.endsWith(o))return true;return false}function be(e,t){if(!t.startsWith("."))return null;let n=S.resolve(S.dirname(e),t);if(S.extname(n))return E.existsSync(n)?p(n):null;let o=[".ts",".tsx",".js",".jsx",".vue",".svelte",".mjs",".cjs"];for(let r of o){let s=n+r;if(E.existsSync(s))return p(s)}for(let r of o){let s=S.join(n,"index"+r);if(E.existsSync(s))return p(s)}return null}function xe(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}function Ie(e){let t=e.match(/<script(?:\s+[^>]*)?>([\s\S]*?)<\/script>/i)?.[1]??"",n=e.match(/<template(?:\s+[^>]*)?>([\s\S]*?)<\/template>/i)?.[1]??"";return {script:t,template:n}}function Se(e){return e.replace(/import\s+(?:type\s+)?[\s\S]*?\s+from\s+['"][^'"]+['"]\s*;?/g,"")}function te(e){return e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/.*$/gm,"")}function ve(e){let t=e.trim();if(!t||t.startsWith("type "))return [];let n=[],o=t.match(/^\*\s+as\s+(\w+)(?:\s*,\s*(.+))?$/);if(o)return n.push(o[1]),o[2]&&n.push(...se(o[2])),n;let r=t.indexOf("{");if(r===-1){let i=t.replace(/,$/,"").trim();return i&&n.push(i),n}let s=t.slice(0,r).replace(/,$/,"").trim();return s&&n.push(s),n.push(...se(t.slice(r))),n}function se(e){let t=[],n=e.match(/\{([\s\S]*)\}/)?.[1]??e;for(let o of n.split(",")){let r=o.trim();if(!r||r.startsWith("type "))continue;let s=r.match(/(?:\w+\s+as\s+)?(\w+)\s*$/);s&&t.push(s[1]);}return t}function Re(e){let t=[],n=/import\s+(type\s+)?([\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g,o;for(;(o=n.exec(e))!==null;){if(o[1])continue;let r=o[2].trim(),s=o[3];if(!(!r||r.startsWith("type ")))for(let i of ve(r))t.push({localName:i,specifier:s});}return t}function we(e,t,n){let o=te(t),r=new RegExp(`\\b${e}\\b`);if(r.test(o))return true;if(!n)return false;let s=te(n);if(r.test(s))return true;let i=xe(e);return !!(i!==e.toLowerCase()&&new RegExp(`<${i}[\\s/>]`,"i").test(s))}function ne(e){return /\.(vue|tsx|jsx)$/i.test(e)&&/pages?|views?|components?|routes?/i.test(e)}function $e(e,t,n){let o=ne(e);return t?"medium":n?"low":o?"medium":"low"}function oe(e){if(!e.options.scanUnusedImports)return [];let t=[],n=he(e),o=n.size>0,r=0;for(let s of e.sourceFiles){let i=M(s);if(!i)continue;let a=s.endsWith(".vue"),{script:c,template:u}=a?Ie(i):{script:i,template:""},g=Re(c);if(g.length===0)continue;let l=Se(a?c:i),m=R(e.projectRoot,s);for(let{localName:f,specifier:d}of g){if(!d.startsWith(".")||we(f,l,u))continue;let b=be(s,d),x=b?R(e.projectRoot,b):d,I=b?ye(b,n):false,me=$e(x,I,o),$=b?P(b):0,fe=o?I?`\u8BE5\u6A21\u5757\u4ECD\u51FA\u73B0\u5728\u6784\u5EFA\u4EA7\u7269\u4E2D\uFF08\u7EA6 ${$} KB\uFF09\u3002`:"\u6784\u5EFA\u4EA7\u7269\u4E2D\u672A\u53D1\u73B0\u8BE5\u6A21\u5757\uFF0C\u53EF\u80FD\u5DF2\u88AB tree-shake\uFF0C\u4F46\u6E90\u7801 import \u4ECD\u5E94\u6E05\u7406\u3002":"\u5F00\u53D1\u6A21\u5F0F\u65E0 bundle \u6570\u636E\uFF0C\u8BF7\u6267\u884C vite build \u83B7\u53D6\u4EA7\u7269\u4EA4\u53C9\u9A8C\u8BC1\u3002";t.push({id:h("dead-import",r++),category:"dead-import",severity:me,title:`\u672A\u4F7F\u7528\u7684\u9759\u6001 import: ${f}`,file:m,detail:`${m} \u5F15\u5165\u4E86 \`${x}\`\uFF08\u7ED1\u5B9A \`${f}\`\uFF09\u4F46\u672A\u5728\u6587\u4EF6\u5185\u4F7F\u7528\u3002${fe}`,suggestion:ne(x)?`\u5220\u9664\u5197\u4F59 import\uFF1B\u82E5\u8DEF\u7531/\u9875\u9762\u5DF2\u4E0B\u7EBF\uFF0C\u786E\u8BA4 \`${x}\` \u662F\u5426\u4E5F\u53EF\u79FB\u9664\u3002`:`\u5220\u9664\u672A\u4F7F\u7528\u7684 import \`${f}\` from '${d}'\u3002`,cost:{transferKb:I?$:0,parseMsEstimate:I?y($):0,description:I?`\u5197\u4F59 import \u5BFC\u81F4\u7EA6 ${$} KB \u8FDB\u5165\u4EA7\u7269`:"\u4E3B\u8981\u4E3A\u7EF4\u62A4\u6027\u6D6A\u8D39\uFF1B\u5F53\u524D\u53EF\u80FD\u672A\u589E\u52A0\u4F20\u8F93\u4F53\u79EF"},metadata:{binding:f,specifier:d,importedModule:x,inBundle:I}});}}return t}function re(e){if(!e.options.scanUnreachable)return [];let t=[],n=ee(e),o=0,r=new Set;for(let s of n){if(s.startsWith("\0")||s.includes("virtual:"))continue;let i=S.isAbsolute(s)?p(s):p(S.resolve(e.projectRoot,s));r.add(i);}for(let s of e.sourceFiles){let i=p(s);if(e.entries.some(d=>p(S.resolve(d))===i))continue;let c=[...r].some(d=>d===i||i.endsWith(d)||d.endsWith(i)),u=e.parsedModules.get(i)??e.parsedModules.get(s),g=u&&u.importers.size>0;if(c||g)continue;let l=P(s),m=R(e.projectRoot,s),f=/pages?|views?|components?|routes?/i.test(m)&&/\.(vue|tsx|jsx)$/.test(m);t.push({id:h("unreachable",o++),category:"unreachable-component",severity:f?"high":"medium",title:f?"\u4E0D\u53EF\u8FBE\u9875\u9762/\u7EC4\u4EF6":"\u4E0D\u53EF\u8FBE\u6E90\u6587\u4EF6",file:m,detail:`${m} \u672A\u51FA\u73B0\u5728\u4EFB\u4F55\u6784\u5EFA\u5165\u53E3\u7684 module graph \u4E2D\uFF0C\u5C5E\u4E8E\u9759\u6001\u4E0D\u53EF\u8FBE\u4EE3\u7801\u3002`,suggestion:f?"\u82E5\u4E1A\u52A1\u5DF2\u4E0B\u7EBF\uFF0C\u5220\u9664\u8BE5\u9875\u9762/\u7EC4\u4EF6\u53CA\u5173\u8054\u9759\u6001\u8D44\u6E90\u3002":"\u786E\u8BA4\u662F\u5426\u4ECD\u88AB\u52A8\u6001\u8DEF\u5F84\u5F15\u7528\uFF1B\u82E5\u65E0\uFF0C\u8BF7\u5220\u9664\u6216\u79FB\u51FA src\u3002",cost:{transferKb:l,parseMsEstimate:y(l),description:`\u6587\u4EF6\u672C\u4F53 ${l} KB\uFF1B\u82E5\u88AB glob \u626B\u5165\u53EF\u80FD\u4EA7\u751F\u66F4\u5927 chunk \u6D6A\u8D39`},metadata:{isPageOrComponent:f}});}return t}function ie(e){let t=[],n=0,o=e.bundleFiles.filter(a=>a.isEntry),r=new Set(e.bundleFiles.filter(a=>a.type==="chunk").map(a=>a.fileName)),s=new Set,i=o.map(a=>a.fileName);for(;i.length;){let a=i.shift();if(s.has(a))continue;s.add(a);let c=e.bundleFiles.find(u=>u.fileName===a);if(c)for(let u of c.imports??[])r.has(u)&&!s.has(u)&&i.push(u);}for(let a of e.bundleFiles){if(a.type!=="chunk"||a.isEntry||s.has(a.fileName))continue;let c=Math.round(a.size/1024*100)/100,u=a.fileName.includes("legacy")||a.fileName.includes("admin");t.push({id:h("orphan-chunk",n++),category:"static-unused-js",severity:"low",title:"\u72EC\u7ACB\u5F02\u6B65 chunk\uFF08\u672A\u88AB\u9759\u6001\u5F15\u7528\uFF09",file:a.fileName,detail:`\u4EA7\u7269 chunk ${a.fileName} (${c} KB) \u4EC5\u901A\u8FC7\u52A8\u6001 import \u52A0\u8F7D\uFF0C\u672C\u8EAB\u4E0D\u662F\u6D6A\u8D39\uFF0C\u4F46\u82E5\u88AB\u5168\u7AD9 prefetch \u5219\u4F1A\u4EA7\u751F\u52A0\u8F7D\u6D6A\u8D39\u3002`,suggestion:"\u68C0\u67E5\u662F\u5426\u5BF9\u8BE5 chunk \u914D\u7F6E\u4E86\u4E0D\u5FC5\u8981\u7684 prefetch/preload\u3002",cost:{transferKb:0,parseMsEstimate:0,description:"\u672C\u8EAB\u4E3A\u6309\u9700\u52A0\u8F7D\uFF1B\u6D6A\u8D39\u53D6\u51B3\u4E8E prefetch \u7B56\u7565"},metadata:{chunkSizeKb:c,dynamicOnly:true,suspiciousPrefetch:u}});}return t}async function A(e){let t=await Promise.all([Y(e),Promise.resolve(oe(e)),Promise.resolve(re(e)),Promise.resolve(ie(e)),Promise.resolve(V(e)),Promise.resolve(J(e)),Promise.resolve(G(e))]);return Ee(t.flat())}function Ee(e){let t=new Set;return e.filter(n=>{let o=`${n.category}:${n.file}:${n.title}`;return t.has(o)?false:(t.add(o),true)})}function K(e){let t={high:0,medium:1,low:2};return [...e].sort((n,o)=>{let r=t[n.severity]-t[o.severity];return r!==0?r:o.cost.transferKb-n.cost.transferKb})}function ae(e,t,n,o,r){let s=p(t),i=e.get(s)??{id:s,importers:new Set,importedIds:new Set,dynamicImporters:new Set,isEntry:false,code:void 0};i.isEntry=i.isEntry||r,n&&(i.code=n);for(let a of o)i.importedIds.add(p(a));e.set(s,i);for(let a of o){let c=p(a),u=e.get(c)??{id:c,importers:new Set,importedIds:new Set,dynamicImporters:new Set,isEntry:false};u.importers.add(s),e.set(c,u);}}function ue(e){let t=[];for(let[n,o]of Object.entries(e))if(o.type==="chunk"){let r=o;t.push({fileName:n,type:"chunk",size:Buffer.byteLength(r.code,"utf-8"),modules:Object.keys(r.modules??{}),isEntry:r.isEntry??false,imports:r.imports,dynamicImports:r.dynamicImports});}else {let s=o.source,i=typeof s=="string"?Buffer.byteLength(s,"utf-8"):s instanceof Uint8Array?s.byteLength:0;t.push({fileName:n,type:"asset",size:i});}return t}function B(e,t){let n=Me(),o={high:0,medium:0,low:0},r=0,s=0;for(let i of e)n[i.category]++,o[i.severity]++,!i.metadata?.informational&&(r+=i.cost.transferKb,s+=i.cost.parseMsEstimate);return {generatedAt:new Date().toISOString(),projectRoot:t.projectRoot,mode:t.mode,outDir:t.outDir,summary:{totalIssues:e.length,totalWasteTransferKb:Math.round(r*100)/100,totalParseMsEstimate:Math.round(s*10)/10,byCategory:n,bySeverity:o},issues:e}}function Me(){return {"static-unused-js":0,"unreachable-component":0,"cache-miss":0,"duplicate-dependency":0,"import-pattern":0,"dead-import":0,"prefetch-waste":0}}function ce(e,t){let{summary:n,issues:o}=e,r=o.slice(0,5);if(console.log(`
|
|
2
2
|
[vite-plugin-resource-waste] Resource waste analysis complete
|
|
3
|
-
`),console.log(` Issues : ${
|
|
4
|
-
Top issues:`);for(let s of
|
|
5
|
-
[vite-plugin-resource-waste]`)+
|
|
6
|
-
`));let
|
|
3
|
+
`),console.log(` Issues : ${n.totalIssues}`),console.log(` Waste (est.) : ${n.totalWasteTransferKb} KB transfer / ${n.totalParseMsEstimate} ms parse`),console.log(` Threshold : ${t} KB`),r.length>0){console.log(`
|
|
4
|
+
Top issues:`);for(let s of r)console.log(` [${s.severity.toUpperCase()}] ${s.title}${s.file?` (${s.file})`:""}`);}console.log("");}var le={cyan:e=>`\x1B[36m${e}\x1B[0m`,yellow:e=>`\x1B[33m${e}\x1B[0m`};function pe(e={}){let t={...T,...e},n={},o=process.cwd(),r="dist",s="production",i=[],a=[],c=new Map,u=(l=[])=>({root:t.root??"src",projectRoot:o,outDir:r,mode:s,entries:i,sourceFiles:a,parsedModules:c,bundleFiles:l,options:t});async function g(){if(!n.bundle)return;let l=ue(n.bundle),m=u(l),f=K(await A(m)),d=B(f,{projectRoot:o,mode:s,outDir:r}),b=S.resolve(o,r,t.reportDir??"resource-waste");j(b);let x=S.join(b,"report.json");if(E.writeFileSync(x,JSON.stringify(d,null,2),"utf-8"),t.htmlReport){let{renderHtmlReport:I}=await import('./html-MLVSEXMF.js');E.writeFileSync(S.join(b,"report.html"),I(d),"utf-8");}if(t.silent||(ce(d,t.wasteThresholdKb??300),console.log(` Report JSON : ${p(S.relative(o,x))}`),t.htmlReport&&console.log(` Report HTML : ${p(S.relative(o,S.join(b,"report.html")))}`)),t.failOnThreshold&&d.summary.totalWasteTransferKb>(t.wasteThresholdKb??300))throw new Error(`[vite-plugin-resource-waste] Estimated waste ${d.summary.totalWasteTransferKb} KB exceeds threshold ${t.wasteThresholdKb} KB`)}return {name:"vite-plugin-resource-waste",enforce:"post",configResolved(l){o=l.root,r=l.build.outDir,s=l.mode;let m=l.build.rollupOptions.input;typeof m=="string"?i=[p(w(o,m))]:Array.isArray(m)?i=m.map(f=>p(w(o,f))):m&&typeof m=="object"?i=Object.values(m).map(f=>p(w(o,f))):i=[p(w(o,"index.html"))];},async buildStart(){a=await z(u());},moduleParsed(l){l.id&&ae(c,l.id,l.code??null,[...l.importedIds,...l.dynamicallyImportedIds??[]],l.isEntry);},generateBundle(l,m){n.bundle=m;},async closeBundle(){await g();},configureServer(l){l.httpServer?.once("listening",async()=>{if(s==="production")return;a=await z(u());let m=K(await A(u()));!t.silent&&m.length>0&&console.log(le.cyan(`
|
|
5
|
+
[vite-plugin-resource-waste]`)+le.yellow(` Dev mode: found ${m.length} potential resource waste issue(s). Run "vite build" for full report.
|
|
6
|
+
`));let f=S.resolve(o,"node_modules/.cache/resource-waste");j(f);let d=B(m,{projectRoot:o,mode:s,outDir:r});E.writeFileSync(S.join(f,"dev-report.json"),JSON.stringify(d,null,2),"utf-8");});}}}var Pe=pe;export{Pe as default,pe as resourceWaste};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-resource-waste",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "基于 Vite 的前端构建资源浪费分析插件,利用 Module Graph 检测未使用 JS、不可达组件、缓存失效与重复依赖,量化传输/解析开销并输出可执行优化报告,支持 CI 构建阈值拦截",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,8 +19,6 @@
|
|
|
19
19
|
"build:debug": "tsup --sourcemap",
|
|
20
20
|
"dev": "tsup --watch",
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
|
-
"example:build": "npm run build && npm run build --prefix example",
|
|
23
|
-
"example:dev": "npm run build && npm run dev --prefix example",
|
|
24
22
|
"prepublishOnly": "npm run build",
|
|
25
23
|
"release": "npm run build && npm publish",
|
|
26
24
|
"release:patch": "npm version patch && npm run release",
|
|
@@ -43,7 +41,7 @@
|
|
|
43
41
|
"无效资源检测"
|
|
44
42
|
],
|
|
45
43
|
"peerDependencies": {
|
|
46
|
-
"vite": "
|
|
44
|
+
"vite": ">=4.0.0"
|
|
47
45
|
},
|
|
48
46
|
"devDependencies": {
|
|
49
47
|
"@types/node": "^22.10.0",
|
package/dist/html-W5TFGDIJ.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var n={"static-unused-js":"\u9759\u6001\u672A\u4F7F\u7528 JS","unreachable-component":"\u4E0D\u53EF\u8FBE\u7EC4\u4EF6/\u9875\u9762","cache-miss":"\u7F13\u5B58\u7B56\u7565\u98CE\u9669","duplicate-dependency":"\u91CD\u590D\u4F9D\u8D56","import-pattern":"\u5BFC\u5165\u6A21\u5F0F\u6D6A\u8D39","prefetch-waste":"\u9884\u52A0\u8F7D\u6D6A\u8D39"},i={high:"\u9AD8",medium:"\u4E2D",low:"\u4F4E"},p='<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Resource Waste Report</title><style>body{font:14px/1.5 system-ui,sans-serif;margin:20px;color:#1e293b}table{border-collapse:collapse;width:100%;margin-top:12px}td,th{border:1px solid #cbd5e1;padding:8px;text-align:left;vertical-align:top}th{background:#f1f5f9}.badge{font-size:12px;font-weight:600}.cards{display:flex;flex-wrap:wrap;gap:12px;margin:12px 0}.card{border:1px solid #cbd5e1;border-radius:8px;padding:10px 14px;min-width:120px}.muted{color:#64748b;font-size:13px}code{font-size:12px;background:#f1f5f9;padding:2px 4px}</style></head><body><h1>\u524D\u7AEF\u8D44\u6E90\u6D6A\u8D39\u5206\u6790\u62A5\u544A</h1><p class="muted">{{META}}</p><div class="cards">{{SUMMARY}}</div><table><thead><tr><th>\u7EA7\u522B</th><th>\u7C7B\u522B</th><th>\u95EE\u9898</th><th>\u6587\u4EF6</th><th>\u4F20\u8F93</th><th>\u89E3\u6790</th><th>\u5EFA\u8BAE</th></tr></thead><tbody>{{ROWS}}</tbody></table><p class="muted">{{LEGEND}}</p></body></html>';function l(e){let{summary:s,issues:d}=e,o=d.map(t=>`<tr><td class="badge">${i[t.severity]}</td><td>${n[t.category]}</td><td><b>${a(t.title)}</b><br/><span class="muted">${a(t.detail)}</span></td><td><code>${a(t.file??"-")}</code></td><td>${t.cost.transferKb>0?t.cost.transferKb+" KB":"-"}</td><td>${t.cost.parseMsEstimate>0?t.cost.parseMsEstimate+" ms":"-"}</td><td>${a(t.suggestion)}</td></tr>`).join(""),c=r("\u95EE\u9898\u603B\u6570",s.totalIssues)+r("\u6D6A\u8D39\u4F20\u8F93",s.totalWasteTransferKb+" KB")+r("\u6D6A\u8D39\u89E3\u6790",s.totalParseMsEstimate+" ms")+r("\u9AD8\u5371",s.bySeverity.high);return p.replace("{{META}}",`\u751F\u6210: ${e.generatedAt} \xB7 \u6A21\u5F0F: ${e.mode}`).replace("{{SUMMARY}}",c).replace("{{ROWS}}",o||'<tr><td colspan="7">\u672A\u68C0\u6D4B\u5230\u8D44\u6E90\u6D6A\u8D39\u95EE\u9898</td></tr>').replace("{{LEGEND}}","\u6784\u5EFA\u671F\u9759\u6001\u5206\u6790\uFF1Btree-shake \u6709\u6548\u65F6\u672A\u4F7F\u7528 export \u4E0D\u4E00\u5B9A\u589E\u5927\u9996\u5305\u3002")}function r(e,s){return `<div class="card"><div class="muted">${e}</div><div><b>${s}</b></div></div>`}function a(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}export{l as renderHtmlReport};
|