import type { ErrorPayload } from 'types/hmrPayload'
const template = /*html*/ `
Click outside or fix the code to dismiss.
You can also disable this overlay by setting
server.hmr.overlay
to false
in vite.config.js.
`
const fileRE = /(?:[a-zA-Z]:\\|\/).*?:\d+:\d+/g
const codeframeRE = /^(?:>?\s+\d+\s+\|.*|\s+\|\s*\^.*)\r?\n/gm
// Allow `ErrorOverlay` to extend `HTMLElement` even in environments where
// `HTMLElement` was not originally defined.
const { HTMLElement = class {} as typeof globalThis.HTMLElement } = globalThis
export class ErrorOverlay extends HTMLElement {
root: ShadowRoot
constructor(err: ErrorPayload['err']) {
super()
this.root = this.attachShadow({ mode: 'open' })
this.root.innerHTML = template
codeframeRE.lastIndex = 0
const hasFrame = err.frame && codeframeRE.test(err.frame)
const message = hasFrame
? err.message.replace(codeframeRE, '')
: err.message
if (err.plugin) {
this.text('.plugin', `[plugin:${err.plugin}] `)
}
this.text('.message-body', message.trim())
const [file] = (err.loc?.file || err.id || 'unknown file').split(`?`)
if (err.loc) {
this.text('.file', `${file}:${err.loc.line}:${err.loc.column}`, true)
} else if (err.id) {
this.text('.file', file)
}
if (hasFrame) {
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 {
const el = this.root.querySelector(selector)!
if (!linkFiles) {
el.textContent = text
} else {
let curIndex = 0
let match: RegExpExecArray | null
while ((match = fileRE.exec(text))) {
const { 0: file, index } = match
if (index != null) {
const frag = text.slice(curIndex, index)
el.appendChild(document.createTextNode(frag))
const link = document.createElement('a')
link.textContent = file
link.className = 'file-link'
link.onclick = () => {
fetch('/__open-in-editor?file=' + encodeURIComponent(file))
}
el.appendChild(link)
curIndex += frag.length + file.length
}
}
}
}
close(): void {
this.parentNode?.removeChild(this)
}
}
export const overlayId = 'vite-error-overlay'
const { customElements } = globalThis // Ensure `customElements` is defined before the next line.
if (customElements && !customElements.get(overlayId)) {
customElements.define(overlayId, ErrorOverlay)
}