Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
238 lines
7.1 KiB
TypeScript
238 lines
7.1 KiB
TypeScript
'use client'
|
|
|
|
import type React from 'react'
|
|
import { CircleHelp } from 'lucide-react'
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from './ui/tooltip'
|
|
import { ImportChain } from '@/components/import-chain'
|
|
import { Skeleton } from '@/components/ui/skeleton'
|
|
import { AnalyzeData, ModulesData } from '@/lib/analyze-data'
|
|
import { SpecialModule } from '@/lib/types'
|
|
import { getSpecialModuleType } from '@/lib/utils'
|
|
import { Badge } from './ui/badge'
|
|
|
|
interface SidebarProps {
|
|
sidebarWidth: number
|
|
analyzeData: AnalyzeData | null
|
|
modulesData: ModulesData | null
|
|
selectedSourceIndex: number | null
|
|
moduleDepthMap: Map<number, number>
|
|
environmentFilter: 'client' | 'server'
|
|
filterSource?: (sourceIndex: number) => boolean
|
|
isLoading?: boolean
|
|
}
|
|
|
|
function formatBytes(bytes: number): string {
|
|
if (bytes === 0) return '0 B'
|
|
if (bytes < 1024) return `${bytes} B`
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`
|
|
if (bytes < 1024 * 1024 * 1024)
|
|
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`
|
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`
|
|
}
|
|
|
|
export function Sidebar({
|
|
sidebarWidth,
|
|
analyzeData,
|
|
modulesData,
|
|
selectedSourceIndex,
|
|
moduleDepthMap,
|
|
environmentFilter,
|
|
filterSource,
|
|
isLoading = false,
|
|
}: SidebarProps) {
|
|
filterSource = filterSource ?? (() => true)
|
|
|
|
if (isLoading || !analyzeData) {
|
|
return (
|
|
<div
|
|
className="flex-none bg-muted border-l border-border overflow-y-auto"
|
|
style={{ width: `${sidebarWidth}%` }}
|
|
>
|
|
<div className="flex-1 p-3 space-y-4 overflow-y-auto">
|
|
<Skeleton className="h-4 w-3/4" />
|
|
<Skeleton className="h-4 w-full" />
|
|
<Skeleton className="h-4 w-5/6" />
|
|
<div className="mt-4 space-y-2">
|
|
<Skeleton className="h-3 w-full" />
|
|
<Skeleton className="h-3 w-full" />
|
|
<Skeleton className="h-3 w-4/5" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className="flex-none bg-muted border-l border-border overflow-y-auto"
|
|
style={{ width: `${sidebarWidth}%` }}
|
|
>
|
|
{selectedSourceIndex != null ? (
|
|
<SelectionDetails
|
|
analyzeData={analyzeData}
|
|
modulesData={modulesData}
|
|
selectedSourceIndex={selectedSourceIndex}
|
|
filterSource={filterSource}
|
|
moduleDepthMap={moduleDepthMap}
|
|
environmentFilter={environmentFilter}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function SelectionDetails({
|
|
analyzeData,
|
|
modulesData,
|
|
selectedSourceIndex,
|
|
filterSource,
|
|
moduleDepthMap,
|
|
environmentFilter,
|
|
}: {
|
|
analyzeData: AnalyzeData
|
|
modulesData: ModulesData | null
|
|
selectedSourceIndex: number
|
|
moduleDepthMap: Map<number, number>
|
|
environmentFilter: 'client' | 'server'
|
|
filterSource: (sourceIndex: number) => boolean
|
|
}) {
|
|
const specialModuleType = getSpecialModuleType(
|
|
analyzeData,
|
|
selectedSourceIndex
|
|
)
|
|
|
|
const selectedSource =
|
|
selectedSourceIndex != null
|
|
? analyzeData.source(selectedSourceIndex)
|
|
: undefined
|
|
|
|
const hasChildModules =
|
|
selectedSourceIndex != null &&
|
|
analyzeData.sourceChildren(selectedSourceIndex).length > 0
|
|
|
|
const childModuleCount =
|
|
hasChildModules && selectedSourceIndex != null
|
|
? analyzeData.getRecursiveModuleCount(selectedSourceIndex, filterSource)
|
|
: null
|
|
|
|
const { size, compressedSize } = analyzeData.getRecursiveSizes(
|
|
selectedSourceIndex,
|
|
filterSource
|
|
)
|
|
|
|
const chunks =
|
|
selectedSourceIndex != null
|
|
? analyzeData.sourceChunks(selectedSourceIndex)
|
|
: []
|
|
|
|
return (
|
|
<div className="flex-1 p-3 space-y-8 overflow-y-auto">
|
|
<div className="space-y-2">
|
|
<h2 className="text-s font-semibold mb-1 text-foreground truncate">
|
|
{selectedSource?.path || 'All Route Modules'}
|
|
</h2>
|
|
{selectedSourceIndex != null &&
|
|
analyzeData.source(selectedSourceIndex) ? (
|
|
<div className="text-xs">
|
|
<div>
|
|
<span>{formatBytes(compressedSize)}</span>
|
|
<span className="text-muted-foreground ml-1">
|
|
compressed (estimated)
|
|
</span>
|
|
<InlineHelpTooltip>
|
|
Estimated compressed size. Modules are compressed in isolation
|
|
which may differ from their size in the final chunk.
|
|
</InlineHelpTooltip>
|
|
</div>
|
|
<div>
|
|
<span>{formatBytes(size)}</span>{' '}
|
|
<span className="text-muted-foreground">uncompressed</span>
|
|
<InlineHelpTooltip>
|
|
Uncompressed modules may still be minified, tree-shaken, and
|
|
dead-code eliminated. They just don't account for general
|
|
compression like gzip.
|
|
</InlineHelpTooltip>
|
|
</div>
|
|
{hasChildModules && childModuleCount != null ? (
|
|
<div>
|
|
<span>{childModuleCount} </span>
|
|
<span className="text-muted-foreground">
|
|
{childModuleCount === 1 ? 'module' : 'modules'}
|
|
</span>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
{selectedSourceIndex != null &&
|
|
analyzeData.source(selectedSourceIndex) &&
|
|
(specialModuleType === SpecialModule.POLYFILL_MODULE ||
|
|
specialModuleType === SpecialModule.POLYFILL_NOMODULE) && (
|
|
<dl className="flex items-center gap-2">
|
|
<dt className="inline-flex items-center">
|
|
<Badge variant="polyfill">Polyfill</Badge>
|
|
</dt>
|
|
<dd className="text-xs text-muted-foreground">
|
|
Next.js built-in polyfills
|
|
</dd>
|
|
</dl>
|
|
)}
|
|
|
|
{selectedSourceIndex != null &&
|
|
analyzeData.source(selectedSourceIndex) &&
|
|
!hasChildModules && (
|
|
<>
|
|
{modulesData && (
|
|
<ImportChain
|
|
startFileId={selectedSourceIndex}
|
|
analyzeData={analyzeData}
|
|
modulesData={modulesData}
|
|
depthMap={moduleDepthMap}
|
|
environmentFilter={environmentFilter}
|
|
/>
|
|
)}
|
|
{chunks.length > 0 ? (
|
|
<div className="mt-2">
|
|
<p className="text-xs font-semibold text-foreground">
|
|
Output Chunks
|
|
</p>
|
|
<ul className="text-xs text-muted-foreground font-mono mt-1 space-y-1">
|
|
{chunks.map((chunk) => (
|
|
<li key={chunk} className="break-all">
|
|
{chunk}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
) : null}
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function InlineHelpTooltip({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<CircleHelp
|
|
size={14}
|
|
className="inline-block ml-1 text-muted-foreground"
|
|
aria-hidden="true"
|
|
/>
|
|
</TooltipTrigger>
|
|
<TooltipContent className="max-w-xs" side="top" align="center">
|
|
{children}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
)
|
|
}
|