Skip to content

Commit 020f989

Browse files
authored
Merge branch 'canary' into refactor/turbopack-node-napi
2 parents d690d93 + 593cef1 commit 020f989

File tree

107 files changed

+1949
-2086
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+1949
-2086
lines changed

apps/bundle-analyzer/app/page.tsx

Lines changed: 48 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,21 @@
11
'use client'
22

33
import type React from 'react'
4-
import { useEffect, useMemo, useRef, useState } from 'react'
4+
import { useEffect, useMemo, useState } from 'react'
55
import useSWR from 'swr'
6-
import { ImportChain } from '@/components/import-chain'
76
import { ErrorState } from '@/components/error-state'
8-
import {
9-
RouteTypeahead,
10-
type RouteTypeaheadRef,
11-
} from '@/components/route-typeahead'
7+
import { FileSearch } from '@/components/file-search'
8+
import { RouteTypeahead } from '@/components/route-typeahead'
9+
import { Sidebar } from '@/components/sidebar'
1210
import { TreemapVisualizer } from '@/components/treemap-visualizer'
1311

14-
import { Input } from '@/components/ui/input'
15-
import { Skeleton, TreemapSkeleton } from '@/components/ui/skeleton'
12+
import { Badge } from '@/components/ui/badge'
13+
import { TreemapSkeleton } from '@/components/ui/skeleton'
1614
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
1715
import { AnalyzeData, ModulesData } from '@/lib/analyze-data'
1816
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-
}
17+
import { fetchStrict } from '@/lib/utils'
18+
import { formatBytes } from '@/lib/utils'
3019

3120
export default function Home() {
3221
const [selectedRoute, setSelectedRoute] = useState<string | null>(null)
@@ -75,35 +64,32 @@ export default function Home() {
7564
const [isMouseInTreemap, setIsMouseInTreemap] = useState(false)
7665
const [hoveredNodeInfo, setHoveredNodeInfo] = useState<{
7766
name: string
67+
size: number
7868
server?: boolean
7969
client?: boolean
8070
} | null>(null)
8171
const [searchQuery, setSearchQuery] = useState('')
82-
const [searchFocused, setSearchFocused] = useState(false)
83-
84-
const routeTypeaheadRef = useRef<RouteTypeaheadRef>(null)
85-
const searchInputRef = useRef<HTMLInputElement>(null)
8672

8773
useEffect(() => {
8874
const handleKeyDown = (e: KeyboardEvent) => {
89-
// Cmd+K or Ctrl+K to focus route filter
90-
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
91-
e.preventDefault()
92-
routeTypeaheadRef.current?.focus()
93-
}
94-
// / to focus search (only if not already in an input)
95-
else if (
96-
e.key === '/' &&
97-
!['INPUT', 'TEXTAREA'].includes((e.target as HTMLElement).tagName)
98-
) {
99-
e.preventDefault()
100-
searchInputRef.current?.focus()
75+
// esc clears current treemap source selection
76+
if (e.key === 'Escape') {
77+
const activeElement = document.activeElement
78+
const isInputFocused =
79+
activeElement && ['INPUT', 'TEXTAREA'].includes(activeElement.tagName)
80+
81+
if (!isInputFocused) {
82+
e.preventDefault()
83+
const rootSourceIndex = getRootSourceIndex(analyzeData)
84+
setSelectedSourceIndex(rootSourceIndex)
85+
setFocusedSourceIndex(rootSourceIndex)
86+
}
10187
}
10288
}
10389

10490
window.addEventListener('keydown', handleKeyDown)
10591
return () => window.removeEventListener('keydown', handleKeyDown)
106-
}, [])
92+
}, [analyzeData])
10793

