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
161 changes: 161 additions & 0 deletions packages/devtools-vite/src/app/components/chunks/Graph.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<script setup lang="ts">
import type { Chunk, ChunkImport } from '@rolldown/debug'
import type { SessionContext } from '~~/shared/types/data'
import type { ModuleGraphLink, ModuleGraphNode } from '~/composables/moduleGraph'
import { computed, nextTick, unref } from 'vue'
import { createModuleGraph } from '~/composables/moduleGraph'

const props = defineProps<{
session: SessionContext
chunks: ChunkInfo[]
}>()

const chunks = computed(() => props.chunks)

type ChunkInfo = Chunk & {
id: string
}
createModuleGraph<ChunkInfo, ChunkImport>({
modules: chunks,
spacing: {
width: 320,
height: 55,
linkOffset: 15,
margin: 120,
gap: 80,
},
generateGraph: (options) => {
const { isFirstCalculateGraph, scale, spacing, tree, hierarchy, collapsedNodes, container, modulesMap, nodes, links, nodesMap, linksMap, width, height, childToParentMap } = options
return () => {
width.value = window.innerWidth
height.value = window.innerHeight

const root = hierarchy<ModuleGraphNode<ChunkInfo, ChunkImport> & { end?: boolean }>(
{ module: { id: '~root' } } as any,
(parent) => {
if (`${parent.module?.id}` === '~root') {
chunks.value.forEach((x) => {
if (isFirstCalculateGraph.value) {
childToParentMap.set(x.id, '~root')
}
})
return chunks.value.map(x => ({
module: x,
expanded: !collapsedNodes.has(x.id),
hasChildren: false,
}))
}

if (collapsedNodes.has(`${parent.module?.id}`) || parent.end) {
return []
}

const nodes = parent.module.imports
.map((x): ModuleGraphNode<ChunkInfo, ChunkImport> & { end: boolean } | undefined => {
const module = modulesMap.value.get(`${x.chunk_id}`)
if (!module)
return undefined

if (isFirstCalculateGraph.value) {
childToParentMap.set(module.id, parent.module.id)
}

return {
module,
import: x,
expanded: !collapsedNodes.has(module.id),
hasChildren: false,
end: true,
}
})
.filter(x => x !== undefined)

return nodes
},
)

if (isFirstCalculateGraph.value) {
isFirstCalculateGraph.value = false
}

// Calculate the layout
const layout = tree<ModuleGraphNode<ChunkInfo, ChunkImport> & { end?: boolean }>()
.nodeSize([unref(spacing.height), unref(spacing.width) + unref(spacing.gap)])
layout(root)

const _nodes = root.descendants()

for (const node of _nodes) {
// Rotate the graph from top-down to left-right
[node.x, node.y] = [node.y! - unref(spacing.width), node.x!]

if (node.data.module.imports) {
node.data.hasChildren = node.data.module.imports.length > 0 && !node.data.end
}
}

// Offset the graph and adding margin
const minX = Math.min(..._nodes.map(n => n.x!))
const minY = Math.min(..._nodes.map(n => n.y!))
if (minX < unref(spacing.margin)) {
for (const node of _nodes) {
node.x! += Math.abs(minX) + unref(spacing.margin)
}
}
if (minY < unref(spacing.margin)) {
for (const node of _nodes) {
node.y! += Math.abs(minY) + unref(spacing.margin)
}
}

nodes.value = _nodes
nodesMap.clear()
for (const node of _nodes) {
nodesMap.set(`${node.data.module.id}`, node)
}
const _links = root.links()
.filter(x => `${x.source.data.module.id}` !== '~root')
.map((x): ModuleGraphLink<ChunkInfo, ChunkImport> => {
return {
...x,
import: x.source.data.import,
id: `${x.source.data.module.id}|${x.target.data.module.id}`,
}
})
linksMap.clear()
for (const link of _links) {
linksMap.set(link.id, link)
}
links.value = _links

nextTick(() => {
width.value = (container.value!.scrollWidth / scale.value + unref(spacing.margin))
height.value = (container.value!.scrollHeight / scale.value + unref(spacing.margin))
})
}
},
})
</script>

<template>
<DisplayModuleGraph
:session="session"
:modules="chunks"
>
<template #default="{ node }">
<div flex="~ items-center">
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After #82 merged, I'll add a click on the chunk graph node to open the chunk detail panel.

