Skip to content

Commit 68b8cfc

Browse files
authored
feat: error tab (#520)
1 parent 6129fa0 commit 68b8cfc

File tree

9 files changed

+141
-30
lines changed

9 files changed

+141
-30
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<script setup lang="ts">
2+
import type { StackFrame } from 'error-stack-parser-es'
3+
4+
defineProps<{
5+
stacktrace: StackFrame[]
6+
}>()
7+
8+
const config = useServerConfig()
9+
10+
function urlToFilepath(url: string) {
11+
try {
12+
let pathname = new URL(url).pathname
13+
if (pathname.startsWith('/_nuxt/'))
14+
pathname = pathname.slice(6)
15+
if (pathname.startsWith('/@id/virtual:nuxt:'))
16+
return `#build/${pathname.split('/.nuxt/')[1]}`.replace(/\.m?js$/, '')
17+
if (pathname.includes('/@fs/'))
18+
return `/${pathname.split('/@fs/')[1]}`
19+
return (config.value?.rootDir || '') + pathname
20+
}
21+
catch (e) {
22+
return url
23+
}
24+
}
25+
</script>
26+
27+
<template>
28+
<div mt2 grid="~ cols-[max-content_1fr] gap-x-4" font-mono>
29+
<template v-for="item, idx of stacktrace" :key="idx">
30+
<div text-right>
31+
{{ item.functionName || `(anonymous)` }}
32+
</div>
33+
<div ws-nowrap>
34+
<FilepathItem
35+
v-if="item.fileName"
36+
:filepath="`${urlToFilepath(item.fileName)}:${item.lineNumber}:${item.columnNumber}`"
37+
subpath
38+
/>
39+
</div>
40+
</template>
41+
</div>
42+
</template>

packages/devtools/client/components/TimelineDetailsFunction.vue

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,8 @@ const props = defineProps<{
55
record: TimelineEventFunction
66
}>()
77
8-
const config = useServerConfig()
98
const timeAgo = useTimeAgo(() => props.record.start, { showSecond: true })
109
11-
function urlToFilepath(url: string) {
12-
let pathname = new URL(url).pathname
13-
if (pathname.startsWith('/_nuxt/'))
14-
pathname = pathname.slice(6)
15-
if (pathname.startsWith('/@id/virtual:nuxt:'))
16-
return `#build/${pathname.split('/.nuxt/')[1]}`.replace(/\.m?js$/, '')
17-
if (pathname.includes('/@fs/'))
18-
return `/${pathname.split('/@fs/')[1]}`
19-
return (config.value?.rootDir || '') + pathname
20-
}
21-
2210
const autoImports = useAutoImports()
2311
const importsMetadata = computed(() => autoImports.value?.metadata)
2412
const importItem = computed(() => {
@@ -60,19 +48,9 @@ const importItem = computed(() => {
6048
</div>
6149
</div>
6250

63-
<div v-if="record.stacktrace" class="text-xs text-gray-400" mt2 grid="~ cols-[max-content_1fr] gap-x-4" font-mono>
64-
<template v-for="item, idx of record.stacktrace" :key="idx">
65-
<div text-right>
66-
{{ item.functionName || `(anonymous)` }}
67-
</div>
68-
<div ws-nowrap>
69-
<FilepathItem
70-
v-if="item.fileName"
71-
:filepath="`${urlToFilepath(item.fileName)}:${item.lineNumber}:${item.columnNumber}`"
72-
subpath
73-
/>
74-
</div>
75-
</template>
76-
</div>
51+
<StacktraceList
52+
v-if="record.stacktrace" :stacktrace="record.stacktrace"
53+
class="text-xs text-gray-400"
54+
/>
7755
</div>
7856
</template>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<script setup lang="ts">
2+
import { parse as parseStrackTrace } from 'error-stack-parser-es'
3+
4+
definePageMeta({
5+
icon: 'i-carbon-warning-alt-filled text-red',
6+
title: 'Error',
7+
category: 'app',
8+
show() {
9+
const client = useClient()
10+
return () => client.value?.nuxt?.payload?.error
11+
},
12+
})
13+
14+
const client = useClient()
15+
const error = computed(() => {
16+
const err = client.value?.nuxt?.payload?.error as {
17+
url?: string
18+
statusCode?: number
19+
statusMessage?: string
20+
message?: string
21+
description?: string
22+
data?: any
23+
} & Error
24+
if (err) {
25+
console.error('[Nuxt DevTools] Error in payload:')
26+
console.error(err)
27+
console.error({ ...err })
28+
}
29+
return err
30+
})
31+
32+
const stacks = computed(() => {
33+
if (!error.value?.stack)
34+
return []
35+
try {
36+
// Nuxt server returns a HTML rendered stacktrace, workaround by removing all HTML tags
37+
if (error.value.stack.startsWith('<pre>')) {
38+
return parseStrackTrace({
39+
stack: error.value.stack.replace(/<.*?>/g, ''),
40+
} as any)
41+
}
42+
return parseStrackTrace(error.value)
43+
}
44+
catch (e) {
45+
console.error(e)
46+
return []
47+
}
48+
})
49+
</script>
50+
51+
<template>
52+
<div p6>
53+
<div v-if="error">
54+
<NTip n="red" icon="i-carbon-warning-alt-filled" mb5>
55+
Error occurred in this page
56+
</NTip>
57+
<div text-6xl>
58+
{{ error.statusCode || 'Client Error' }}
59+
</div>
60+
<div v-if="error.statusMessage" op75>
61+
{{ error.statusMessage }}
62+
</div>
63+
<div my4 text-xl text-red>
64+
{{ error.message || error.description || 'Unknown error' }}
65+
</div>
66+
<div v-if="stacks.length || error.stack" of-auto rounded bg-active p2>
67+
<div px1 op50>
68+
Stacktrace
69+
</div>
70+
<StacktraceList v-if="stacks.length" px2 :stacktrace="stacks" />
71+
<pre v-else v-text="error.stack" />
72+
</div>
73+
</div>
74+
<div v-else op50>
75+
No error
76+
</div>
77+
</div>
78+
</template>

packages/devtools/src/runtime/plugins/devtools.client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ export default defineNuxtPlugin((nuxt: any) => {
5555
})
5656

5757
import('./view/client')
58-
.then(({ setupDevToolsClient }) => {
59-
setupDevToolsClient({
58+
.then(async ({ setupDevToolsClient }) => {
59+
await setupDevToolsClient({
6060
nuxt,
6161
clientHooks,
6262
timeMetric,

packages/devtools/src/runtime/plugins/view/Main.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ const draggingOffset = reactive({ x: 0, y: 0 })
5757
const mousePosition = reactive({ x: 0, y: 0 })
5858
5959
function onPointerDown(e: PointerEvent) {
60+
if (!panelEl.value)
61+
return
6062
isDragging.value = true
6163
const { left, top, width, height } = panelEl.value!.getBoundingClientRect()
6264
draggingOffset.x = e.clientX - left - width / 2

playgrounds/tab-timeline/components/GlobalNav.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
<NButton n="primary sm" to="/dynamic-bar" active-class="n-solid">
2424
/dynamic-bar
2525
</NButton>
26+
<NButton n="primary sm" to="/error" active-class="n-solid">
27+
/error
28+
</NButton>
2629
</div>
2730
</div>
2831
</template>

playgrounds/tab-timeline/nuxt.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default defineNuxtConfig({
44
'../../packages/devtools-ui-kit/src/module',
55
'../../local',
66
],
7-
ssr: false,
7+
ssr: true,
88
vite: {
99
clearScreen: false,
1010
},

playgrounds/tab-timeline/pages/empty.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<script setup lang="ts">
2-
32
</script>
43

54
<template>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script setup lang="ts">
2+
throw new Error('This is an error thrown from error.vue')
3+
</script>
4+
5+
<template>
6+
<div>
7+
Error
8+
</div>
9+
</template>

0 commit comments

Comments
 (0)