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 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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}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 : ${i.totalIssues}`),console.log(` Waste (est.) : ${i.totalWasteTransferKb} KB transfer / ${i.totalParseMsEstimate} ms parse`),console.log(` Threshold : ${t} KB`),o.length>0){console.log(`
4
- Top issues:`);for(let s of o)console.log(` [${s.severity.toUpperCase()}] ${s.title}${s.file?` (${s.file})`:""}`);}console.log("");}var se={cyan:e=>`\x1B[36m${e}\x1B[0m`,yellow:e=>`\x1B[33m${e}\x1B[0m`};function oe(e={}){let t={...N,...e},i={},n=process.cwd(),o="dist",s="production",r=[],a=[],c=new Map,u=(l=[])=>({root:t.root??"src",projectRoot:n,outDir:o,mode:s,entries:r,sourceFiles:a,parsedModules:c,bundleFiles:l,options:t});async function f(){if(!i.bundle)return;let l=ee(i.bundle),p=u(l),d=$(await C(p)),h=F(d,{projectRoot:n,mode:s,outDir:o}),v=b.resolve(n,o,t.reportDir??"resource-waste");w(v);let A=b.join(v,"report.json");if(R.writeFileSync(A,JSON.stringify(h,null,2),"utf-8"),t.htmlReport){let{renderHtmlReport:re}=await import('./html-W5TFGDIJ.js');R.writeFileSync(b.join(v,"report.html"),re(h),"utf-8");}if(t.silent||(te(h,t.wasteThresholdKb??300),console.log(` Report JSON : ${m(b.relative(n,A))}`),t.htmlReport&&console.log(` Report HTML : ${m(b.relative(n,b.join(v,"report.html")))}`)),t.failOnThreshold&&h.summary.totalWasteTransferKb>(t.wasteThresholdKb??300))throw new Error(`[vite-plugin-resource-waste] Estimated waste ${h.summary.totalWasteTransferKb} KB exceeds threshold ${t.wasteThresholdKb} KB`)}return {name:"vite-plugin-resource-waste",enforce:"post",configResolved(l){n=l.root,o=l.build.outDir,s=l.mode;let p=l.build.rollupOptions.input;typeof p=="string"?r=[m(I(n,p))]:Array.isArray(p)?r=p.map(d=>m(I(n,d))):p&&typeof p=="object"?r=Object.values(p).map(d=>m(I(n,d))):r=[m(I(n,"index.html"))];},async buildStart(){a=await M(u());},moduleParsed(l){l.id&&Z(c,l.id,l.code??null,[...l.importedIds,...l.dynamicallyImportedIds??[]],l.isEntry);},generateBundle(l,p){i.bundle=p;},async closeBundle(){await f();},configureServer(l){l.httpServer?.once("listening",async()=>{if(s==="production")return;a=await M(u());let p=$(await C(u()));!t.silent&&p.length>0&&console.log(se.cyan(`
5
- [vite-plugin-resource-waste]`)+se.yellow(` Dev mode: found ${p.length} potential resource waste issue(s). Run "vite build" for full report.
6
- `));let d=b.resolve(n,"node_modules/.cache/resource-waste");w(d);let h=F(p,{projectRoot:n,mode:s,outDir:o});R.writeFileSync(b.join(d,"dev-report.json"),JSON.stringify(h,null,2),"utf-8");});}}}var ce=oe;export{ce as default,oe as resourceWaste};
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.4",
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": "^5.0.0 || ^6.0.0 || ^7.0.0"
44
+ "vite": ">=4.0.0"
47
45
  },
48
46
  "devDependencies": {
49
47
  "@types/node": "^22.10.0",
@@ -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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}export{l as renderHtmlReport};