From adedd4e6b7dc7c91c1a1b650c51a75a3b3bc8e8f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:14:57 +0000 Subject: [PATCH 01/10] Initial plan From 04518836a1e0be4b39d27a984e546e66c423bb22 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:21:46 +0000 Subject: [PATCH 02/10] Add graph visualization panel for module and chunk graphs Co-authored-by: sapphi-red <49056869+sapphi-red@users.noreply.github.com> --- app/app.vue | 2 +- app/components/GraphContainer.vue | 248 +++++++++++++++++++++++++++++ app/components/OutputContainer.vue | 12 ++ app/components/OutputView.vue | 67 ++++++++ app/composables/bundler.ts | 58 +++++++ app/state/output.ts | 7 + 6 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 app/components/GraphContainer.vue create mode 100644 app/components/OutputView.vue create mode 100644 app/state/output.ts diff --git a/app/app.vue b/app/app.vue index f3bf263..42ca3d2 100644 --- a/app/app.vue +++ b/app/app.vue @@ -32,7 +32,7 @@ useHeadSafe({
- +
diff --git a/app/components/GraphContainer.vue b/app/components/GraphContainer.vue new file mode 100644 index 0000000..675ff6f --- /dev/null +++ b/app/components/GraphContainer.vue @@ -0,0 +1,248 @@ + + + + + diff --git a/app/components/OutputContainer.vue b/app/components/OutputContainer.vue index e06db1a..96077aa 100644 --- a/app/components/OutputContainer.vue +++ b/app/components/OutputContainer.vue @@ -10,6 +10,7 @@ import { timeCost, } from '~/state/bundler' import { npmVfsFiles, userDependencies } from '~/state/npm' +import { bundlerError, bundlerOutput, bundlerStatus } from '~/state/output' const { data: rolldownVersions } = await useRolldownVersions() @@ -145,6 +146,17 @@ watch( watch([files, currentVersion], () => refresh(), { deep: true }) +// Sync with shared state +watch(data, (newData) => { + bundlerOutput.value = newData +}) +watch(status, (newStatus) => { + bundlerStatus.value = newStatus +}) +watch(error, (newError) => { + bundlerError.value = newError +}) + const isLoading = computed(() => status.value === 'pending') const isLoadingDebounced = useDebounce(isLoading, 100) diff --git a/app/components/OutputView.vue b/app/components/OutputView.vue new file mode 100644 index 0000000..25ff5f8 --- /dev/null +++ b/app/components/OutputView.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/app/composables/bundler.ts b/app/composables/bundler.ts index 8b22332..88e0f86 100644 --- a/app/composables/bundler.ts +++ b/app/composables/bundler.ts @@ -4,10 +4,32 @@ import type { OutputOptions, } from '@rolldown/browser' +export interface ModuleNode { + id: string + imports: string[] + dynamicImports: string[] + importers: string[] + dynamicImporters: string[] + isEntry: boolean +} + +export interface ChunkNode { + fileName: string + name: string + isEntry: boolean + isDynamicEntry: boolean + imports: string[] + dynamicImports: string[] + exports: string[] + modules: string[] +} + export interface TransformResult { output: Record sourcemaps?: Record warnings?: string[] + moduleGraph?: ModuleNode[] + chunkGraph?: ChunkNode[] } export async function build( @@ -58,9 +80,45 @@ export async function build( ]), ) + // Extract module graph from chunks + const moduleMap = new Map() + const chunks = result.output.filter( + (chunk): chunk is OutputChunk => chunk.type === 'chunk', + ) + + // Build module graph from all chunks + for (const chunk of chunks) { + for (const [moduleId] of Object.entries(chunk.modules)) { + if (!moduleMap.has(moduleId)) { + moduleMap.set(moduleId, { + id: moduleId, + imports: [], + dynamicImports: [], + importers: [], + dynamicImporters: [], + isEntry: chunk.isEntry && chunk.facadeModuleId === moduleId, + }) + } + } + } + + // Extract chunk graph + const chunkGraph: ChunkNode[] = chunks.map((chunk) => ({ + fileName: chunk.fileName, + name: chunk.name, + isEntry: chunk.isEntry, + isDynamicEntry: chunk.isDynamicEntry, + imports: chunk.imports, + dynamicImports: chunk.dynamicImports, + exports: chunk.exports, + modules: Object.keys(chunk.modules), + })) + return { output, sourcemaps, warnings, + moduleGraph: Array.from(moduleMap.values()), + chunkGraph, } } diff --git a/app/state/output.ts b/app/state/output.ts new file mode 100644 index 0000000..9a43b36 --- /dev/null +++ b/app/state/output.ts @@ -0,0 +1,7 @@ +import type { TransformResult } from '~/composables/bundler' + +export const bundlerOutput = ref() +export const bundlerStatus = ref<'idle' | 'pending' | 'error' | 'success'>( + 'idle', +) +export const bundlerError = ref(null) From a3698f6406ddf5d3698af0c198843d3d0519ecc1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:24:57 +0000 Subject: [PATCH 03/10] Fix code review feedback: improve module graph entry detection and props usage Co-authored-by: sapphi-red <49056869+sapphi-red@users.noreply.github.com> --- app/components/GraphContainer.vue | 18 +++++++++--------- app/composables/bundler.ts | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/components/GraphContainer.vue b/app/components/GraphContainer.vue index 675ff6f..38553c2 100644 --- a/app/components/GraphContainer.vue +++ b/app/components/GraphContainer.vue @@ -1,7 +1,7 @@ @@ -39,17 +39,17 @@ const stats = computed(() => {
- + -
+
{{ stats.modules }} modules, {{ stats.entries }} entries
{
-
+
{{ stats.chunks }} chunks
chunk.type === 'chunk', ) - // Build module graph from all chunks + // First pass: identify all entry modules + const entryModules = new Set() + for (const chunk of chunks) { + if (chunk.isEntry && chunk.facadeModuleId) { + entryModules.add(chunk.facadeModuleId) + } + } + + // Second pass: build module graph from all chunks for (const chunk of chunks) { for (const [moduleId] of Object.entries(chunk.modules)) { if (!moduleMap.has(moduleId)) { moduleMap.set(moduleId, { id: moduleId, + // Note: Rolldown's current output doesn't expose per-module import/importer relationships + // These would need to be populated from the module graph API if/when available imports: [], dynamicImports: [], importers: [], dynamicImporters: [], - isEntry: chunk.isEntry && chunk.facadeModuleId === moduleId, + isEntry: entryModules.has(moduleId), }) } } From 2e2c7584c60761893bb54b0fcdacd307e28e7404 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:28:56 +0000 Subject: [PATCH 04/10] Implement Mermaid visualization for module and chunk graphs Co-authored-by: sapphi-red <49056869+sapphi-red@users.noreply.github.com> --- app/components/GraphContainer.vue | 263 ++++++++++++++---------------- app/composables/graph-mermaid.ts | 133 +++++++++++++++ app/composables/mermaid.ts | 92 +++++++++++ 3 files changed, 344 insertions(+), 144 deletions(-) create mode 100644 app/composables/graph-mermaid.ts create mode 100644 app/composables/mermaid.ts diff --git a/app/components/GraphContainer.vue b/app/components/GraphContainer.vue index 38553c2..64bf92d 100644 --- a/app/components/GraphContainer.vue +++ b/app/components/GraphContainer.vue @@ -1,4 +1,8 @@