10894
// Compute module depth map from active entries
10995
const moduleDepthMap = useMemo(() => {
@@ -153,11 +139,6 @@ export default function Home() {
153139
const isAnyLoading = isAnalyzeLoading || isModulesLoading
154140
const rootSourceIndex = getRootSourceIndex(analyzeData)
155141

156-
const specialModuleType = getSpecialModuleType(
157-
analyzeData,
158-
selectedSourceIndex
159-
)
160-
161142
return (
162143
<main
163144
className="h-screen flex flex-col bg-background"
@@ -167,7 +148,6 @@ export default function Home() {
167148
<div className="flex-none px-4 py-2 border-b border-border flex items-center gap-4">
168149
<div className="basis-1/3 flex">
169150
<RouteTypeahead
170-
ref={routeTypeaheadRef}
171151
selectedRoute={selectedRoute}
172152
onRouteSelected={(route) => {
173153
setSelectedRoute(route)
@@ -208,27 +188,7 @@ export default function Home() {
208188
<ToggleGroupItem value="asset">Asset</ToggleGroupItem>
209189
</ToggleGroup>
210190

211-
{!searchFocused && (
212-
<div className="flex items-center gap-4 text-xs">
213-
<p className="text-muted-foreground">
214-
{
215-
analyzeData.source(focusedSourceIndex ?? rootSourceIndex)
216-
?.path
217-
}
218-
</p>
219-
</div>
220-
)}
221-
222-
<Input
223-
ref={searchInputRef}
224-
type="search"
225-
value={searchQuery}
226-
onChange={(e) => setSearchQuery(e.target.value)}
227-
onFocus={() => setSearchFocused(true)}
228-
onBlur={() => setSearchFocused(false)}
229-
placeholder="Search files..."
230-
className="w-48 focus:w-80 transition-all duration-200"
231-
/>
191+
<FileSearch value={searchQuery} onChange={setSearchQuery} />
232192
</>
233193
)}
234194
</div>
@@ -250,24 +210,15 @@ export default function Home() {
250210
aria-label="Resize sidebar"
251211
/>
252212

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>
213+
<Sidebar
214+
sidebarWidth={sidebarWidth}
215+
analyzeData={null}
216+
modulesData={null}
217+
selectedSourceIndex={null}
218+
moduleDepthMap={new Map()}
219+
environmentFilter={environmentFilter}
220+
isLoading={true}
221+
/>
271222
</>
272223
) : analyzeData ? (
273224
<>
@@ -294,116 +245,44 @@ export default function Home() {
294245
aria-label="Resize sidebar"
295246
/>
296247

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>
248+
<Sidebar
249+
sidebarWidth={sidebarWidth}
250+
analyzeData={analyzeData ?? null}
251+
modulesData={modulesData ?? null}
252+
selectedSourceIndex={selectedSourceIndex}
253+
moduleDepthMap={moduleDepthMap}
254+
environmentFilter={environmentFilter}
255+
/>
377256
</>
378257
) : null}
379258
</div>
380259

381260
{analyzeData && (
382261
<div className="flex-none border-t border-border bg-background px-4 py-2 h-10">
383-
<p className="text-sm text-muted-foreground">
262+
<div className="text-sm text-muted-foreground">
384263
{hoveredNodeInfo ? (
385264
<>
386265
<span className="font-medium text-foreground">
387266
{hoveredNodeInfo.name}
388267
</span>
268+
<span className="ml-2 text-muted-foreground">
269+
{formatBytes(hoveredNodeInfo.size)}
270+
</span>
389271
{(hoveredNodeInfo.server || hoveredNodeInfo.client) && (
390-
<span className="ml-2 text-xs">
272+
<span className="ml-2 inline-flex gap-1">
391273
{hoveredNodeInfo.client && (
392-
<span className="text-primary">[client]</span>
393-
)}
394-
{hoveredNodeInfo.server && hoveredNodeInfo.client && (
395-
<span> </span>
274+
<Badge variant="client">client</Badge>
396275
)}
397276
{hoveredNodeInfo.server && (
398-
<span className="text-primary">[server]</span>
277+
<Badge variant="server">server</Badge>
399278
)}
400279
</span>
401280
)}
402281
</>
403282
) : (
404283
'Hover over a file to see details'
405284
)}
406-
</p>
285+
</div>
407286
</div>
408287
)}
409288
</main>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use client'
2+
3+
import { useEffect, useRef, useState } from 'react'
4+
import { Input } from '@/components/ui/input'
5+
import { Kbd } from '@/components/ui/kbd'
6+
7+
interface FileSearchProps {
8+
value: string
9+
onChange: (value: string) => void
10+
}
11+
12+
export function FileSearch({ value, onChange }: FileSearchProps) {
13+
const [focused, setFocused] = useState(false)
14+
const inputRef = useRef<HTMLInputElement>(null)
15+
16+
useEffect(() => {
17+
const handleKeyDown = (e: KeyboardEvent) => {
18+
if (
19+
e.key === '/' &&
20+
!['INPUT', 'TEXTAREA'].includes((e.target as HTMLElement).tagName)
21+
) {
22+
e.preventDefault()
23+
inputRef.current?.focus()
24+
} else if (
25+
e.key === 'Escape' &&
26+
document.activeElement === inputRef.current
27+
) {
28+
e.preventDefault()
29+
onChange('')
30+
inputRef.current?.blur()
31+
}
32+
}
33+
34+
window.addEventListener('keydown', handleKeyDown)
35+
return () => window.removeEventListener('keydown', handleKeyDown)
36+
}, [onChange])
37+
38+
const handleFocus = () => {
39+
setFocused(true)
40+
}
41+
42+
const handleBlur = () => {
43+
setFocused(false)
44+
}
45+
46+
return (
47+
<div className="relative">
48+
<Input
49+
ref={inputRef}
50+
type="search"
51+
value={value}
52+
onChange={(e) => onChange(e.target.value)}
53+
onFocus={handleFocus}
54+
onBlur={handleBlur}
55+
placeholder="Search files..."
56+
className="w-48 focus:w-80 transition-all duration-200 pr-8"
57+
/>
58+
{!value && !focused && (
59+
<Kbd className="absolute right-2 top-1/2 -translate-y-1/2">/</Kbd>
60+
)}
61+
</div>
62+
)
63+
}

0 commit comments

Comments
 (0)