Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ withDefaults(defineProps<{
delta: number
format: 'bytes' | 'duration' | 'number'
subtitle?: string
link?: string
badges?: string[]
stats?: SingleSideDiffStat[]
}>(), {
Expand All @@ -32,7 +33,10 @@ withDefaults(defineProps<{
{{ sessionLabel }}
</div>
<div class="flex items-baseline gap-2 min-w-0 font-mono" :title="titleMeta ? `${title} (${titleMeta})` : title">
<span class="truncate">{{ title }}</span>
<NuxtLink v-if="link" :to="link" class="truncate hover:underline">
{{ title }}
</NuxtLink>
<span v-else class="truncate">{{ title }}</span>
<span v-if="titleMeta" class="flex-none op50">({{ titleMeta }})</span>
</div>
<div v-if="subtitle" class="truncate text-xs op55 mt1" :title="subtitle">
Expand Down
12 changes: 10 additions & 2 deletions packages/rolldown/src/app/components/compare/SplitRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const props = withDefaults(defineProps<{
currentTitleMeta?: string
previousSubtitle?: string
currentSubtitle?: string
previousLink?: string
currentLink?: string
previousStats?: SplitDiffStat[]
currentStats?: SplitDiffStat[]
previousBadges?: string[]
Expand Down Expand Up @@ -62,7 +64,10 @@ const ratioClass = computed(() => {
Session A
</div>
<div class="flex items-baseline gap-2 min-w-0 font-mono font-600" :title="previousTitleMeta ? `${previousTitle} (${previousTitleMeta})` : previousTitle">
<span class="truncate">{{ previousTitle }}</span>
<NuxtLink v-if="previousLink" :to="previousLink" class="truncate hover:underline">
{{ previousTitle }}
</NuxtLink>
<span v-else class="truncate">{{ previousTitle }}</span>
<span v-if="previousTitleMeta" class="flex-none font-400 op50">({{ previousTitleMeta }})</span>
</div>
<div v-if="previousSubtitle" class="truncate text-xs op50 mt1" :title="previousSubtitle">
Expand Down Expand Up @@ -92,7 +97,10 @@ const ratioClass = computed(() => {
Session B
</div>
<div class="flex items-baseline gap-2 min-w-0 font-mono font-600" :title="currentTitleMeta ? `${currentTitle} (${currentTitleMeta})` : currentTitle">
<span class="truncate">{{ currentTitle }}</span>
<NuxtLink v-if="currentLink" :to="currentLink" class="truncate hover:underline">
{{ currentTitle }}
</NuxtLink>
<span v-else class="truncate">{{ currentTitle }}</span>
<span v-if="currentTitleMeta" class="flex-none font-400 op50">({{ currentTitleMeta }})</span>
</div>
<div v-if="currentSubtitle" class="truncate text-xs op50 mt1" :title="currentSubtitle">
Expand Down
117 changes: 116 additions & 1 deletion packages/rolldown/src/app/components/compare/TabView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -37,6 +38,8 @@ interface SplitDiffRow extends SessionCompareMetricValue {
currentTitleMeta?: string
previousSubtitle?: string
currentSubtitle?: string
previousLink?: string
currentLink?: string
previousStats?: SplitDiffStat[]
currentStats?: SplitDiffStat[]
previousBadges: string[]
Expand All @@ -54,7 +57,13 @@ const props = defineProps<{

const TABLE_LIMIT = 300

const route = useRoute()
const router = useRouter()

const activeDiffMode = ref<DiffMode>('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(() => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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))

Expand All @@ -250,6 +290,8 @@ const chunkRows = computed<SplitDiffRow[]>(() => 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,
Expand All @@ -270,6 +312,29 @@ const chunkRows = computed<SplitDiffRow[]>(() => chunkDiffs.value.map(item => cr
].filter(Boolean) as string[],
})))

const moduleRows = computed<SplitDiffRow[]>(() => {
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<SplitDiffRow[]>(() => packageDiffs.value.map(item => createSplitRow({
key: item.key,
status: item.status,
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -586,6 +671,28 @@ function sumDelta(rows: SplitDiffRow[]) {

<template v-else>
<div class="flex flex-col gap-4">
<div v-if="props.tab === 'modules'" class="flex items-center gap-2">
<div class="flex-1 flex items-center gap-2 border border-base rounded px3 py1.5">
<div class="i-ph-magnifying-glass-duotone op50" />
<input
v-model="moduleSearch"
class="flex-1 bg-transparent outline-none font-mono text-sm"
placeholder="Filter modules by path or package"
>
</div>
<button
v-if="moduleChunkFilter"
type="button"
class="btn-action"
title="Clear chunk filter"
@click="clearModuleChunkFilter"
>
<div class="i-ph-shapes-duotone" />
<span class="font-mono">{{ moduleChunkFilterName }}</span>
<div class="i-ph-x" />
</button>
</div>

<div class="grid grid-cols-3 gap-3">
<button
v-for="item of diffModeOptions"
Expand Down Expand Up @@ -618,6 +725,8 @@ function sumDelta(rows: SplitDiffRow[]) {
:current-title-meta="row.currentTitleMeta"
:previous-subtitle="row.previousSubtitle"
:current-subtitle="row.currentSubtitle"
:previous-link="row.previousLink"
:current-link="row.currentLink"
:previous-stats="row.previousStats"
:current-stats="row.currentStats"
:previous-badges="row.previousBadges"
Expand All @@ -639,6 +748,7 @@ function sumDelta(rows: SplitDiffRow[]) {
:title="row.currentTitle || row.key"
:title-meta="row.currentTitleMeta"
:subtitle="row.currentSubtitle"
:link="row.currentLink"
:badges="row.currentBadges"
:stats="row.currentStats"
:delta="row.delta"
Expand All @@ -656,6 +766,7 @@ function sumDelta(rows: SplitDiffRow[]) {
:title="row.previousTitle || row.key"
:title-meta="row.previousTitleMeta"
:subtitle="row.previousSubtitle"
:link="row.previousLink"
:badges="row.previousBadges"
:stats="row.previousStats"
:delta="row.delta"
Expand All @@ -665,6 +776,10 @@ function sumDelta(rows: SplitDiffRow[]) {
</template>
</section>

<div v-if="activeDiffRows.length > TABLE_LIMIT" class="text-center text-sm op50">
Showing {{ TABLE_LIMIT }} of {{ activeDiffRows.length.toLocaleString() }} entries{{ props.tab === 'modules' ? ', filter to narrow down' : '' }}
</div>

<div v-if="!activeDiffRows.length" class="h40 flex items-center justify-center op50 italic">
{{ activeDiffEmptyText }}
</div>
Expand Down
5 changes: 5 additions & 0 deletions packages/rolldown/src/app/pages/compare/[sessions].vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ const compareTabs = [
label: 'Chunks',
icon: 'i-ph-shapes-duotone',
},
{
id: 'modules',
label: 'Modules',
icon: 'i-ph-graph-duotone',
},
{
id: 'assets',
label: 'Assets',
Expand Down
28 changes: 28 additions & 0 deletions packages/rolldown/src/app/pages/compare/[sessions]/modules.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script setup lang="ts">
import type {
SessionCompareContext,
SessionCompareDetails,
} from '~~/shared/types'

interface CompareTabItem {
id: string
label: string
icon: string
}
type NormalizedSessionCompareContext = SessionCompareContext & { createdAt: Date, title: string }

defineProps<{
tabs: CompareTabItem[]
sessions: NormalizedSessionCompareContext[]
details: SessionCompareDetails | null
}>()
</script>

<template>
<CompareTabView
tab="modules"
:tabs="tabs"
:sessions="sessions"
:details="details"
/>
</template>
Loading
Loading