mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-20 20:39:30 +00:00
## Summary Add structured preload error logging with Sentry context enrichment and a user-facing toast notification when chunk loading fails (e.g. after a deploy with new hashed filenames). ## Changes - **`parsePreloadError` utility** (`src/utils/preloadErrorUtil.ts`): Extracts structured info from `vite:preloadError` events — URL, file type (JS/CSS/unknown), chunk name, and whether it looks like a hash mismatch. - **Sentry enrichment** (`src/App.vue`): Sets Sentry context and tags on preload errors so they are searchable/filterable in the Sentry dashboard. - **User-facing toast**: Shows an actionable "please refresh" message when a preload error occurs, across all distributions (cloud, desktop, localhost). - **Capture-phase resource error listener** (`src/App.vue`): Catches CSS/script load failures that bypass `vite:preloadError` and reports them to Sentry with the same structured context. - **Unit tests** (`src/utils/preloadErrorUtil.test.ts`): 9 tests covering URL parsing, chunk name extraction, hash mismatch detection, and edge cases. ## Files Changed | File | What | |------|------| | `src/App.vue` | Preload error handler + resource error listener | | `src/locales/en/main.json` | Toast message string | | `src/utils/preloadErrorUtil.ts` | `parsePreloadError()` utility | | `src/utils/preloadErrorUtil.test.ts` | Unit tests | ## Review Focus - Toast fires for all distributions (cloud/desktop/localhost) — intentional so all users see stale chunk errors - `parsePreloadError` is defensive — returns `unknown` for any field it cannot parse - Capture-phase listener filters to only `<script>` and `<link rel="stylesheet">` elements ## References - [Vite preload error handling](https://vite.dev/guide/build#load-error-handling) --------- Co-authored-by: bymyself <cbyrne@comfy.org>
93 lines
2.8 KiB
TypeScript
93 lines
2.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { parsePreloadError } from './preloadErrorUtil'
|
|
|
|
describe('parsePreloadError', () => {
|
|
it('parses CSS preload error', () => {
|
|
const error = new Error(
|
|
'Unable to preload CSS for /assets/vendor-vue-core-abc123.css'
|
|
)
|
|
const result = parsePreloadError(error)
|
|
|
|
expect(result.url).toBe('/assets/vendor-vue-core-abc123.css')
|
|
expect(result.fileType).toBe('css')
|
|
expect(result.chunkName).toBe('vendor-vue-core')
|
|
expect(result.message).toBe(error.message)
|
|
})
|
|
|
|
it('parses dynamically imported module error', () => {
|
|
const error = new Error(
|
|
'Failed to fetch dynamically imported module: https://example.com/assets/vendor-three-def456.js'
|
|
)
|
|
const result = parsePreloadError(error)
|
|
|
|
expect(result.url).toBe('https://example.com/assets/vendor-three-def456.js')
|
|
expect(result.fileType).toBe('js')
|
|
expect(result.chunkName).toBe('vendor-three')
|
|
})
|
|
|
|
it('extracts URL from generic error message', () => {
|
|
const error = new Error(
|
|
'Something went wrong loading https://cdn.example.com/assets/app-9f8e7d.js'
|
|
)
|
|
const result = parsePreloadError(error)
|
|
|
|
expect(result.url).toBe('https://cdn.example.com/assets/app-9f8e7d.js')
|
|
expect(result.fileType).toBe('js')
|
|
expect(result.chunkName).toBe('app')
|
|
})
|
|
|
|
it('returns null url when no URL found', () => {
|
|
const error = new Error('Something failed')
|
|
const result = parsePreloadError(error)
|
|
|
|
expect(result.url).toBeNull()
|
|
expect(result.fileType).toBe('unknown')
|
|
expect(result.chunkName).toBeNull()
|
|
})
|
|
|
|
it('detects font file types', () => {
|
|
const error = new Error(
|
|
'Unable to preload CSS for /assets/inter-abc123.woff2'
|
|
)
|
|
const result = parsePreloadError(error)
|
|
|
|
expect(result.fileType).toBe('font')
|
|
})
|
|
|
|
it('detects image file types', () => {
|
|
const error = new Error('Unable to preload CSS for /assets/logo-abc123.png')
|
|
const result = parsePreloadError(error)
|
|
|
|
expect(result.fileType).toBe('image')
|
|
})
|
|
|
|
it('handles mjs extension', () => {
|
|
const error = new Error(
|
|
'Failed to fetch dynamically imported module: /assets/chunk-abc123.mjs'
|
|
)
|
|
const result = parsePreloadError(error)
|
|
|
|
expect(result.fileType).toBe('js')
|
|
})
|
|
|
|
it('handles URLs with query parameters', () => {
|
|
const error = new Error(
|
|
'Unable to preload CSS for /assets/style-abc123.css?v=2'
|
|
)
|
|
const result = parsePreloadError(error)
|
|
|
|
expect(result.url).toBe('/assets/style-abc123.css?v=2')
|
|
expect(result.fileType).toBe('css')
|
|
})
|
|
|
|
it('extracts chunk name from filename without hash', () => {
|
|
const error = new Error(
|
|
'Failed to fetch dynamically imported module: /assets/index.js'
|
|
)
|
|
const result = parsePreloadError(error)
|
|
|
|
expect(result.chunkName).toBe('index')
|
|
})
|
|
})
|