diff --git a/packages/playground/ssr-vue/__tests__/ssr-vue.spec.ts b/packages/playground/ssr-vue/__tests__/ssr-vue.spec.ts index a762909db538f5..8dad7016e4bb35 100644 --- a/packages/playground/ssr-vue/__tests__/ssr-vue.spec.ts +++ b/packages/playground/ssr-vue/__tests__/ssr-vue.spec.ts @@ -158,3 +158,12 @@ test('import.meta.url', async () => { await page.goto(url) expect(await page.textContent('.protocol')).toEqual('file:') }) + +if (!isBuild) { + test('error overlay', async () => { + await page.goto(url + '/error') + expect(await page.textContent('vite-error-overlay .message-body')).toMatch( + 'This should render vite-error-overlay' + ) + }) +} diff --git a/packages/playground/ssr-vue/server.js b/packages/playground/ssr-vue/server.js index 642f274647294f..b24b9e3f3a0e22 100644 --- a/packages/playground/ssr-vue/server.js +++ b/packages/playground/ssr-vue/server.js @@ -52,10 +52,11 @@ async function createServer( } app.use('*', async (req, res) => { + let template, render + try { const url = req.originalUrl - let template, render if (!isProd) { // always read fresh template in dev template = fs.readFileSync(resolve('index.html'), 'utf-8') @@ -76,7 +77,20 @@ async function createServer( } catch (e) { vite && vite.ssrFixStacktrace(e) console.log(e.stack) - res.status(500).end(e.stack) + + if (template && !isProd) { + res + .status(500) + .set({ 'Content-Type': 'text/html' }) + .end( + template.replace( + ``, + `\n` + ) + ) + } else { + res.status(500).end(e.stack) + } } }) diff --git a/packages/playground/ssr-vue/src/pages/Error.vue b/packages/playground/ssr-vue/src/pages/Error.vue new file mode 100644 index 00000000000000..cd0e741359c43a --- /dev/null +++ b/packages/playground/ssr-vue/src/pages/Error.vue @@ -0,0 +1,11 @@ + + + diff --git a/packages/vite/src/client/overlay.ts b/packages/vite/src/client/overlay.ts index 150c570fbc8aaf..0faeda7122f4b7 100644 --- a/packages/vite/src/client/overlay.ts +++ b/packages/vite/src/client/overlay.ts @@ -123,6 +123,37 @@ export class ErrorOverlay extends HTMLElement { this.root = this.attachShadow({ mode: 'open' }) this.root.innerHTML = template + if (!err) { + // In SSR, the overlay element can be directly injected in + // the HTML response with the error information as attributes. + err = { + id: this.getAttribute('id') || undefined, + message: this.getAttribute('message') || '', + stack: this.getAttribute('stack') || '', + plugin: this.getAttribute('plugin') || undefined, + pluginCode: this.getAttribute('plugin-code') || undefined, + frame: this.getAttribute('frame') || undefined, + loc: this.hasAttribute('loc-line') + ? { + file: this.getAttribute('loc-file') || undefined, + line: Number(this.getAttribute('loc-line')), + column: Number(this.getAttribute('loc-column')) + } + : undefined + } + } + + this.showError(err) + + this.root.querySelector('.window')!.addEventListener('click', (e) => { + e.stopPropagation() + }) + this.addEventListener('click', () => { + this.close() + }) + } + + showError(err: ErrorPayload['err']) { codeframeRE.lastIndex = 0 const hasFrame = err.frame && codeframeRE.test(err.frame) const message = hasFrame @@ -144,13 +175,6 @@ export class ErrorOverlay extends HTMLElement { this.text('.frame', err.frame!.trim()) } this.text('.stack', err.stack, true) - - this.root.querySelector('.window')!.addEventListener('click', (e) => { - e.stopPropagation() - }) - this.addEventListener('click', () => { - this.close() - }) } text(selector: string, text: string, linkFiles = false): void {