Skip to content
Merged
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
102 changes: 102 additions & 0 deletions packages/rolldown/src/app/components/compare/ModulePanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<script setup lang="ts">
import type { SessionContext } from '~~/shared/types'
import { onKeyStroke } from '@vueuse/core'
import { computed, reactive, watch } from 'vue'
import { useRpc } from '#imports'
import { getFileTypeFromName } from '~/utils/icon'

interface ModuleSide {
session: string
module: string
}

const props = defineProps<{
previous?: ModuleSide
current?: ModuleSide
side: 'previous' | 'current'
}>()

const emit = defineEmits<{
(e: 'close'): void
(e: 'update:side', side: 'previous' | 'current'): void
}>()

const rpc = useRpc()

// Lazily load and cache the full SessionContext for each session id.
const sessionCache = new Map<string, SessionContext>()
const contexts = reactive<Record<string, SessionContext | undefined>>({})

async function loadSession(id: string) {
if (sessionCache.has(id)) {
contexts[id] = sessionCache.get(id)
return
}
const summary = await rpc.value.call('vite:rolldown:get-session-summary', { session: id })
const context: SessionContext = {
id,
meta: summary.meta!,
modulesList: summary.modules.map(mod => ({
id: mod.id,
fileType: getFileTypeFromName(mod.id).name,
imports: mod.imports ?? [],
importers: mod.importers ?? [],
buildMetrics: mod.build_metrics,
})),
buildDuration: summary.build_duration,
}
sessionCache.set(id, context)
contexts[id] = context
}

const activeSide = computed(() => props.side === 'current' ? props.current : props.previous)
const activeSession = computed(() => activeSide.value ? contexts[activeSide.value.session] : undefined)

watch(activeSide, (sideValue) => {
if (sideValue)
loadSession(sideValue.session)
}, { immediate: true })

onKeyStroke('Escape', (e) => {
e.preventDefault()
emit('close')
})
</script>

<template>
<div
class="fixed right-0 bottom-0 top-20 left-20 z-panel-content bg-glass border-l border-t border-base rounded-tl-xl of-hidden"
>
<DataModuleDetailsLoader
v-if="activeSide && activeSession"
:key="`${activeSide.session}:${activeSide.module}`"
:module="activeSide.module"
:session="activeSession"
@close="emit('close')"
/>
<VisualLoading v-else />

<!-- A/B switch floats above the loader, only meaningful when both sides exist -->
<div
v-if="previous && current"
class="absolute top-2 left-1/2 translate-x--1/2 z-panel-content flex items-center gap-1 p0.5 bg-glass border border-base rounded-lg"
>
<button
type="button"
class="px3 py1 rounded-md text-sm"
:class="side === 'previous' ? 'bg-active font-600' : 'op60 hover:op100'"
@click="emit('update:side', 'previous')"
>
Session A
</button>
<button
type="button"
class="px3 py1 rounded-md text-sm"
:class="side === 'current' ? 'bg-active font-600' : 'op60 hover:op100'"
@click="emit('update:side', 'current')"
>
Session B
</button>
</div>
</div>
</template>
15 changes: 14 additions & 1 deletion packages/rolldown/src/app/components/compare/SingleSideRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,21 @@ withDefaults(defineProps<{
delta: number
format: 'bytes' | 'duration' | 'number'
subtitle?: string
link?: string
badges?: string[]
stats?: SingleSideDiffStat[]
selectable?: boolean
}>(), {
subtitle: '',
titleMeta: '',
badges: () => [],
stats: () => [],
selectable: false,
})

const emit = defineEmits<{
(e: 'select'): void
}>()
</script>

<template>
Expand All @@ -32,7 +39,13 @@ 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>
<button v-if="selectable" type="button" class="truncate hover:underline text-left" @click="emit('select')">
{{ title }}
</button>
<NuxtLink v-else-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
24 changes: 22 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 All @@ -25,14 +27,20 @@ const props = withDefaults(defineProps<{
delta: number
format: 'bytes' | 'duration' | 'number'
ratioText?: string
selectable?: boolean
}>(), {
previousStats: () => [],
currentStats: () => [],
previousBadges: () => [],
currentBadges: () => [],
ratioText: '',
selectable: false,
})

const emit = defineEmits<{
(e: 'select', side: 'previous' | 'current'): void
}>()

const ratioClass = computed(() => {
if (props.delta > 0)
return 'text-red-500'
Expand Down Expand Up @@ -62,7 +70,13 @@ 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>
<button v-if="selectable" type="button" class="truncate hover:underline text-left" @click="emit('select', 'previous')">
{{ previousTitle }}
</button>
<NuxtLink v-else-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 +106,13 @@ 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>
<button v-if="selectable" type="button" class="truncate hover:underline text-left" @click="emit('select', 'current')">
{{ currentTitle }}
</button>
<NuxtLink v-else-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
Loading
Loading