<span op50 font-mono w12>#{{ node.data.module.id }}</span>
<div flex="~ gap-2 items-center" :title="`Chunk #${node.data.module.id}`">
<div i-ph-shapes-duotone />
<div>{{ node.data.module.name || '[unnamed]' }}</div>
<DisplayBadge :text="node.data.module.reason" />
</div>
<div flex-auto />
<div flex="~ gap-1 items-center">
<div i-ph-package-duotone />
{{ node.data.module.modules.length }}
</div>
</div>
</template>
</DisplayModuleGraph>
</template>
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
<script setup lang="ts">
import type { Asset as AssetInfo } from '@rolldown/debug'
import type { HierarchyLink, HierarchyNode } from 'd3-hierarchy'
import { linkHorizontal, linkVertical } from 'd3-shape'
import type { HierarchyNode } from 'd3-hierarchy'
import type { ModuleGraphLink, ModuleGraphNode } from '~/composables/moduleGraph'
import { computed, onMounted, shallowRef, useTemplateRef, watch } from 'vue'
import { generateModuleGraphLink, getModuleGraphLinkColor } from '~/composables/moduleGraph'

const props = defineProps<{
importers?: AssetInfo[]
imports?: AssetInfo[]
}>()

interface Node {
asset: AssetInfo
}

type Link = HierarchyLink<Node> & {
id: string
}

type LinkPoint = 'importer-start' | 'importer-end' | 'import-start' | 'import-end'

const MAX_LINKS = 20
Expand All @@ -32,7 +25,7 @@ const SPACING = {
}

const container = useTemplateRef<HTMLDivElement>('container')
const links = shallowRef<Link[]>([])
const links = shallowRef<ModuleGraphLink<AssetInfo, AssetInfo>[]>([])

