Skip to content

Commit 01e434d

Browse files
committed
fix(katex): improve error handling for worker initialization and fallback rendering
1 parent 5a955ac commit 01e434d

File tree

6 files changed

+160
-181
lines changed

6 files changed

+160
-181
lines changed

src/components/MathBlockNode/MathBlockNode.vue

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,33 +44,44 @@ function renderMath() {
4444
mathBlockElement.value.innerHTML = html
4545
hasRenderedOnce = true
4646
})
47-
.catch(() => {
47+
.catch((err: any) => {
4848
// ignore if a newer render was requested or component unmounted
4949
if (isUnmounted || renderId !== currentRenderId)
5050
return
5151
if (!mathBlockElement.value)
5252
return
53-
// Try a synchronous KaTeX render on the main thread as a fallback
54-
try {
55-
const html = katex.renderToString(props.node.content, {
56-
throwOnError: true,
57-
displayMode: true,
58-
})
59-
mathBlockElement.value.innerHTML = html
60-
hasRenderedOnce = true
61-
// populate worker client cache so future calls hit cache
53+
54+
// If the worker failed to initialize (e.g. bad new Worker path), the
55+
// worker client will return a special error with code 'WORKER_INIT_ERROR'
56+
// and `fallbackToRenderer = true`. In that case, perform a synchronous
57+
// KaTeX render on the main thread as a fallback. If the error is a
58+
// KaTeX render error from the worker (syntax), we should ignore it here
59+
// and fall through to the raw/text fallback below.
60+
if (err?.code === 'WORKER_INIT_ERROR' || err?.fallbackToRenderer) {
6261
try {
63-
setKaTeXCache(props.node.content, true, html)
62+
const html = katex.renderToString(props.node.content, {
63+
throwOnError: true,
64+
displayMode: true,
65+
})
66+
mathBlockElement.value.innerHTML = html
67+
hasRenderedOnce = true
68+
// populate worker client cache so future calls hit cache
69+
try {
70+
setKaTeXCache(props.node.content, true, html)
71+
}
72+
catch {
73+
// ignore cache set errors
74+
}
75+
return
6476
}
6577
catch {
66-
// ignore cache set errors
78+
// if synchronous render fails, fall through to raw/text fallback
6779
}
6880
}
69-
catch {
70-
// show raw fallback when we never successfully rendered before or when loading flag is false
71-
if (!hasRenderedOnce || !props.node.loading) {
72-
mathBlockElement.value.textContent = props.node.raw
73-
}
81+
82+
// show raw fallback when we never successfully rendered before or when loading flag is false
83+
if (!hasRenderedOnce || !props.node.loading) {
84+
mathBlockElement.value.textContent = props.node.raw
7485
}
7586
})
7687
}

src/components/MathInlineNode/MathInlineNode.vue

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import katex from 'katex'
23
import { computed, onBeforeUnmount, onMounted, ref, useAttrs, watch } from 'vue'
34
import { renderKaTeXInWorker } from '../../workers/katexWorkerClient'
45
@@ -55,11 +56,28 @@ function renderMath() {
5556
mathElement.value.innerHTML = html
5657
hasRenderedOnce = true
5758
})
58-
.catch(() => {
59+
.catch((err: any) => {
5960
if (isUnmounted || renderId !== currentRenderId)
6061
return
6162
if (!mathElement.value)
6263
return
64+
65+
// Only attempt synchronous KaTeX fallback when the worker failed to initialize.
66+
// If the worker returned a render error (syntax), leave the loading state as-is
67+
// and don't try to synchronously render here to avoid surfacing KaTeX errors.
68+
if (err?.code === 'WORKER_INIT_ERROR' || err?.fallbackToRenderer) {
69+
try {
70+
const html = katex.renderToString(props.node.content, { throwOnError: true, displayMode: false })
71+
renderingLoading.value = false
72+
mathElement.value.innerHTML = html
73+
hasRenderedOnce = true
74+
return
75+
}
76+
catch {
77+
// fall through to existing loading/raw behaviour
78+
}
79+
}
80+
6381
if (!hasRenderedOnce || !props.node.loading) {
6482
renderingLoading.value = true
6583
// mathElement.value.textContent = props.node.raw
@@ -112,12 +130,15 @@ const attrs = useAttrs()
112130
</script>
113131

114132
<template>
115-
<!-- <span v-if="renderingLoading"
116-
:style="cssVars"
117-
>Loading...</span> -->
118133
<span v-show="renderingLoading" class="math-loading inline-flex items-baseline gap-1.5" :aria-hidden="!node.loading ? 'true' : 'false'" v-bind="attrs" :style="cssVars">
119134
<span class="math-text-wrapper relative inline-flex">
120-
<span class="leading-[normal] math-text">Loading...</span>
135+
<span class="leading-[normal] math-text">
136+
<!-- Allow consumers to override the loading content via a named slot `loading`,
137+
or via the default slot. Fallback to the original text when no slot is provided. -->
138+
<slot name="loading">
139+
<slot>Loading...</slot>
140+
</slot>
141+
</span>
121142
<span class="underline-anim" aria-hidden="true" />
122143
</span>
123144
</span>

src/components/MermaidBlockNode/MermaidBlockNode.vue

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -988,10 +988,7 @@ function cleanupAfterLoadingSettled() {
988988
previewPollController = null
989989
}
990990
// terminate parser worker to free resources; it will be recreated on demand
991-
try {
992-
terminateMermaidWorker()
993-
}
994-
catch {}
991+
terminateMermaidWorker()
995992
}
996993
997994
function scheduleNextPreviewPoll(delay = 800) {
@@ -1233,10 +1230,7 @@ onUnmounted(() => {
12331230
currentWorkController.abort()
12341231
currentWorkController = null
12351232
}
1236-
try {
1237-
terminateMermaidWorker()
1238-
}
1239-
catch {}
1233+
terminateMermaidWorker()
12401234
stopPreviewPolling()
12411235
})
12421236

src/utils/markdown.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -208,23 +208,29 @@ export async function renderMarkdownAsync(md: MarkdownIt, content: string) {
208208
try {
209209
return await renderKaTeXInWorker(latex, false, 800)
210210
}
211-
catch {
212-
try {
213-
const data = katex.renderToString(latex, {
214-
throwOnError: true,
215-
displayMode: false,
216-
})
211+
catch (err: any) {
212+
// Only fallback to synchronous main-thread KaTeX render if the worker
213+
// couldn't be instantiated (init error). If the worker reported a
214+
// render/syntax error, we should not treat it as an init failure.
215+
if (err?.code === 'WORKER_INIT_ERROR' || err?.fallbackToRenderer) {
217216
try {
218-
setKaTeXCache(latex, false, data)
217+
const data = katex.renderToString(latex, {
218+
throwOnError: true,
219+
displayMode: false,
220+
})
221+
try {
222+
setKaTeXCache(latex, false, data)
223+
}
224+
catch {
225+
// ignore cache set errors
226+
}
227+
return data
219228
}
220229
catch {
221-
// ignore cache set errors
230+
return null
222231
}
223-
return data
224-
}
225-
catch {
226-
return null
227232
}
233+
return null
228234
}
229235
})()
230236
})

0 commit comments

Comments
 (0)