Compare commits

...

1 Commits

Author SHA1 Message Date
bymyself
e02776c793 fix: only show preload error toast for stale chunk errors
Add isStaleChunkError() filter that checks for hashed JS/CSS/MJS assets
under /assets/ before showing the toast. Non-asset URLs (e.g. /api/i18n)
and general network errors no longer trigger the toast. Logging and
Sentry reporting remain unconditional.
2026-03-14 22:56:04 -07:00
3 changed files with 97 additions and 8 deletions

View File

@@ -18,7 +18,7 @@ import { useToastStore } from '@/platform/updates/common/toastStore'
import { app } from '@/scripts/app'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { electronAPI } from '@/utils/envUtil'
import { parsePreloadError } from '@/utils/preloadErrorUtil'
import { isStaleChunkError, parsePreloadError } from '@/utils/preloadErrorUtil'
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
const { t } = useI18n()
@@ -96,12 +96,14 @@ onMounted(() => {
}
})
}
useToastStore().add({
severity: 'error',
summary: t('g.preloadErrorTitle'),
detail: t('g.preloadError'),
life: 10000
})
if (isStaleChunkError(info)) {
useToastStore().add({
severity: 'error',
summary: t('g.preloadErrorTitle'),
detail: t('g.preloadError'),
life: 10000
})
}
})
// Capture resource load failures (CSS, scripts) in non-localhost distributions

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { parsePreloadError } from './preloadErrorUtil'
import { isStaleChunkError, parsePreloadError } from './preloadErrorUtil'
describe('parsePreloadError', () => {
it('parses CSS preload error', () => {
@@ -90,3 +90,74 @@ describe('parsePreloadError', () => {
expect(result.chunkName).toBe('index')
})
})
describe('isStaleChunkError', () => {
it('returns true for hashed JS chunk under /assets/', () => {
const info = parsePreloadError(
new Error(
'Failed to fetch dynamically imported module: /assets/vendor-vue-core-abc123.js'
)
)
expect(isStaleChunkError(info)).toBe(true)
})
it('returns true for hashed CSS chunk under /assets/', () => {
const info = parsePreloadError(
new Error('Unable to preload CSS for /assets/style-9f8e7d.css')
)
expect(isStaleChunkError(info)).toBe(true)
})
it('returns true for hashed mjs chunk under /assets/', () => {
const info = parsePreloadError(
new Error(
'Failed to fetch dynamically imported module: /assets/chunk-abc123.mjs'
)
)
expect(isStaleChunkError(info)).toBe(true)
})
it('returns false for non-asset URLs like /api/i18n', () => {
const info = parsePreloadError(
new Error(
'Failed to fetch dynamically imported module: https://cloud.comfy.org/api/i18n'
)
)
expect(isStaleChunkError(info)).toBe(false)
})
it('returns false for unhashed asset files', () => {
const info = parsePreloadError(
new Error('Failed to fetch dynamically imported module: /assets/index.js')
)
expect(isStaleChunkError(info)).toBe(false)
})
it('returns false when no URL can be extracted', () => {
const info = parsePreloadError(new Error('Something failed'))
expect(isStaleChunkError(info)).toBe(false)
})
it('returns false for font files', () => {
const info = parsePreloadError(
new Error('Unable to preload CSS for /assets/inter-abc123.woff2')
)
expect(isStaleChunkError(info)).toBe(false)
})
it('returns false for image files', () => {
const info = parsePreloadError(
new Error('Unable to preload CSS for /assets/logo-abc123.png')
)
expect(isStaleChunkError(info)).toBe(false)
})
it('returns true for full URL with hashed asset path', () => {
const info = parsePreloadError(
new Error(
'Failed to fetch dynamically imported module: https://cloud.comfy.org/assets/vendor-three-def456.js'
)
)
expect(isStaleChunkError(info)).toBe(true)
})
})

View File

@@ -64,6 +64,22 @@ function extractChunkName(url: string): string | null {
return withoutHash || null
}
const HASHED_ASSET_RE = /\/assets\/.+-[a-f0-9]{6,}\.(js|mjs|css)$/
/**
* Determines if a preload error is a genuine stale chunk error — i.e. a hashed
* JS/CSS asset under /assets/ that 404'd, typically after a new deployment
* changed chunk hashes. Returns false for non-asset URLs (e.g. /api/i18n),
* unknown file types, and errors with no extractable URL.
*/
export function isStaleChunkError(info: PreloadErrorInfo): boolean {
if (!info.url) return false
if (info.fileType !== 'js' && info.fileType !== 'css') return false
const pathname = new URL(info.url, 'https://cloud.comfy.org').pathname
return HASHED_ASSET_RE.test(pathname)
}
export function parsePreloadError(error: Error): PreloadErrorInfo {
const message = error.message || String(error)
const url = extractUrl(message)