const normalizedMaxLinks = computed(() => {
return Math.min(Math.max(props.importers?.length || 0, props.imports?.length || 0), MAX_LINKS)
Expand All @@ -54,31 +47,6 @@ const importsVerticalOffset = computed(() => {
return Math.min(offset, nodesHeight.value / 2)
})

const createLinkHorizontal = linkHorizontal()
.x(d => d[0])
.y(d => d[1])

const createLinkVertical = linkVertical()
.x(d => d[0])
.y(d => d[1])

function generateLink(link: Link) {
if (link.target.x! <= link.source.x!) {
return createLinkVertical({
source: [link.source.x!, link.source.y!],
target: [link.target.x!, link.target.y!],
})
}
return createLinkHorizontal({
source: [link.source.x!, link.source.y!],
target: [link.target.x!, link.target.y!],
})
}

function getLinkColor(_link: Link) {
return 'stroke-#8882'
}

const dotNodeMargin = computed(() => `${nodesHeight.value / 2 - SPACING.dot / 2}px ${SPACING.dotOffset}px 0 ${props.importers?.length ? SPACING.dotOffset : 0}px`)
const linkStartX = computed(() => props.importers?.length ? SPACING.width + SPACING.marginX : SPACING.marginX)
const dotStartX = computed(() => props.importers?.length ? linkStartX.value + SPACING.dotOffset : linkStartX.value)
Expand Down Expand Up @@ -120,11 +88,11 @@ function generateLinks() {
source: {
x: calculateLinkX('importer-start'),
y: calculateLinkY('importer-start', i),
} as HierarchyNode<Node>,
} as HierarchyNode<ModuleGraphNode<AssetInfo, AssetInfo>>,
target: {
x: calculateLinkX('importer-end'),
y: calculateLinkY('importer-end'),
} as HierarchyNode<Node>,
} as HierarchyNode<ModuleGraphNode<AssetInfo, AssetInfo>>,
}
})
links.value.push(..._importersLinks)
Expand All @@ -138,11 +106,11 @@ function generateLinks() {
source: {
x: calculateLinkX('import-start'),
y: calculateLinkY('import-start'),
} as HierarchyNode<Node>,
} as HierarchyNode<ModuleGraphNode<AssetInfo, AssetInfo>>,
target: {
x: calculateLinkX('import-end'),
y: calculateLinkY('import-end', i),
} as HierarchyNode<Node>,
} as HierarchyNode<ModuleGraphNode<AssetInfo, AssetInfo>>,
}
})
links.value.push(..._importsLinks)
Expand Down Expand Up @@ -255,8 +223,8 @@ onMounted(() => {
<path
v-for="link of links"
:key="link.id"
:d="generateLink(link)!"
:class="getLinkColor(link)"
:d="generateModuleGraphLink<AssetInfo, AssetInfo>(link)!"
:class="getModuleGraphLinkColor<AssetInfo, AssetInfo>(link)"
fill="none"
/>
</g>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
<script setup lang="ts">
import type { HierarchyLink, HierarchyNode } from 'd3-hierarchy'
import type { HierarchyNode } from 'd3-hierarchy'
import type { ModuleImport, ModuleInfo, ModuleListItem, SessionContext } from '~~/shared/types'
import { linkHorizontal, linkVertical } from 'd3-shape'
import type { ModuleGraphLink, ModuleGraphNode } from '~/composables/moduleGraph'
import { computed, onMounted, shallowRef, useTemplateRef, watch } from 'vue'
import { generateModuleGraphLink, getModuleGraphLinkColor } from '~/composables/moduleGraph'

const props = defineProps<{
module: ModuleInfo
session: SessionContext
}>()

interface Node {
module: ModuleListItem
import?: ModuleImport
}

type Link = HierarchyLink<Node> & {
id: string
import?: ModuleImport
}

type LinkPoint = 'importer-start' | 'importer-end' | 'import-start' | 'import-end'

const MAX_LINKS = 20
Expand All @@ -31,10 +22,11 @@ const SPACING = {
margin: 8,
dot: 16,
dotOffset: 80,
linkOffset: 0,
}

const container = useTemplateRef<HTMLDivElement>('container')
const links = shallowRef<Link[]>([])
const links = shallowRef<ModuleGraphLink<ModuleListItem, ModuleImport>[]>([])

const modulesMap = computed(() => {
const map = new Map<string, ModuleListItem>()
Expand Down Expand Up @@ -66,31 +58,6 @@ const importsVerticalOffset = computed(() => {
return Math.min(offset, nodesHeight.value / 2)
})

const createLinkHorizontal = linkHorizontal()
.x(d => d[0])
.y(d => d[1])

const createLinkVertical = linkVertical()
.x(d => d[0])
.y(d => d[1])

function generateLink(link: Link) {
if (link.target.x! <= link.source.x!) {
return createLinkVertical({
source: [link.source.x!, link.source.y!],
target: [link.target.x!, link.target.y!],
})
}
return createLinkHorizontal({
source: [link.source.x!, link.source.y!],
target: [link.target.x!, link.target.y!],
})
}

function getLinkColor(_link: Link) {
return 'stroke-#8882'
}

const dotNodeMargin = computed(() => `${nodesHeight.value / 2 - SPACING.dot / 2}px ${SPACING.dotOffset}px 0 ${importers.value?.length ? SPACING.dotOffset : 0}px`)
const linkStartX = computed(() => importers.value?.length ? SPACING.width + SPACING.marginX : SPACING.marginX)
const dotStartX = computed(() => importers.value?.length ? linkStartX.value + SPACING.dotOffset : linkStartX.value)
Expand Down Expand Up @@ -131,11 +98,11 @@ function generateLinks() {
source: {
x: calculateLinkX('importer-start'),
y: calculateLinkY('importer-start', i),
} as HierarchyNode<Node>,
} as HierarchyNode<ModuleGraphNode<ModuleListItem, ModuleImport>>,
target: {
x: calculateLinkX('importer-end'),
y: calculateLinkY('importer-end'),
} as HierarchyNode<Node>,
} as HierarchyNode<ModuleGraphNode<ModuleListItem, ModuleImport>>,
}
})
links.value.push(..._importersLinks)
Expand All @@ -148,11 +115,11 @@ function generateLinks() {
source: {
x: calculateLinkX('import-start'),
y: calculateLinkY('import-start'),
} as HierarchyNode<Node>,
} as HierarchyNode<ModuleGraphNode<ModuleListItem, ModuleImport>>,
target: {
x: calculateLinkX('import-end'),
y: calculateLinkY('import-end', i),
} as HierarchyNode<Node>,
} as HierarchyNode<ModuleGraphNode<ModuleListItem, ModuleImport>>,
}
})
links.value.push(..._importsLinks)
Expand Down Expand Up @@ -250,8 +217,8 @@ onMounted(() => {
<path
v-for="link of links"
:key="link.id"
:d="generateLink(link)!"
:class="getLinkColor(link)"
:d="generateModuleGraphLink<ModuleListItem, ModuleImport>(link)!"
:class="getModuleGraphLinkColor<ModuleListItem, ModuleImport>(link)"
:stroke-dasharray="link.import?.kind === 'dynamic-import' ? '3 6' : undefined"
fill="none"
/>
Expand Down
Loading
Loading