@stacksjs/stx
Version:
A performant UI Framework. Powered by Bun.
500 lines (433 loc) • 26.9 kB
JavaScript
// @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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}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};