import React from "react"; import { useContext, useState, useCallback, createContext } from "react"; import { render, unmountComponentAtNode } from "react-dom"; import type { FallbackMessageContainer, JSException, JSException as JSExceptionType, Location, Message, SourceLine, StackFrame, WebsocketMessageBuildFailure, } from "../../src/api/schema"; enum StackFrameScope { Eval = 1, Module = 2, Function = 3, Global = 4, Wasm = 5, Constructor = 6, } enum JSErrorCode { Error = 0, EvalError = 1, RangeError = 2, ReferenceError = 3, SyntaxError = 4, TypeError = 5, URIError = 6, AggregateError = 7, // StackOverflow & OutOfMemoryError is not an ErrorType in within JSC, so the number here is just totally made up OutOfMemoryError = 8, BundlerError = 252, StackOverflow = 253, UserErrorCode = 254, } const JSErrorCodeLabel = { 0: "Error", 1: "EvalError", 2: "RangeError", 3: "ReferenceError", 4: "SyntaxError", 5: "TypeError", 6: "URIError", 7: "AggregateError", 253: "StackOverflow", 8: "OutOfMemory", }; const BUN_ERROR_CONTAINER_ID = "__bun-error__"; enum RuntimeType { Nothing = 0x0, Function = 0x1, Undefined = 0x2, Null = 0x4, Boolean = 0x8, AnyInt = 0x10, Number = 0x20, String = 0x40, Object = 0x80, Symbol = 0x100, BigInt = 0x200, } enum ErrorTagType { build, resolve, server, client, hmr, } const ErrorTag = ({ type }: { type: ErrorTagType }) => (
{ErrorTagType[type]}
); const errorTags = [ , , , , , ]; const normalizedFilename = (filename: string, cwd: string): string => { if (filename.startsWith(cwd)) { return filename.substring(cwd.length); } return filename; }; const blobFileURL = (filename: string): string => { return new URL("/blob:" + filename, location.href).href; }; const srcFileURL = (filename: string, line: number, column: number): string => { if (filename.endsWith(".bun")) { return new URL("/" + filename, location.href).href; } var base = `/src:${filename}`; if (line > -1) { base = base + `:${line}`; if (column > -1) { base = base + `:${column}`; } } return new URL(base, location.href).href; }; class FancyTypeError { constructor(exception: JSException) { this.runtimeType = exception.runtime_type || 0; this.runtimeTypeName = RuntimeType[this.runtimeType] || "undefined"; this.message = exception.message; this.explain = ""; this.normalize(exception); } runtimeType: RuntimeType; explain: string; runtimeTypeName: string; message: string; normalize(exception: JSException) { let i = exception.message.lastIndexOf(" is "); if (i === -1) return; const partial = exception.message.substring(i + " is ".length); const nextWord = /(["a-zA-Z0-9_\.]+)\)$/.exec(partial); if (nextWord && nextWord[0]) { this.runtimeTypeName = nextWord[0]; this.runtimeTypeName = this.runtimeTypeName.substring( 0, this.runtimeTypeName.length - 1 ); switch (this.runtimeTypeName.toLowerCase()) { case "undefined": { this.runtimeType = RuntimeType.Undefined; break; } case "null": { this.runtimeType = RuntimeType.Null; break; } case "string": { this.runtimeType = RuntimeType.String; break; } case "true": case "false": { this.runtimeType = RuntimeType.Boolean; break; } case "number": this.runtimeType = RuntimeType.Number; break; case "bigint": this.runtimeType = RuntimeType.BigInt; break; case "symbol": this.runtimeType = RuntimeType.Symbol; break; default: { this.runtimeType = RuntimeType.Object; break; } } this.message = exception.message.substring(0, i); this.message = this.message.substring( 0, this.message.lastIndexOf("(In ") ); } } } var onClose = dismissError; const IndentationContext = createContext(0); const SourceLines = ({ sourceLines, highlight = -1, highlightColumnStart = 0, highlightColumnEnd = Infinity, children, }: { sourceLines: SourceLine[]; highlightColumnStart: number; highlightColumnEnd: number; highlight: number; }) => { let start = sourceLines.length; let end = 0; let dedent = Infinity; let originalLines = new Array(sourceLines.length); let _i = 0; for (let i = 0; i < sourceLines.length; i++) { // bun only prints \n, no \r\n, so this should work fine sourceLines[i].text = sourceLines[i].text.replaceAll("\n", ""); // This will now only trim spaces (and vertical tab character which never prints) const left = sourceLines[i].text.trimLeft(); if (left.length > 0) { start = Math.min(start, i); end = Math.max(end, i + 1); dedent = Math.min(sourceLines[i].text.length - left.length, dedent); } } const _sourceLines = sourceLines.slice(start, end); const childrenArray = children || []; const numbers = new Array(_sourceLines.length + childrenArray.length); const lines = new Array(_sourceLines.length + childrenArray.length); let highlightI = 0; for (let i = 0; i < _sourceLines.length; i++) { const { line, text } = _sourceLines[i]; const classes = { empty: text.trim().length === 0, highlight: highlight + 1 === line || _sourceLines.length === 1, }; if (classes.highlight) highlightI = i; const _text = classes.empty ? "" : text.substring(dedent); lines[i] = (
{classes.highlight ? ( <> {_text.substring(0, highlightColumnStart - dedent)} {_text.substring( highlightColumnStart - dedent, highlightColumnEnd - dedent )} {_text.substring(highlightColumnEnd - dedent)} ) : ( _text )}
); numbers[i] = (
{line}
); if (classes.highlight && children) { i++; numbers.push( ...childrenArray.map((child, index) => (
)) ); lines.push( ...childrenArray.map((child, index) => (
{childrenArray[index]}
)) ); } } return (
{numbers}
{lines}
); }; const BuildErrorSourceLines = ({ location }: { location: Location }) => { const { line, line_text, column, file } = location; const sourceLines: SourceLine[] = [{ line, text: line_text }]; return ( ); }; const BuildErrorStackTrace = ({ location }: { location: Location }) => { const { cwd } = useContext(ErrorGroupContext); const filename = normalizedFilename(location.file, cwd); const { line, column } = location; return (
{filename}:{line}:{column}
); }; const StackFrameIdentifier = ({ functionName, scope, }: { functionName?: string; scope: StackFrameScope; }) => { switch (scope) { case StackFrameScope.Constructor: { return functionName ? `new ${functionName}` : "new (anonymous)"; break; } case StackFrameScope.Eval: { return "eval"; break; } case StackFrameScope.Module: { return "(esm)"; break; } case StackFrameScope.Global: { return "(global)"; break; } case StackFrameScope.Wasm: { return "(wasm)"; break; } default: { return functionName ? functionName : "λ()"; break; } } }; const NativeStackFrame = ({ frame, isTop, }: { frame: StackFrame; isTop: boolean; }) => { const { cwd } = useContext(ErrorGroupContext); const { file, function_name: functionName, position: { line, column_start: column }, scope, } = frame; const fileName = normalizedFilename(file, cwd); return (
{fileName}
{line > -1 && (
:{line + 1}
)} {column > -1 && (
:{column}
)}
); }; const NativeStackFrames = ({ frames }) => { const items = new Array(frames.length); for (let i = 0; i < frames.length; i++) { items[i] = ; } return
{items}
; }; const NativeStackTrace = ({ frames, sourceLines, children, }: { frames: StackFrame[]; sourceLines: SourceLine[]; }) => { const { file = "", position } = frames[0]; const { cwd } = useContext(ErrorGroupContext); const filename = normalizedFilename(file, cwd); return (
{filename}:{position.line + 1}:{position.column_start} {sourceLines.length > 0 && ( {children} )} {frames.length > 0 && }
); }; const divet = ^; const DivetRange = ({ start, stop }) => { const length = Math.max(stop - start, 0); if (length === 0) return null; return ( ); }; const Indent = ({ by, children }) => { const amount = useContext(IndentationContext); return ( <> {` `.repeat(by - amount)} {children} ); }; const JSException = ({ value }: { value: JSExceptionType }) => { switch (value.code) { case JSErrorCode.TypeError: { const fancyTypeError = new FancyTypeError(value); if (fancyTypeError.runtimeType !== RuntimeType.Nothing) { return (
TypeError
{errorTags[ErrorTagType.server]}
{fancyTypeError.message}
{fancyTypeError.runtimeTypeName.length && (
It's{" "} {fancyTypeError.runtimeTypeName} .
)} {value.stack && ( {fancyTypeError.runtimeTypeName} )}
); } } default: { const newline = value.message.indexOf("\n"); if (newline > -1) { const subtitle = value.message.substring(newline + 1).trim(); const message = value.message.substring(0, newline).trim(); if (subtitle.length) { return (
{value.name}
{errorTags[ErrorTagType.server]}
{message}
{subtitle}
{value.stack && ( )}
); } } return (
{value.name}
{errorTags[ErrorTagType.server]}
{value.message}
{value.stack && ( )}
); } } }; const Summary = ({ errorCount, onClose, }: { errorCount: number; onClose: Function; }) => { return (
{errorCount} error{errorCount > 1 ? "s" : ""} on this page
); }; const BuildError = ({ message }: { message: Message }) => { let title = (message.data.text || "").trim(); const newline = title.indexOf("\n"); let subtitle = ""; if (newline > -1) { subtitle = title.slice(newline + 1).trim(); title = title.slice(0, newline); } return (
BuildError
{title}
{subtitle.length > 0 && (
{subtitle}
)} {message.data.location && ( )}
); }; const ResolveError = ({ message }: { message: Message }) => { const { cwd } = useContext(ErrorGroupContext); let title = (message.data.text || "").trim(); const newline = title.indexOf("\n"); let subtitle = null; if (newline > -1) { subtitle = title.slice(newline + 1).trim(); title = title.slice(0, newline); } return (
ResolveError
Can't import{" "} {message.on.resolve}
{subtitle &&
{subtitle}
} {message.data.location && ( )}
); }; const OverlayMessageContainer = ({ problems, reason, router, }: FallbackMessageContainer) => { return (
{problems.exceptions.map((problem, index) => ( ))} {problems.build.msgs.map((buildMessage, index) => { if (buildMessage.on.build) { return ; } else if (buildMessage.on.resolve) { return ; } else { throw new Error("Unknown build message type"); } })}
); }; const BuildFailureMessageContainer = ({ messages, }: { messages: Message[]; }) => { return (
{messages.map((buildMessage, index) => { if (buildMessage.on.build) { return ; } else if (buildMessage.on.resolve) { return ; } else { throw new Error("Unknown build message type"); } })}
); }; const ErrorGroupContext = createContext<{ cwd: string }>(null); var reactRoot; function renderWithFunc(func) { if (!reactRoot) { const root = document.createElement("div"); root.id = "__bun__error-root"; reactRoot = document.createElement("div"); reactRoot.id = BUN_ERROR_CONTAINER_ID; reactRoot.style.visibility = "hidden"; const link = document.createElement("link"); link.rel = "stylesheet"; link.href = new URL("/bun:erro.css", document.baseURI).href; link.onload = () => { reactRoot.style.visibility = "visible"; }; const shadowRoot = root.attachShadow({ mode: "closed" }); shadowRoot.appendChild(link); shadowRoot.appendChild(reactRoot); document.body.appendChild(root); render(func(), reactRoot); } else { render(func(), reactRoot); } } export function renderFallbackError(fallback: FallbackMessageContainer) { // Not an error if (fallback?.problems?.name === "JSDisabled") return; return renderWithFunc(() => ( )); } export function dismissError() { if (reactRoot) { unmountComponentAtNode(reactRoot); const root = document.getElementById("__bun__error-root"); if (root) root.remove(); reactRoot = null; } } export const renderBuildFailure = ( failure: WebsocketMessageBuildFailure, cwd: string ) => { renderWithFunc(() => ( )); }; export const clearBuildFailure = dismissError; globalThis.__BunClearBuildFailure = dismissError;