Skip to content

Commit 2ac04b0

Browse files
authored
bundle-analyzer: redesign info hierarchy in sidebar (#86963)
This better reflects the information hierarchy in the design and declutters the sidebar. This PR also adds recursive size calculation when directories are selected. Closes PACK-5828 Closes PACK-6030 ![image.png](https://app.graphite.com/user-attachments/assets/83297d13-1689-4fc2-a254-4c0d1f21aaba.png) ![image.png](https://app.graphite.com/user-attachments/assets/fb3c76bc-61ae-4f50-a64c-fad0ff3ec9a8.png) ![image.png](https://app.graphite.com/user-attachments/assets/6d85c828-4917-4546-b8e9-742035635d73.png)
1 parent daafa4f commit 2ac04b0

File tree

6 files changed

+354
-142
lines changed

6 files changed

+354
-142
lines changed

apps/bundle-analyzer/app/page.tsx

Lines changed: 20 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,20 @@
33
import type React from 'react'
44
import { useEffect, useMemo, useRef, useState } from 'react'
55
import useSWR from 'swr'
6-
import { ImportChain } from '@/components/import-chain'
76
import { ErrorState } from '@/components/error-state'
87
import {
98
RouteTypeahead,
109
type RouteTypeaheadRef,
1110
} from '@/components/route-typeahead'
11+
import { Sidebar } from '@/components/sidebar'
1212
import { TreemapVisualizer } from '@/components/treemap-visualizer'
1313

1414
import { Input } from '@/components/ui/input'
15-
import { Skeleton, TreemapSkeleton } from '@/components/ui/skeleton'
15+
import { TreemapSkeleton } from '@/components/ui/skeleton'
1616
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
1717
import { AnalyzeData, ModulesData } from '@/lib/analyze-data'
1818
import { computeActiveEntries, computeModuleDepthMap } from '@/lib/module-graph'
19-
import { SpecialModule } from '@/lib/types'
20-
import { getSpecialModuleType, fetchStrict } from '@/lib/utils'
21-
22-
function formatBytes(bytes: number): string {
23-
if (bytes === 0) return '0 B'
24-
if (bytes < 1024) return `${bytes} B`
25-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`
26-
if (bytes < 1024 * 1024 * 1024)
27-
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`
28-
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`
29-
}
19+
import { fetchStrict } from '@/lib/utils'
3020

3121
export default function Home() {
3222
const [selectedRoute, setSelectedRoute] = useState<string | null>(null)
@@ -153,11 +143,6 @@ export default function Home() {
153143
const isAnyLoading = isAnalyzeLoading || isModulesLoading
154144
const rootSourceIndex = getRootSourceIndex(analyzeData)
155145

156-
const specialModuleType = getSpecialModuleType(
157-
analyzeData,
158-
selectedSourceIndex
159-
)
160-
161146
return (
162147
<main
163148
className="h-screen flex flex-col bg-background"
@@ -250,24 +235,15 @@ export default function Home() {
250235
aria-label="Resize sidebar"
251236
/>
252237

253-
<div
254-
className="flex-none bg-muted border-l border-border overflow-y-auto"
255-
style={{ width: `${sidebarWidth}%` }}
256-
>
257-
<div className="flex-1 p-3 space-y-4 overflow-y-auto">
258-
<h2 className="text-xs font-semibold mb-2 text-foreground">
259-
Selected Source
260-
</h2>
261-
<Skeleton className="h-4 w-3/4" />
262-
<Skeleton className="h-4 w-full" />
263-
<Skeleton className="h-4 w-5/6" />
264-
<div className="mt-4 space-y-2">
265-
<Skeleton className="h-3 w-full" />
266-
<Skeleton className="h-3 w-full" />
267-
<Skeleton className="h-3 w-4/5" />
268-
</div>
269-
</div>
270-
</div>
238+
<Sidebar
239+
sidebarWidth={sidebarWidth}
240+
analyzeData={null}
241+
modulesData={null}
242+
selectedSourceIndex={null}
243+
moduleDepthMap={new Map()}
244+
environmentFilter={environmentFilter}
245+
isLoading={true}
246+
/>
271247
</>
272248
) : analyzeData ? (
273249
<>
@@ -294,86 +270,14 @@ export default function Home() {
294270
aria-label="Resize sidebar"
295271
/>
296272

297-
<div
298-
className="flex-none bg-muted border-l border-border overflow-y-auto"
299-
style={{ width: `${sidebarWidth}%` }}
300-
>
301-
<div className="flex-1 p-3 space-y-4 overflow-y-auto">
302-
<h2 className="text-xs font-semibold mb-2 text-foreground">
303-
Selected Source
304-
</h2>
305-
306-
{selectedSourceIndex != null &&
307-
analyzeData.source(selectedSourceIndex) && (
308-
<>
309-
<dl className="space-y-2">
310-
<div>
311-
<dt className="text-xs text-muted-foreground inline">
312-
Output Size:{' '}
313-
</dt>
314-
<dd className="text-xs text-muted-foreground inline">
315-
{formatBytes(
316-
analyzeData.getSourceOutputSize(
317-
selectedSourceIndex
318-
)
319-
)}
320-
</dd>
321-
</div>
322-
{(specialModuleType === SpecialModule.POLYFILL_MODULE ||
323-
specialModuleType ===
324-
SpecialModule.POLYFILL_NOMODULE) && (
325-
<div className="flex items-center gap-2">
326-
<dt className="inline-flex items-center rounded-md bg-polyfill/10 dark:bg-polyfill/30 px-2 py-1 text-xs font-medium text-polyfill dark:text-polyfill-foreground ring-1 ring-inset ring-polyfill/20 shrink-0">
327-
Polyfill
328-
</dt>
329-
<dd className="text-xs text-muted-foreground">
330-
Next.js built-in polyfills
331-
{specialModuleType ===
332-
SpecialModule.POLYFILL_NOMODULE ? (
333-
<>
334-
. <code>polyfill-nomodule.js</code> is only
335-
sent to legacy browsers.
336-
</>
337-
) : null}
338-
</dd>
339-
</div>
340-
)}
341-
</dl>
342-
{modulesData && (
343-
<ImportChain
344-
key={selectedSourceIndex}
345-
startFileId={selectedSourceIndex}
346-
analyzeData={analyzeData}
347-
modulesData={modulesData}
348-
depthMap={moduleDepthMap}
349-
environmentFilter={environmentFilter}
350-
/>
351-
)}
352-
{(() => {
353-
const chunks =
354-
analyzeData.sourceChunks(selectedSourceIndex)
355-
if (chunks.length > 0) {
356-
return (
357-
<div className="mt-2">
358-
<p className="text-xs font-semibold text-foreground">
359-
Output Chunks:
360-
</p>
361-
<ul className="text-xs text-muted-foreground font-mono mt-1 space-y-1">
362-
{chunks.map((chunk) => (
363-
<li key={chunk} className="break-all">
364-
{chunk}
365-
</li>
366-
))}
367-
</ul>
368-
</div>
369-
)
370-
}
371-
return null
372-
})()}
373-
</>
374-
)}
375-
</div>
376-
</div>
273+
<Sidebar
274+
sidebarWidth={sidebarWidth}
275+
analyzeData={analyzeData ?? null}
276+
modulesData={modulesData ?? null}
277+
selectedSourceIndex={selectedSourceIndex}
278+
moduleDepthMap={moduleDepthMap}
279+
environmentFilter={environmentFilter}
280+
/>
377281
</>
378282
) : null}
379283
</div>

apps/bundle-analyzer/components/import-chain.tsx

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type {
2121
} from '@/lib/analyze-data'
2222
import { splitIdent } from '@/lib/utils'
2323
import clsx from 'clsx'
24+
import { Button } from '@/components/ui/button'
2425

2526
interface ImportChainProps {
2627
startFileId: number
@@ -130,7 +131,7 @@ export function ImportChain({
130131
environmentFilter,
131132
}: ImportChainProps) {
132133
// Filter to include only the current route
133-
const [showAll, setShowAll] = useState(false)
134+
const [currentRouteOnly, setCurrentRouteOnly] = useState(true)
134135

135136
// Track which dependent is selected at each level
136137
const [selectedIndices, setSelectedIndices] = useState<number[]>([])
@@ -168,7 +169,7 @@ export function ImportChain({
168169
const startModuleIndices = getModuleIndicesFromSourceIndex(
169170
startFileId
170171
).filter((moduleIndex) => {
171-
if (!showAll && !depthMap.has(moduleIndex)) {
172+
if (currentRouteOnly && !depthMap.has(moduleIndex)) {
172173
return false
173174
}
174175
let module = modulesData.module(moduleIndex)
@@ -232,7 +233,7 @@ export function ImportChain({
232233
// Filter out dependents that would create a cycle
233234
const validDependents = dependentModuleIndices.filter(
234235
({ index, depth }) =>
235-
!visitedModules.has(index) && (isFinite(depth) || showAll)
236+
!visitedModules.has(index) && (isFinite(depth) || !currentRouteOnly)
236237
)
237238

238239
if (validDependents.length === 0) {
@@ -276,7 +277,7 @@ export function ImportChain({
276277
const selectedDepInfo = dependentsInfo[actualIdx]
277278
const selectedDepModule = modulesData.module(selectedDepInfo.moduleIndex)
278279

279-
if (!selectedDepModule) break
280+
if (!selectedDepModule || selectedDepModule.ident == null) break
280281

281282
result.push({
282283
moduleIndex: selectedDepInfo.moduleIndex,
@@ -304,7 +305,7 @@ export function ImportChain({
304305
analyzeData,
305306
modulesData,
306307
selectedIndices,
307-
showAll,
308+
currentRouteOnly,
308309
depthMap,
309310
environmentFilter,
310311
])
@@ -336,7 +337,23 @@ export function ImportChain({
336337

337338
return (
338339
<div className="space-y-2">
339-
<h3 className="text-xs font-semibold text-foreground">Import chain</h3>
340+
<div className="flex items-center space-x-2">
341+
<h3 className="text-xs font-semibold text-foreground flex-1">
342+
Import Chain
343+
</h3>
344+
<label
345+
className="inline-flex items-center space-x-2 text-xs cursor-pointer"
346+
title="Only include dependent modules that are part of the current route's bundle"
347+
>
348+
<span>Current route only</span>
349+
<input
350+
type="checkbox"
351+
className="form-checkbox h-4 w-4 text-primary"
352+
checked={currentRouteOnly}
353+
onChange={() => setCurrentRouteOnly((prev) => !prev)}
354+
/>
355+
</label>
356+
</div>
340357
<div className="space-y-0">
341358
{chain.map((level, index) => {
342359
const previousPath = index > 0 ? chain[index - 1].path : null
@@ -503,23 +520,19 @@ export function ImportChain({
503520
})}
504521
{chain.length === 0 && (
505522
<p className="text-muted-foreground italic text-xs">
506-
No dependents found
523+
No dependents found.{' '}
524+
{currentRouteOnly ? (
525+
<Button
526+
variant="link"
527+
className="inline p-0 text-xs h-auto"
528+
onClick={() => setCurrentRouteOnly(false)}
529+
>
530+
Show all routes.
531+
</Button>
532+
) : null}
507533
</p>
508534
)}
509535
</div>
510-
<div className="pt-2">
511-
<label className="inline-flex items-center space-x-2 text-sm cursor-pointer">
512-
<input
513-
type="checkbox"
514-
className="form-checkbox h-4 w-4 text-primary"
515-
checked={showAll}
516-
onChange={() => setShowAll((prev) => !prev)}
517-
/>
518-
<span>
519-
Show all dependents (including those outside current route)
520-
</span>
521-
</label>
522-
</div>
523536
</div>
524537
)
525538
}

0 commit comments

Comments
 (0)