Skip to content

Commit 0b3d0cc

Browse files
Copilotsapphi-red
andcommitted
Implement sourcemap visualizer feature with UI controls
Co-authored-by: sapphi-red <[email protected]>
1 parent 5bdd179 commit 0b3d0cc

File tree

4 files changed

+96
-10
lines changed

4 files changed

+96
-10
lines changed

app/components/Navbar.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
DEFAULT_ENTRY,
88
defaultFiles,
99
files,
10+
sourcemapEnabled,
1011
timeCost,
1112
} from '~/state/bundler'
1213
import { handleDownloadProject } from '~/utils/download'
@@ -83,6 +84,19 @@ function resetState() {
8384
<span op80>{{ timeCost }}ms</span>
8485
</div>
8586

87+
<label
88+
flex
89+
cursor-pointer
90+
items-center
91+
gap1
92+
text-sm
93+
title="Generate source maps for visualization"
94+
>
95+
<input v-model="sourcemapEnabled" type="checkbox" class="mr-1" />
96+
<div i-ri:map-line op60 />
97+
<span op80>Sourcemap</span>
98+
</label>
99+
86100
<button title="Reset State" nav-button @click="resetState">
87101
<div i-ri:refresh-line />
88102
</button>

app/components/OutputContainer.vue

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
<script setup lang="ts">
22
import ansis from 'ansis'
33
import { build } from '~/composables/bundler'
4-
import { CONFIG_FILES, currentVersion, files, timeCost } from '~/state/bundler'
4+
import {
5+
CONFIG_FILES,
6+
currentVersion,
7+
files,
8+
sourcemapEnabled,
9+
timeCost,
10+
} from '~/state/bundler'
511
612
const { data: rolldownVersions } = await useRolldownVersions()
713
@@ -73,6 +79,14 @@ const { data, status, error, refresh } = useAsyncData(
7379
const startTime = performance.now()
7480
7581
try {
82+
// Ensure sourcemap is enabled if the state is set
83+
if (sourcemapEnabled.value && !configObject.output?.sourcemap) {
84+
if (!configObject.output) {
85+
configObject.output = {}
86+
}
87+
configObject.output.sourcemap = true
88+
}
89+
7690
const result = await build(core, files.value, entries, configObject)
7791
return result
7892
} finally {
@@ -82,7 +96,7 @@ const { data, status, error, refresh } = useAsyncData(
8296
{ server: false, deep: false },
8397
)
8498
85-
watch([files, currentVersion], () => refresh(), {
99+
watch([files, currentVersion, sourcemapEnabled], () => refresh(), {
86100
deep: true,
87101
})
88102
@@ -110,6 +124,31 @@ const errorText = computed(() => {
110124
}
111125
return `${str}\n\n${stack && str !== stack ? `${stack}\n` : ''}`
112126
})
127+
128+
// Helper function for UTF-8 encoding (needed for sourcemap visualization with unicode)
129+
const utf16ToUTF8 = (str: string) => unescape(encodeURIComponent(str))
130+
131+
const sourcemapLinks = computed(() => {
132+
if (!data.value?.output || !data.value?.sourcemaps) return {}
133+
134+
const links: Record<string, string> = {}
135+
136+
for (const [fileName, code] of Object.entries(data.value.output)) {
137+
const sourcemap = data.value.sourcemaps[fileName]
138+
if (code && sourcemap) {
139+
// Encode for source map visualization
140+
const encodedCode = utf16ToUTF8(code)
141+
const encodedMap = utf16ToUTF8(sourcemap)
142+
const hash = btoa(
143+
`${encodedCode.length}\0${encodedCode}${encodedMap.length}\0${encodedMap}`,
144+
)
145+
links[fileName] =
146+
`https://evanw.github.io/source-map-visualization/#${hash}`
147+
}
148+
}
149+
150+
return links
151+
})
113152
</script>
114153

115154
<template>
@@ -133,14 +172,32 @@ const errorText = computed(() => {
133172
w-full
134173
flex-1
135174
>
136-
<CodeEditor
137-
:model-value="data?.output[value] || ''"
138-
language="javascript"
139-
readonly
140-
min-h-0
141-
w-full
142-
flex-1
143-
/>
175+
<div min-h-0 w-full flex flex-1 flex-col>
176+
<CodeEditor
177+
:model-value="data?.output[value] || ''"
178+
language="javascript"
179+
readonly
180+
min-h-0
181+
w-full
182+
flex-1
183+
/>
184+
<a
185+
v-if="sourcemapLinks[value]"
186+
class="m-2 flex items-center self-start text-sm opacity-80"
187+
:href="sourcemapLinks[value]"
188+
target="_blank"
189+
rel="noopener"
190+
>
191+
<span
192+
class="text-[#3c3c43] font-medium dark:text-[#fffff5]/[.86] hover:text-[#3451b2] dark:hover:text-[#a8b1ff]"
193+
>
194+
Visualize source map
195+
</span>
196+
<div
197+
class="i-ri:arrow-right-up-line ml-1 h-3 w-3 text-[#3c3c43]/[.56] dark:text-[#fffff5]/[.6]"
198+
/>
199+
</a>
200+
</div>
144201
</Tabs>
145202
<div
146203
v-if="status === 'success' && data?.warnings?.length"

app/composables/bundler.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { InputOptions, OutputOptions } from '@rolldown/browser'
33

44
export interface TransformResult {
55
output: Record<string, string>
6+
sourcemaps?: Record<string, string>
67
warnings?: string[]
78
}
89

@@ -61,8 +62,21 @@ export async function build(
6162
],
6263
),
6364
)
65+
66+
const sourcemaps = Object.fromEntries(
67+
result.output
68+
.filter((chunk) => chunk.type === 'chunk' && (chunk as any).map)
69+
.map((chunk) => [
70+
chunk.fileName,
71+
typeof (chunk as any).map === 'string'
72+
? (chunk as any).map
73+
: JSON.stringify((chunk as any).map),
74+
]),
75+
)
76+
6477
return {
6578
output,
79+
sourcemaps: Object.keys(sourcemaps).length > 0 ? sourcemaps : undefined,
6680
warnings,
6781
}
6882
}

app/state/bundler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ export const defaultFiles = () => {
2222
export const files = ref<SourceFileMap>(defaultFiles())
2323
export const activeFile = ref<string>()
2424
export const timeCost = ref<number>()
25+
export const sourcemapEnabled = ref<boolean>(false)
2526

2627
export const currentVersion = ref<string>('latest')

0 commit comments

Comments
 (0)