UNPKG

@stacksjs/stx

Version:

A performant UI Framework. Powered by Bun.

500 lines (433 loc) 26.9 kB
// @bun import v from"fs";import H from"path";import{gzipSync as z}from"zlib";import{createHash as g}from"crypto";var D={".js":"js",".mjs":"js",".cjs":"js",".ts":"js",".jsx":"js",".tsx":"js",".css":"css",".scss":"css",".sass":"css",".less":"css",".html":"html",".htm":"html",".png":"image",".jpg":"image",".jpeg":"image",".gif":"image",".svg":"image",".webp":"image",".avif":"image",".ico":"image",".woff":"font",".woff2":"font",".ttf":"font",".eot":"font",".otf":"font"},b=[/node_modules/,/vendor/,/bower_components/,/\.min\./],B=[/index\.(js|ts|html)$/,/main\.(js|ts)$/,/app\.(js|ts)$/,/entry\.(js|ts)$/];async function L(Z,$={}){let{includeSourceMaps:X=!1,parseImports:j=!0,detectDuplicates:U=!0,topModulesCount:Q=20}=$;if(!v.existsSync(Z))throw Error(`Build directory not found: ${Z}`);if(!v.statSync(Z).isDirectory())throw Error(`Not a directory: ${Z}`);let K=await k(Z,X),J=[],F=[];for(let A of K)try{let O=await f(A,Z,j);J.push(O)}catch(O){F.push(`Failed to process ${A}: ${O}`)}let Y=c(J,Z),V=J.reduce((A,O)=>A+O.size,0),_=J.reduce((A,O)=>A+O.gzipSize,0),w=m(J),G=U?p(J):[],C=[...J].sort((A,O)=>O.size-A.size).slice(0,Q);return{timestamp:Date.now(),buildDir:H.resolve(Z),chunks:Y,totalSize:V,totalGzipSize:_,moduleCount:J.length,duplicates:G,byType:w,largestModules:C,warnings:F}}async function k(Z,$){let X=[];async function j(U){let Q=await v.promises.readdir(U,{withFileTypes:!0});for(let q of Q){let K=H.join(U,q.name);if(q.isDirectory()){if(!q.name.startsWith(".")&&q.name!=="node_modules")await j(K)}else if(q.isFile()){if(!$&&q.name.endsWith(".map"))continue;X.push(K)}}}return await j(Z),X}async function f(Z,$,X){let j=await v.promises.readFile(Z),U=H.relative($,Z),Q=H.extname(Z).toLowerCase(),q=j.length,K=y(j),J=h(j,Q),F=g("md5").update(j).digest("hex").slice(0,8),Y=D[Q]||"other",V=[];if(X&&Y==="js")V.push(...d(j.toString("utf-8")));return{id:U,path:U,size:q,gzipSize:K,parsedSize:J,extension:Q,type:Y,imports:V,importedBy:[],hash:F}}function y(Z){try{let $=z(Z,{level:9}).length;return Math.min($,Z.length)}catch{return Z.length}}function h(Z,$){if($.includes(".min.")||Z.length<1000)return Z.length;let j=D[$];if(j==="js")return Math.round(Z.length*0.6);if(j==="css")return Math.round(Z.length*0.7);return Z.length}function d(Z){let $=[],X=/import\s+(?:[\w*{}\s,]+\s+from\s+)?['"]([^'"]+)['"]/g,j;while((j=X.exec(Z))!==null)$.push(j[1]);let U=/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;while((j=U.exec(Z))!==null)$.push(j[1]);let Q=/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;while((j=Q.exec(Z))!==null)$.push(j[1]);return[...new Set($)]}function c(Z,$){let X=new Map;for(let U of Z){let Q=U.path.split(H.sep),q;if(Q.length===1)q=H.basename(U.path,U.extension);else q=Q[0];let K=X.get(q)||[];K.push(U),X.set(q,K)}let j=[];for(let[U,Q]of X){let q=Q.reduce((Y,V)=>Y+V.size,0),K=Q.reduce((Y,V)=>Y+V.gzipSize,0),J=Q.some((Y)=>B.some((V)=>V.test(Y.path))),F=Q.some((Y)=>b.some((V)=>V.test(Y.path)));j.push({name:U,files:Q.map((Y)=>Y.path),modules:Q,size:q,gzipSize:K,isEntry:J,isVendor:F})}return j.sort((U,Q)=>Q.size-U.size)}function m(Z){let $={js:{count:0,size:0,gzipSize:0},css:{count:0,size:0,gzipSize:0},html:{count:0,size:0,gzipSize:0},image:{count:0,size:0,gzipSize:0},font:{count:0,size:0,gzipSize:0},other:{count:0,size:0,gzipSize:0}};for(let X of Z)$[X.type].count++,$[X.type].size+=X.size,$[X.type].gzipSize+=X.gzipSize;return $}function p(Z){let $=new Map;for(let j of Z){let U=$.get(j.hash)||[];U.push(j),$.set(j.hash,U)}let X=[];for(let[,j]of $)if(j.length>1){let U=j[0];X.push({path:U.path,count:j.length,wastedBytes:U.size*(j.length-1),locations:j.map((Q)=>Q.path)})}return X.sort((j,U)=>U.wastedBytes-j.wastedBytes)}function W(Z){if(Z<1024)return`${Z} B`;if(Z<1048576)return`${(Z/1024).toFixed(2)} KB`;return`${(Z/1048576).toFixed(2)} MB`}function I(Z){let $=Z.match(/^([\d.]+)\s*(B|KB|MB|GB)?$/i);if(!$)return 0;let X=Number.parseFloat($[1]);switch(($[2]||"B").toUpperCase()){case"KB":return X*1024;case"MB":return X*1024*1024;case"GB":return X*1024*1024*1024;default:return X}}function E(Z,$){if($===0)return"0%";return`${(Z/$*100).toFixed(1)}%`}function x(Z,$){switch($.format){case"json":return u(Z,$);case"markdown":return l(Z,$);case"text":default:return r(Z,$)}}function u(Z,$){let X=N(Z,$);return JSON.stringify({timestamp:Z.timestamp,buildDir:Z.buildDir,summary:{totalSize:Z.totalSize,totalSizeFormatted:W(Z.totalSize),totalGzipSize:Z.totalGzipSize,totalGzipSizeFormatted:W(Z.totalGzipSize),moduleCount:Z.moduleCount,chunkCount:Z.chunks.length,duplicateCount:Z.duplicates.length},byType:Object.fromEntries(Object.entries(Z.byType).map(([j,U])=>[j,{...U,sizeFormatted:W(U.size),gzipSizeFormatted:W(U.gzipSize)}])),chunks:Z.chunks.map((j)=>({name:j.name,size:j.size,sizeFormatted:W(j.size),gzipSize:j.gzipSize,gzipSizeFormatted:W(j.gzipSize),moduleCount:j.modules.length,isEntry:j.isEntry,isVendor:j.isVendor})),largestModules:Z.largestModules.slice(0,$.topModules||10).map((j)=>({path:j.path,size:j.size,sizeFormatted:W(j.size),gzipSize:j.gzipSize,gzipSizeFormatted:W(j.gzipSize),type:j.type})),duplicates:Z.duplicates,analysis:X,warnings:Z.warnings},null,2)}function r(Z,$){let X=[],j=$.showGzip!==!1;if(X.push(""),X.push("Bundle Analysis Report"),X.push("\u2550".repeat(50)),X.push(""),X.push("Summary"),X.push("\u2500".repeat(50)),j)X.push(`Total Size: ${W(Z.totalSize)} (${W(Z.totalGzipSize)} gzip)`);else X.push(`Total Size: ${W(Z.totalSize)}`);if(X.push(`Modules: ${Z.moduleCount}`),X.push(`Chunks: ${Z.chunks.length}`),Z.duplicates.length>0)X.push(`Duplicates: ${Z.duplicates.length}`);if(X.push(""),$.threshold&&Z.totalSize>$.threshold)X.push(`\u26A0\uFE0F Bundle exceeds threshold of ${W($.threshold)}!`),X.push("");X.push("Size by Type"),X.push("\u2500".repeat(50));let U=["js","css","html","image","font","other"];for(let q of U){let K=Z.byType[q];if(K.count>0){let J=E(K.size,Z.totalSize),F=j?` (${W(K.gzipSize)} gzip)`:"";X.push(` ${q.padEnd(8)} ${W(K.size).padStart(12)}${F} ${J.padStart(6)} (${K.count} files)`)}}X.push(""),X.push("Chunks"),X.push("\u2500".repeat(50));for(let q of Z.chunks.slice(0,10)){let K=[];if(q.isEntry)K.push("entry");if(q.isVendor)K.push("vendor");let J=K.length>0?` [${K.join(", ")}]`:"",F=j?` (${W(q.gzipSize)} gzip)`:"";X.push(` ${q.name.padEnd(20)} ${W(q.size).padStart(12)}${F}${J}`)}if(Z.chunks.length>10)X.push(` ... and ${Z.chunks.length-10} more chunks`);X.push("");let Q=$.topModules||10;X.push(`Largest Modules (Top ${Q})`),X.push("\u2500".repeat(50));for(let q=0;q<Math.min(Q,Z.largestModules.length);q++){let K=Z.largestModules[q],J=j?` (${W(K.gzipSize)} gzip)`:"";X.push(` ${(q+1).toString().padStart(2)}. ${K.path}`),X.push(` ${W(K.size)}${J}`)}if(X.push(""),Z.duplicates.length>0){X.push("Duplicate Modules"),X.push("\u2500".repeat(50));for(let q of Z.duplicates.slice(0,5))X.push(` ${q.path}`),X.push(` ${q.count} copies, wasting ${W(q.wastedBytes)}`);if(Z.duplicates.length>5)X.push(` ... and ${Z.duplicates.length-5} more duplicates`);X.push("")}if($.recommendations!==!1){let q=N(Z,$);if(q.recommendations.length>0){X.push("Recommendations"),X.push("\u2500".repeat(50));for(let K of q.recommendations.slice(0,5)){let J=K.impact==="high"?"\u26A1":K.impact==="medium"?"\uD83D\uDCCA":"\uD83D\uDCA1";X.push(` ${J} ${K.title}`),X.push(` ${K.description}`)}X.push("")}}if(Z.warnings.length>0){X.push("Warnings"),X.push("\u2500".repeat(50));for(let q of Z.warnings)X.push(` \u26A0\uFE0F ${q}`);X.push("")}return X.join(` `)}function l(Z,$){let X=[],j=$.showGzip!==!1;if(X.push("# Bundle Analysis Report"),X.push(""),X.push(`Generated: ${new Date(Z.timestamp).toISOString()}`),X.push(""),X.push("## Summary"),X.push(""),X.push("| Metric | Value |"),X.push("|--------|-------|"),X.push(`| Total Size | ${W(Z.totalSize)} |`),j)X.push(`| Gzip Size | ${W(Z.totalGzipSize)} |`);X.push(`| Modules | ${Z.moduleCount} |`),X.push(`| Chunks | ${Z.chunks.length} |`),X.push(`| Duplicates | ${Z.duplicates.length} |`),X.push(""),X.push("## Size by Type"),X.push(""),X.push("| Type | Size | Gzip | % | Files |"),X.push("|------|------|------|---|-------|");let U=["js","css","html","image","font","other"];for(let q of U){let K=Z.byType[q];if(K.count>0)X.push(`| ${q} | ${W(K.size)} | ${W(K.gzipSize)} | ${E(K.size,Z.totalSize)} | ${K.count} |`)}X.push(""),X.push("## Chunks"),X.push(""),X.push("| Name | Size | Gzip | Modules | Type |"),X.push("|------|------|------|---------|------|");for(let q of Z.chunks.slice(0,15)){let K=q.isEntry?"Entry":q.isVendor?"Vendor":"-";X.push(`| ${q.name} | ${W(q.size)} | ${W(q.gzipSize)} | ${q.modules.length} | ${K} |`)}X.push("");let Q=$.topModules||10;X.push(`## Largest Modules (Top ${Q})`),X.push(""),X.push("| # | Path | Size | Gzip |"),X.push("|---|------|------|------|");for(let q=0;q<Math.min(Q,Z.largestModules.length);q++){let K=Z.largestModules[q];X.push(`| ${q+1} | \`${K.path}\` | ${W(K.size)} | ${W(K.gzipSize)} |`)}if(X.push(""),Z.duplicates.length>0){X.push("## Duplicates"),X.push(""),X.push("| Path | Copies | Wasted |"),X.push("|------|--------|--------|");for(let q of Z.duplicates.slice(0,10))X.push(`| \`${q.path}\` | ${q.count} | ${W(q.wastedBytes)} |`);X.push("")}if($.recommendations!==!1){let q=N(Z,$);if(q.recommendations.length>0){X.push("## Recommendations"),X.push("");for(let K of q.recommendations)X.push(`### ${K.title}`),X.push(""),X.push(K.description),X.push(""),X.push(`- **Impact:** ${K.impact}`),X.push(`- **Effort:** ${K.effort}`),X.push("")}}return X.join(` `)}function N(Z,$){let X=[],j=[];if($.threshold&&Z.totalSize>$.threshold)X.push({severity:"error",message:`Bundle size (${W(Z.totalSize)}) exceeds threshold (${W($.threshold)})`});if(Z.totalSize>1048576)X.push({severity:"warning",message:`Bundle is over 1MB (${W(Z.totalSize)})`,details:"Large bundles can significantly impact load time"});if(Z.duplicates.length>0){let K=Z.duplicates.reduce((J,F)=>J+F.wastedBytes,0);X.push({severity:"warning",message:`${Z.duplicates.length} duplicate modules found, wasting ${W(K)}`}),j.push({title:"Remove Duplicate Modules",description:"Consider using import aliases or ensuring consistent package versions to eliminate duplicates.",impact:"medium",effort:"low"})}if(Z.byType.js.size/Z.totalSize>0.8&&Z.totalSize>512000)j.push({title:"Code Splitting",description:"Over 80% of your bundle is JavaScript. Consider splitting code by route or feature.",impact:"high",effort:"medium"});let Q=Z.largestModules.filter((K)=>K.size>102400);if(Q.length>0){X.push({severity:"info",message:`${Q.length} modules are over 100KB`});for(let K of Q){if(K.path.includes("lodash")&&!K.path.includes("lodash-es"))j.push({title:"Use lodash-es instead of lodash",description:"lodash-es supports tree-shaking and can significantly reduce bundle size.",impact:"high",effort:"low"});if(K.path.includes("moment"))j.push({title:"Replace moment.js",description:"Consider using date-fns or dayjs for smaller date handling libraries.",impact:"high",effort:"medium"})}}if(Z.byType.image.size>512000)j.push({title:"Optimize Images",description:`Images account for ${W(Z.byType.image.size)}. Consider using WebP/AVIF formats and responsive images.`,impact:"high",effort:"low"});let q=100;if(Z.totalSize>1048576)q-=20;else if(Z.totalSize>512000)q-=10;if(q-=Math.min(20,Z.duplicates.length*2),q-=Math.min(15,Q.length*3),$.threshold&&Z.totalSize>$.threshold)q-=25;return{score:Math.max(0,q),issues:X,recommendations:j}}var i={lodash:{alternative:"lodash-es or individual imports",savings:"~70%"},moment:{alternative:"date-fns or dayjs",savings:"~90%"},"chart.js":{alternative:"chart.js with tree-shaking",savings:"~50%"},"highlight.js":{alternative:"Selective language imports",savings:"~80%"},fontawesome:{alternative:"Individual icon imports",savings:"~90%"},"material-ui":{alternative:"Named imports from @mui/*",savings:"~60%"}};function n(Z){for(let[$,X]of Object.entries(i))if(Z.includes($))return{title:`Optimize ${$}`,description:`Consider using ${X.alternative}. Potential savings: ${X.savings}`,impact:"high",effort:"low"};return null}var o={default:{js:"#f7df1e",css:"#264de4",html:"#e34c26",image:"#4caf50",font:"#9c27b0",other:"#607d8b",vendor:"#ff5722"},pastel:{js:"#fff3b0",css:"#b0d4ff",html:"#ffb0b0",image:"#b0ffb0",font:"#e0b0ff",other:"#d0d0d0",vendor:"#ffcfb0"},monochrome:{js:"#333333",css:"#555555",html:"#777777",image:"#999999",font:"#bbbbbb",other:"#dddddd",vendor:"#444444"}};function R(Z,$={}){let{title:X="Bundle Analysis",gzip:j=!0,colorScheme:U="default"}=$,Q=o[U],q=a(Z),K=JSON.stringify(q);return`<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>${M(X)}</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; background: #1a1a2e; color: #eee; min-height: 100vh; } .header { background: #16213e; padding: 1rem 2rem; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #0f3460; } .header h1 { font-size: 1.5rem; font-weight: 500; } .stats { display: flex; gap: 2rem; } .stat { text-align: center; } .stat-value { font-size: 1.25rem; font-weight: bold; color: #e94560; } .stat-label { font-size: 0.75rem; color: #888; text-transform: uppercase; } .controls { background: #16213e; padding: 0.75rem 2rem; display: flex; gap: 1rem; align-items: center; border-bottom: 1px solid #0f3460; } .controls label { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; } .controls input[type="checkbox"] { width: 1rem; height: 1rem; } .search-box { flex: 1; max-width: 300px; } .search-box input { width: 100%; padding: 0.5rem 1rem; border: 1px solid #0f3460; border-radius: 4px; background: #1a1a2e; color: #eee; font-size: 0.875rem; } .search-box input:focus { outline: none; border-color: #e94560; } .breadcrumb { background: #16213e; padding: 0.5rem 2rem; display: flex; gap: 0.5rem; align-items: center; font-size: 0.875rem; border-bottom: 1px solid #0f3460; } .breadcrumb-item { color: #888; cursor: pointer; } .breadcrumb-item:hover { color: #e94560; } .breadcrumb-item.active { color: #eee; cursor: default; } .breadcrumb-sep { color: #444; } #treemap { width: 100%; height: calc(100vh - 180px); min-height: 400px; } .tooltip { position: fixed; background: #16213e; border: 1px solid #0f3460; border-radius: 4px; padding: 0.75rem 1rem; pointer-events: none; z-index: 1000; max-width: 400px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } .tooltip-title { font-weight: bold; margin-bottom: 0.5rem; word-break: break-all; } .tooltip-row { display: flex; justify-content: space-between; gap: 2rem; font-size: 0.875rem; color: #888; } .tooltip-row span:last-child { color: #eee; font-weight: 500; } .legend { position: fixed; bottom: 1rem; right: 1rem; background: #16213e; border: 1px solid #0f3460; border-radius: 4px; padding: 0.75rem 1rem; } .legend-title { font-size: 0.75rem; color: #888; margin-bottom: 0.5rem; text-transform: uppercase; } .legend-item { display: flex; align-items: center; gap: 0.5rem; font-size: 0.75rem; margin-bottom: 0.25rem; } .legend-color { width: 12px; height: 12px; border-radius: 2px; } .node { cursor: pointer; transition: opacity 0.2s; } .node:hover { opacity: 0.8; } .node-label { font-size: 11px; fill: #000; pointer-events: none; text-anchor: middle; } </style> </head> <body> <div class="header"> <h1>${M(X)}</h1> <div class="stats"> <div class="stat"> <div class="stat-value" id="total-size">${W(Z.totalSize)}</div> <div class="stat-label">Total Size</div> </div> <div class="stat"> <div class="stat-value" id="gzip-size">${W(Z.totalGzipSize)}</div> <div class="stat-label">Gzip Size</div> </div> <div class="stat"> <div class="stat-value">${Z.moduleCount}</div> <div class="stat-label">Modules</div> </div> <div class="stat"> <div class="stat-value">${Z.chunks.length}</div> <div class="stat-label">Chunks</div> </div> </div> </div> <div class="controls"> <div class="search-box"> <input type="text" id="search" placeholder="Search modules..."> </div> <label> <input type="checkbox" id="show-gzip" ${j?"checked":""}> Show Gzip Size </label> </div> <div class="breadcrumb" id="breadcrumb"> <span class="breadcrumb-item active">root</span> </div> <div id="treemap"></div> <div class="tooltip" id="tooltip" style="display: none;"></div> <div class="legend"> <div class="legend-title">File Types</div> <div class="legend-item"> <div class="legend-color" style="background: ${Q.js}"></div> <span>JavaScript</span> </div> <div class="legend-item"> <div class="legend-color" style="background: ${Q.css}"></div> <span>CSS</span> </div> <div class="legend-item"> <div class="legend-color" style="background: ${Q.html}"></div> <span>HTML</span> </div> <div class="legend-item"> <div class="legend-color" style="background: ${Q.image}"></div> <span>Images</span> </div> <div class="legend-item"> <div class="legend-color" style="background: ${Q.font}"></div> <span>Fonts</span> </div> <div class="legend-item"> <div class="legend-color" style="background: ${Q.other}"></div> <span>Other</span> </div> </div> <script src="https://d3js.org/d3.v7.min.js"></script> <script> const data = ${K}; const colors = ${JSON.stringify(Q)}; let showGzip = ${j}; let currentRoot = data; let searchTerm = ''; const container = document.getElementById('treemap'); const tooltip = document.getElementById('tooltip'); const breadcrumb = document.getElementById('breadcrumb'); const searchInput = document.getElementById('search'); const showGzipCheckbox = document.getElementById('show-gzip'); function formatSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'; return (bytes / (1024 * 1024)).toFixed(2) + ' MB'; } function getColor(d) { if (d.data.type) { return colors[d.data.type] || colors.other; } if (d.data.name.includes('node_modules') || d.data.name.includes('vendor')) { return colors.vendor; } return colors.other; } function getSizeValue(d) { return showGzip ? d.data.gzipSize : d.data.size; } function render() { container.innerHTML = ''; const width = container.clientWidth; const height = container.clientHeight; const hierarchy = d3.hierarchy(currentRoot) .sum(d => { if (searchTerm && d.name && !d.name.toLowerCase().includes(searchTerm.toLowerCase())) { return 0; } return d.children ? 0 : getSizeValue({ data: d }); }) .sort((a, b) => getSizeValue(b) - getSizeValue(a)); const treemap = d3.treemap() .size([width, height]) .paddingOuter(3) .paddingTop(19) .paddingInner(1) .round(true); const root = treemap(hierarchy); const svg = d3.select(container) .append('svg') .attr('width', width) .attr('height', height); const node = svg.selectAll('g') .data(root.descendants().filter(d => d.depth > 0)) .join('g') .attr('transform', d => \`translate(\${d.x0},\${d.y0})\`); node.append('rect') .attr('class', 'node') .attr('width', d => Math.max(0, d.x1 - d.x0)) .attr('height', d => Math.max(0, d.y1 - d.y0)) .attr('fill', d => getColor(d)) .attr('stroke', '#1a1a2e') .attr('stroke-width', 1) .on('click', (event, d) => { if (d.children) { zoomIn(d.data); } }) .on('mouseover', (event, d) => showTooltip(event, d)) .on('mousemove', (event) => moveTooltip(event)) .on('mouseout', hideTooltip); node.append('text') .attr('class', 'node-label') .attr('x', d => (d.x1 - d.x0) / 2) .attr('y', d => (d.y1 - d.y0) / 2 + 4) .text(d => { const width = d.x1 - d.x0; const height = d.y1 - d.y0; if (width < 40 || height < 20) return ''; const name = d.data.name; const maxLen = Math.floor(width / 7); return name.length > maxLen ? name.slice(0, maxLen - 2) + '..' : name; }); // Group headers node.filter(d => d.children) .append('text') .attr('x', 4) .attr('y', 14) .attr('fill', '#000') .attr('font-size', '12px') .attr('font-weight', 'bold') .text(d => { const width = d.x1 - d.x0; const name = d.data.name; const maxLen = Math.floor(width / 7); return name.length > maxLen ? name.slice(0, maxLen - 2) + '..' : name; }); } function showTooltip(event, d) { const size = d.data.size || d.value; const gzipSize = d.data.gzipSize || Math.round(size * 0.3); const path = d.data.path || d.data.name; tooltip.innerHTML = \` <div class="tooltip-title">\${path}</div> <div class="tooltip-row"> <span>Size</span> <span>\${formatSize(size)}</span> </div> <div class="tooltip-row"> <span>Gzip</span> <span>\${formatSize(gzipSize)}</span> </div> \${d.data.type ? \`<div class="tooltip-row"><span>Type</span><span>\${d.data.type}</span></div>\` : ''} \${d.children ? \`<div class="tooltip-row"><span>Children</span><span>\${d.children.length}</span></div>\` : ''} \`; tooltip.style.display = 'block'; moveTooltip(event); } function moveTooltip(event) { const x = event.clientX + 10; const y = event.clientY + 10; tooltip.style.left = x + 'px'; tooltip.style.top = y + 'px'; } function hideTooltip() { tooltip.style.display = 'none'; } function zoomIn(node) { currentRoot = node; updateBreadcrumb(); render(); } function zoomOut(index) { let node = data; const parts = getBreadcrumbParts(); for (let i = 1; i <= index; i++) { const child = node.children?.find(c => c.name === parts[i]); if (child) node = child; } currentRoot = node; updateBreadcrumb(); render(); } function getBreadcrumbParts() { const parts = ['root']; let node = currentRoot; const path = []; function findPath(current, target, currentPath) { if (current === target) { path.push(...currentPath); return true; } if (current.children) { for (const child of current.children) { if (findPath(child, target, [...currentPath, child.name])) { return true; } } } return false; } if (currentRoot !== data) { findPath(data, currentRoot, []); parts.push(...path); } return parts; } function updateBreadcrumb() { const parts = getBreadcrumbParts(); breadcrumb.innerHTML = parts.map((part, i) => { const isLast = i === parts.length - 1; const sep = i > 0 ? '<span class="breadcrumb-sep">/</span>' : ''; return \`\${sep}<span class="breadcrumb-item \${isLast ? 'active' : ''}" onclick="zoomOut(\${i})">\${part}</span>\`; }).join(''); } searchInput.addEventListener('input', (e) => { searchTerm = e.target.value; render(); }); showGzipCheckbox.addEventListener('change', (e) => { showGzip = e.target.checked; render(); }); window.addEventListener('resize', render); render(); </script> </body> </html>`}function a(Z){let $={name:"root",size:Z.totalSize,gzipSize:Z.totalGzipSize,children:[]};for(let X of Z.chunks){let j={name:X.name,size:X.size,gzipSize:X.gzipSize,children:[]};for(let U of X.modules)e(j,U);$.children.push(j)}return $}function e(Z,$){let X=$.path.split("/"),j=Z;for(let Q=0;Q<X.length-1;Q++){let q=X[Q],K=j.children?.find((J)=>J.name===q);if(!K){if(K={name:q,size:0,gzipSize:0,children:[]},!j.children)j.children=[];j.children.push(K)}j=K}let U=X[X.length-1];if(!j.children)j.children=[];j.children.push({name:U,path:$.path,size:$.size,gzipSize:$.gzipSize,type:$.type}),s(Z,$.size,$.gzipSize)}function s(Z,$,X){}function M(Z){return Z.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function T(Z){let $=process.platform,X=$==="darwin"?"open":$==="win32"?"start":"xdg-open";Bun.spawn([X,Z],{stdio:["ignore","ignore","ignore"]})}import P from"fs";import S from"path";async function CX(Z={}){let{directory:$="dist",output:X,format:j="html",open:U=!0,gzip:Q=!0,threshold:q,recommendations:K=!0,topModules:J=20,parseImports:F=!0}=Z,V=await L($,{parseImports:F,detectDuplicates:!0,topModulesCount:J}),_=q?typeof q==="string"?I(q):q:void 0,w=_?V.totalSize>_:!1,G,C;if(j==="html"){let A={title:`Bundle Analysis - ${S.basename(S.resolve($))}`,gzip:Q};if(G=R(V,A),C=X||S.join($,"bundle-report.html"),await P.promises.writeFile(C,G),U)T(C)}else if(G=x(V,{format:j,threshold:_,recommendations:K,topModules:J,showGzip:Q}),X)C=X,await P.promises.writeFile(C,G);return{stats:V,report:G,outputPath:C,exceedsThreshold:w}}async function GX(Z){let $=await L(Z,{parseImports:!1,detectDuplicates:!1,topModulesCount:0});return{totalSize:$.totalSize,totalSizeFormatted:W($.totalSize),gzipSize:$.totalGzipSize,gzipSizeFormatted:W($.totalGzipSize),moduleCount:$.moduleCount,chunkCount:$.chunks.length}}export{E as percentage,I as parseSize,T as openInBrowser,n as getModuleRecommendation,GX as getBundleSummary,R as generateTreemap,x as generateReport,W as formatBytes,L as collectBundleStats,N as analyzeBundle,CX as analyze};