From bdcfa7bdbf6b7a7f49b41f30864adbc6bf3d8c55 Mon Sep 17 00:00:00 2001 From: Ben Maurer Date: Fri, 17 Apr 2026 22:34:59 -0700 Subject: [PATCH] bundle-analyzer: add 'Sync only' filter toggle Adds a toggle in the bundle-analyzer toolbar that filters the treemap to show only modules reachable from an entry without crossing an async import() boundary. Uses the existing `computeModuleDepthMap` where async edges add 1000 to the depth; a source is kept if any of its module indices has depth < 1000. Helps developers see what's actually in the initial critical path without the noise of lazy-loaded code. --- apps/bundle-analyzer/app/page.tsx | 45 ++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/apps/bundle-analyzer/app/page.tsx b/apps/bundle-analyzer/app/page.tsx index 4811c03ba4fc..eb47f3a6eea4 100644 --- a/apps/bundle-analyzer/app/page.tsx +++ b/apps/bundle-analyzer/app/page.tsx @@ -11,6 +11,7 @@ import { Sidebar } from '@/components/sidebar' import { TreemapVisualizer } from '@/components/treemap-visualizer' import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' import { TreemapSkeleton } from '@/components/ui/skeleton' import { Select, @@ -32,6 +33,7 @@ import { FileJson, Palette, Package, + Zap, } from 'lucide-react' enum Environment { @@ -45,6 +47,7 @@ export default function Home() { Environment.Client ) const [typeFilter, setTypeFilter] = useState(['js', 'css', 'json']) + const [syncOnly, setSyncOnly] = useState(false) const [selectedSourceIndex, setSelectedSourceIndex] = useState( null ) @@ -139,9 +142,32 @@ export default function Home() { (typeFilter.includes('json') && flags.json) || (typeFilter.includes('asset') && flags.asset) + // Check sync-only filter: exclude sources that are only reachable + // through an async import() boundary. computeModuleDepthMap adds 1000 + // per async hop, so any module with depth < 1000 is synchronously + // reachable from an entry. A source is kept if at least one of its + // module indices is sync-reachable. + if (syncOnly && modulesData) { + const sourcePath = analyzeData.getFullSourcePath(sourceIndex) + const moduleIndices = sourcePath + ? modulesData.getModuleIndiciesFromPath(sourcePath) + : [] + const hasSyncModule = moduleIndices.some( + (mi) => (moduleDepthMap.get(mi) ?? Infinity) < 1000 + ) + if (!hasSyncModule) return false + } + return hasEnvironment && hasType } - }, [analyzeData, environmentFilter, typeFilter]) + }, [ + analyzeData, + environmentFilter, + typeFilter, + syncOnly, + modulesData, + moduleDepthMap, + ]) const handleMouseDown = () => { setIsResizing(true) @@ -177,6 +203,8 @@ export default function Home() { setFocusedSourceIndex={setFocusedSourceIndex} typeFilter={typeFilter} setTypeFilter={setTypeFilter} + syncOnly={syncOnly} + setSyncOnly={setSyncOnly} searchQuery={searchQuery} setSearchQuery={setSearchQuery} /> @@ -307,6 +335,8 @@ function TopBar({ setFocusedSourceIndex, typeFilter, setTypeFilter, + syncOnly, + setSyncOnly, searchQuery, setSearchQuery, }: { @@ -319,6 +349,8 @@ function TopBar({ setFocusedSourceIndex: (index: number | null) => void typeFilter: string[] setTypeFilter: (types: string[]) => void + syncOnly: boolean + setSyncOnly: (value: boolean) => void searchQuery: string setSearchQuery: (query: string) => void }) { @@ -373,6 +405,17 @@ function TopBar({ aria-label="Filter by file type" /> + +