From a139876a4f1f55082c52c874cf9d79aedea94a8f Mon Sep 17 00:00:00 2001 From: arlo Date: Tue, 20 Oct 2026 11:19:25 +0800 Subject: [PATCH] feat(rolldown): add modules to session compare Co-Authored-By: Claude Opus 5.5 (1M context) --- .../app/components/compare/SingleSideRow.vue | 6 +- .../src/app/components/compare/SplitRow.vue | 12 +- .../src/app/components/compare/TabView.vue | 117 +++++++++++++++++- .../src/app/pages/compare/[sessions].vue | 5 + .../app/pages/compare/[sessions]/modules.vue | 28 +++++ .../rolldown-get-session-compare-details.ts | 84 ++++++++++++- packages/rolldown/src/shared/types/data.ts | 15 +++ 7 files changed, 262 insertions(+), 5 deletions(-) create mode 100644 packages/rolldown/src/app/pages/compare/[sessions]/modules.vue diff --git a/packages/rolldown/src/app/components/compare/SingleSideRow.vue b/packages/rolldown/src/app/components/compare/SingleSideRow.vue index ad018de7f..7f26db27b 100644 --- a/packages/rolldown/src/app/components/compare/SingleSideRow.vue +++ b/packages/rolldown/src/app/components/compare/SingleSideRow.vue @@ -14,6 +14,7 @@ withDefaults(defineProps<{ delta: number format: 'bytes' | 'duration' | 'number' subtitle?: string + link?: string badges?: string[] stats?: SingleSideDiffStat[] }>(), { @@ -32,7 +33,10 @@ withDefaults(defineProps<{ {{ sessionLabel }}
- {{ title }} + + {{ title }} + + {{ title }} ({{ titleMeta }})
diff --git a/packages/rolldown/src/app/components/compare/SplitRow.vue b/packages/rolldown/src/app/components/compare/SplitRow.vue index b22b61825..af92c306f 100644 --- a/packages/rolldown/src/app/components/compare/SplitRow.vue +++ b/packages/rolldown/src/app/components/compare/SplitRow.vue @@ -16,6 +16,8 @@ const props = withDefaults(defineProps<{ currentTitleMeta?: string previousSubtitle?: string currentSubtitle?: string + previousLink?: string + currentLink?: string previousStats?: SplitDiffStat[] currentStats?: SplitDiffStat[] previousBadges?: string[] @@ -62,7 +64,10 @@ const ratioClass = computed(() => { Session A
- {{ previousTitle }} + + {{ previousTitle }} + + {{ previousTitle }} ({{ previousTitleMeta }})
@@ -92,7 +97,10 @@ const ratioClass = computed(() => { Session B
- {{ currentTitle }} + + {{ currentTitle }} + + {{ currentTitle }} ({{ currentTitleMeta }})
diff --git a/packages/rolldown/src/app/components/compare/TabView.vue b/packages/rolldown/src/app/components/compare/TabView.vue index d45ce450f..a36f80ee5 100644 --- a/packages/rolldown/src/app/components/compare/TabView.vue +++ b/packages/rolldown/src/app/components/compare/TabView.vue @@ -7,8 +7,9 @@ import type { } from '~~/shared/types' import { bytesToHumanSize } from '@vitejs/devtools-ui/utils/format' import { computed, ref, watch } from 'vue' +import { useRoute, useRouter } from '#app/composables/router' -type CompareTab = 'overview' | 'plugins' | 'chunks' | 'assets' | 'packages' +type CompareTab = 'overview' | 'plugins' | 'chunks' | 'modules' | 'assets' | 'packages' type DiffMode = 'changed' | 'added' | 'removed' type DiffFormat = 'bytes' | 'duration' | 'number' type SummaryTone = 'increase' | 'decrease' @@ -37,6 +38,8 @@ interface SplitDiffRow extends SessionCompareMetricValue { currentTitleMeta?: string previousSubtitle?: string currentSubtitle?: string + previousLink?: string + currentLink?: string previousStats?: SplitDiffStat[] currentStats?: SplitDiffStat[] previousBadges: string[] @@ -54,7 +57,13 @@ const props = defineProps<{ const TABLE_LIMIT = 300 +const route = useRoute() +const router = useRouter() + const activeDiffMode = ref('changed') +const moduleSearch = ref('') +const moduleChunkFilter = computed(() => typeof route.query.chunk === 'string' ? route.query.chunk : undefined) +const moduleChunkFilterName = computed(() => props.details?.chunks.find(chunk => chunk.key === moduleChunkFilter.value)?.name ?? moduleChunkFilter.value) const activeTabMeta = computed(() => props.tabs.find(tab => tab.id === props.tab)) const comparisonMetrics = computed(() => { @@ -167,6 +176,26 @@ const sessionSummaries = computed(() => { tone: getSummaryTone(index, session.initial_js, sessionA?.initial_js), }, ]) + case 'modules': { + return props.sessions.map((session, index) => { + const codeSize = sumCompareSide(props.details?.modules, index, item => item.previous, item => item.current) + const codeSizeA = sumCompareSide(props.details?.modules, 0, item => item.previous, item => item.current) + return [ + { + label: 'Modules', + value: session.modules.toLocaleString(), + icon: 'i-ph-graph-duotone', + tone: getSummaryTone(index, session.modules, sessionA?.modules), + }, + { + label: 'Code Size', + value: formatBytesSummary(codeSize), + icon: 'i-ph-file-code-duotone', + tone: getSummaryTone(index, codeSize, codeSizeA), + }, + ] + }) + } case 'packages': { const packageStatsA = getPackageSummaryStats(0) return props.sessions.map((_session, index) => { @@ -226,6 +255,17 @@ const sessionSummaries = computed(() => { const assetDiffs = computed(() => changedEntries(props.details?.assets)) const chunkDiffs = computed(() => changedEntries(props.details?.chunks)) +const moduleDiffs = computed(() => { + const chunk = moduleChunkFilter.value + const search = moduleSearch.value.trim().toLowerCase() + return changedEntries(props.details?.modules).filter((item) => { + if (chunk && !item.previousChunkKeys.includes(chunk) && !item.currentChunkKeys.includes(chunk)) + return false + if (search && !item.name.toLowerCase().includes(search) && !item.package?.toLowerCase().includes(search)) + return false + return true + }) +}) const packageDiffs = computed(() => changedEntries(props.details?.packages)) const pluginDiffs = computed(() => changedEntries(props.details?.plugins)) @@ -250,6 +290,8 @@ const chunkRows = computed(() => chunkDiffs.value.map(item => cr status: item.status, previousTitle: item.previousChunkId == null ? undefined : item.name, currentTitle: item.currentChunkId == null ? undefined : item.name, + previousLink: item.previousChunkId == null ? undefined : getChunkModulesLink(item.key), + currentLink: item.currentChunkId == null ? undefined : getChunkModulesLink(item.key), previousStats: createChunkStats(item.previousModules, item.previousImports), currentStats: createChunkStats(item.currentModules, item.currentImports, { modules: item.previousModules, @@ -270,6 +312,29 @@ const chunkRows = computed(() => chunkDiffs.value.map(item => cr ].filter(Boolean) as string[], }))) +const moduleRows = computed(() => { + const [sessionA, sessionB] = props.sessions + return moduleDiffs.value.map(item => createSplitRow({ + key: item.key, + status: item.status, + previousTitle: item.previousId == null ? undefined : item.name, + currentTitle: item.currentId == null ? undefined : item.name, + previousSubtitle: formatModuleChunks(item.previousChunks), + currentSubtitle: formatModuleChunks(item.currentChunks), + previousLink: item.previousId == null || !sessionA ? undefined : getModuleLink(sessionA.id, item.previousId), + currentLink: item.currentId == null || !sessionB ? undefined : getModuleLink(sessionB.id, item.currentId), + previous: item.previous, + current: item.current, + delta: item.delta, + deltaRatio: item.deltaRatio, + format: 'bytes', + previousBadges: item.package ? [item.package] : [], + currentBadges: item.moved + ? [item.package, 'moved'].filter(Boolean) as string[] + : item.package ? [item.package] : [], + })) +}) + const packageRows = computed(() => packageDiffs.value.map(item => createSplitRow({ key: item.key, status: item.status, @@ -322,6 +387,8 @@ const activeRows = computed(() => { return assetRows.value case 'chunks': return chunkRows.value + case 'modules': + return moduleRows.value case 'packages': return packageRows.value case 'plugins': @@ -420,6 +487,24 @@ function formatAssetTitleMeta(chunkName: string | undefined) { return chunkName?.replace(/[[\]]/g, '') } +function formatModuleChunks(chunks: string[]) { + if (!chunks.length) + return undefined + return `in ${chunks.map(chunk => chunk.replace(/[[\]]/g, '')).join(', ')}` +} + +function getChunkModulesLink(chunkKey: string) { + return `/compare/${props.sessions.map(session => session.id).join(',')}/modules?chunk=${encodeURIComponent(chunkKey)}` +} + +function clearModuleChunkFilter() { + router.replace({ query: { ...route.query, chunk: undefined } }) +} + +function getModuleLink(session: string, module: string) { + return `/session/${session}/graph?module=${encodeURIComponent(module)}` +} + function formatPackageTitle(name: string, version: string | undefined) { if (!version || version === '(unknown)') return name @@ -586,6 +671,28 @@ function sumDelta(rows: SplitDiffRow[]) {