mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 11:59:00 +00:00
803 lines
21 KiB
TypeScript
803 lines
21 KiB
TypeScript
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 <JavaScriptCore/ErrorType.h> 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 }) => (
|
|
<div className={`BunError-ErrorTag BunError-ErrorTag--${ErrorTagType[type]}`}>
|
|
{ErrorTagType[type]}
|
|
</div>
|
|
);
|
|
|
|
const errorTags = [
|
|
<ErrorTag type={ErrorTagType.build}></ErrorTag>,
|
|
<ErrorTag type={ErrorTagType.resolve}></ErrorTag>,
|
|
<ErrorTag type={ErrorTagType.server}></ErrorTag>,
|
|
<ErrorTag type={ErrorTagType.client}></ErrorTag>,
|
|
<ErrorTag type={ErrorTagType.hmr}></ErrorTag>,
|
|
];
|
|
|
|
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] = (
|
|
<div
|
|
key={i}
|
|
className={`BunError-SourceLine-text ${
|
|
classes.empty ? "BunError-SourceLine-text--empty" : ""
|
|
} ${classes.highlight ? "BunError-SourceLine-text--highlight" : ""}`}
|
|
>
|
|
{classes.highlight ? (
|
|
<>
|
|
{_text.substring(0, highlightColumnStart - dedent)}
|
|
<span id="BunError-SourceLine-text-highlightExpression">
|
|
{_text.substring(
|
|
highlightColumnStart - dedent,
|
|
highlightColumnEnd - dedent
|
|
)}
|
|
</span>
|
|
{_text.substring(highlightColumnEnd - dedent)}
|
|
</>
|
|
) : (
|
|
_text
|
|
)}
|
|
</div>
|
|
);
|
|
numbers[i] = (
|
|
<div
|
|
key={line}
|
|
className={`BunError-SourceLine-number ${
|
|
classes.empty ? "BunError-SourceLine-number--empty" : ""
|
|
} ${classes.highlight ? "BunError-SourceLine-number--highlight" : ""}`}
|
|
>
|
|
{line}
|
|
</div>
|
|
);
|
|
|
|
if (classes.highlight && children) {
|
|
i++;
|
|
|
|
numbers.push(
|
|
...childrenArray.map((child, index) => (
|
|
<div
|
|
key={"highlight-number-" + index}
|
|
className={`BunError-SourceLine-number ${
|
|
classes.empty ? "BunError-SourceLine-number--empty" : ""
|
|
} ${
|
|
classes.highlight ? "BunError-SourceLine-number--highlight" : ""
|
|
}`}
|
|
></div>
|
|
))
|
|
);
|
|
lines.push(
|
|
...childrenArray.map((child, index) => (
|
|
<div
|
|
key={"highlight-line-" + index}
|
|
className={`BunError-SourceLine-text`}
|
|
>
|
|
{childrenArray[index]}
|
|
</div>
|
|
))
|
|
);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<IndentationContext.Provider value={dedent}>
|
|
<div className="BunError-SourceLines">
|
|
<div
|
|
className={`BunError-SourceLines-highlighter--${highlightI}`}
|
|
></div>
|
|
|
|
<div className="BunError-SourceLines-numbers">{numbers}</div>
|
|
<div className="BunError-SourceLines-lines">{lines}</div>
|
|
</div>
|
|
</IndentationContext.Provider>
|
|
);
|
|
};
|
|
|
|
const BuildErrorSourceLines = ({ location }: { location: Location }) => {
|
|
const { line, line_text, column, file } = location;
|
|
const sourceLines: SourceLine[] = [{ line, text: line_text }];
|
|
return (
|
|
<SourceLines
|
|
sourceLines={sourceLines}
|
|
highlight={line}
|
|
highlightColumnStart={column}
|
|
highlightColumnEnd={column}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const BuildErrorStackTrace = ({ location }: { location: Location }) => {
|
|
const { cwd } = useContext(ErrorGroupContext);
|
|
const filename = normalizedFilename(location.file, cwd);
|
|
const { line, column } = location;
|
|
return (
|
|
<div className={`BunError-NativeStackTrace`}>
|
|
<a
|
|
href={srcFileURL(filename, line, column)}
|
|
target="_blank"
|
|
className="BunError-NativeStackTrace-filename"
|
|
>
|
|
{filename}:{line}:{column}
|
|
</a>
|
|
<BuildErrorSourceLines location={location} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
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 (
|
|
<div
|
|
className={`BunError-StackFrame ${
|
|
fileName.endsWith(".bun") ? "BunError-StackFrame--muted" : ""
|
|
}`}
|
|
>
|
|
<div
|
|
title={StackFrameScope[scope]}
|
|
className="BunError-StackFrame-identifier"
|
|
>
|
|
<StackFrameIdentifier functionName={functionName} scope={scope} />
|
|
</div>
|
|
|
|
<a
|
|
target="_blank"
|
|
href={blobFileURL(fileName)}
|
|
className="BunError-StackFrame-link"
|
|
>
|
|
<div className="BunError-StackFrame-link-content">
|
|
<div className={`BunError-StackFrame-file`}>{fileName}</div>
|
|
{line > -1 && (
|
|
<div className="BunError-StackFrame-line">:{line + 1}</div>
|
|
)}
|
|
{column > -1 && (
|
|
<div className="BunError-StackFrame-column">:{column}</div>
|
|
)}
|
|
</div>
|
|
</a>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const NativeStackFrames = ({ frames }) => {
|
|
const items = new Array(frames.length);
|
|
for (let i = 0; i < frames.length; i++) {
|
|
items[i] = <NativeStackFrame key={i} frame={frames[i]} />;
|
|
}
|
|
|
|
return <div className="BunError-StackFrames">{items}</div>;
|
|
};
|
|
|
|
const NativeStackTrace = ({
|
|
frames,
|
|
sourceLines,
|
|
children,
|
|
}: {
|
|
frames: StackFrame[];
|
|
sourceLines: SourceLine[];
|
|
}) => {
|
|
const { file = "", position } = frames[0];
|
|
const { cwd } = useContext(ErrorGroupContext);
|
|
const filename = normalizedFilename(file, cwd);
|
|
return (
|
|
<div className={`BunError-NativeStackTrace`}>
|
|
<a
|
|
href={blobFileURL(filename)}
|
|
target="_blank"
|
|
className="BunError-NativeStackTrace-filename"
|
|
>
|
|
{filename}:{position.line + 1}:{position.column_start}
|
|
</a>
|
|
{sourceLines.length > 0 && (
|
|
<SourceLines
|
|
highlight={position.line}
|
|
sourceLines={sourceLines}
|
|
highlightColumnStart={position.column_start}
|
|
highlightColumnEnd={position.column_stop}
|
|
>
|
|
{children}
|
|
</SourceLines>
|
|
)}
|
|
{frames.length > 0 && <NativeStackFrames frames={frames} />}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const divet = <span className="BunError-divet">^</span>;
|
|
const DivetRange = ({ start, stop }) => {
|
|
const length = Math.max(stop - start, 0);
|
|
if (length === 0) return null;
|
|
return (
|
|
<span
|
|
className="BunError-DivetRange"
|
|
style={{ width: `${length - 1}ch` }}
|
|
></span>
|
|
);
|
|
};
|
|
|
|
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 (
|
|
<div
|
|
className={`BunError-JSException BunError-JSException--TypeError`}
|
|
>
|
|
<div className="BunError-error-header">
|
|
<div className={`BunError-error-code`}>TypeError</div>
|
|
{errorTags[ErrorTagType.server]}
|
|
</div>
|
|
|
|
<div className={`BunError-error-message`}>
|
|
{fancyTypeError.message}
|
|
</div>
|
|
|
|
{fancyTypeError.runtimeTypeName.length && (
|
|
<div className={`BunError-error-subtitle`}>
|
|
It's{" "}
|
|
<span className="BunError-error-typename">
|
|
{fancyTypeError.runtimeTypeName}
|
|
</span>
|
|
.
|
|
</div>
|
|
)}
|
|
|
|
{value.stack && (
|
|
<NativeStackTrace
|
|
frames={value.stack.frames}
|
|
sourceLines={value.stack.source_lines}
|
|
>
|
|
<Indent by={value.stack.frames[0].position.column_start}>
|
|
<span className="BunError-error-typename">
|
|
{fancyTypeError.runtimeTypeName}
|
|
</span>
|
|
</Indent>
|
|
</NativeStackTrace>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
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 (
|
|
<div className={`BunError-JSException`}>
|
|
<div className="BunError-error-header">
|
|
<div className={`BunError-error-code`}>{value.name}</div>
|
|
{errorTags[ErrorTagType.server]}
|
|
</div>
|
|
|
|
<div className={`BunError-error-message`}>{message}</div>
|
|
<div className={`BunError-error-subtitle`}>{subtitle}</div>
|
|
|
|
{value.stack && (
|
|
<NativeStackTrace
|
|
frames={value.stack.frames}
|
|
sourceLines={value.stack.source_lines}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className={`BunError-JSException`}>
|
|
<div className="BunError-error-header">
|
|
<div className={`BunError-error-code`}>{value.name}</div>
|
|
{errorTags[ErrorTagType.server]}
|
|
</div>
|
|
|
|
<div className={`BunError-error-message`}>{value.message}</div>
|
|
|
|
{value.stack && (
|
|
<NativeStackTrace
|
|
frames={value.stack.frames}
|
|
sourceLines={value.stack.source_lines}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
const Summary = ({
|
|
errorCount,
|
|
onClose,
|
|
}: {
|
|
errorCount: number;
|
|
onClose: Function;
|
|
}) => {
|
|
return (
|
|
<div className="BunError-Summary">
|
|
<div className="BunError-Summary-ErrorIcon"></div>
|
|
<div className="BunError-Summary-Title">
|
|
{errorCount} error{errorCount > 1 ? "s" : ""} on this page
|
|
</div>
|
|
|
|
<div onClick={onClose} className="BunError-Summary-CloseButton">
|
|
<div className="BunError-Summary-CloseIcon"></div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
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 (
|
|
<div className={`BunError-BuildError BunError-BuildError--build`}>
|
|
<div className="BunError-error-header">
|
|
<div className={`BunError-error-code`}>BuildError</div>
|
|
</div>
|
|
|
|
<div className={`BunError-error-message`}>{title}</div>
|
|
|
|
{subtitle.length > 0 && (
|
|
<div className={`BunError-error-subtitle`}>{subtitle}</div>
|
|
)}
|
|
|
|
{message.data.location && (
|
|
<BuildErrorStackTrace location={message.data.location} />
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
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 (
|
|
<div className={`BunError-BuildError BunError-BuildError--resolve`}>
|
|
<div className="BunError-error-header">
|
|
<div className={`BunError-error-code`}>ResolveError</div>
|
|
</div>
|
|
|
|
<div className={`BunError-error-message`}>
|
|
Can't import{" "}
|
|
<span className="BunError-error-message--mono">
|
|
{message.on.resolve}
|
|
</span>
|
|
</div>
|
|
|
|
{subtitle && <div className={`BunError-error-subtitle`}>{subtitle}</div>}
|
|
|
|
{message.data.location && (
|
|
<BuildErrorStackTrace location={message.data.location} />
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
const OverlayMessageContainer = ({
|
|
problems,
|
|
reason,
|
|
router,
|
|
}: FallbackMessageContainer) => {
|
|
return (
|
|
<div id="BunErrorOverlay-container">
|
|
<div className="BunError-content">
|
|
<div className="BunError-header">
|
|
<Summary
|
|
errorCount={problems.exceptions.length + problems.build.errors}
|
|
onClose={onClose}
|
|
problems={problems}
|
|
reason={reason}
|
|
/>
|
|
</div>
|
|
<div className={`BunError-list`}>
|
|
{problems.exceptions.map((problem, index) => (
|
|
<JSException key={index} value={problem} />
|
|
))}
|
|
{problems.build.msgs.map((buildMessage, index) => {
|
|
if (buildMessage.on.build) {
|
|
return <BuildError key={index} message={buildMessage} />;
|
|
} else if (buildMessage.on.resolve) {
|
|
return <ResolveError key={index} message={buildMessage} />;
|
|
} else {
|
|
throw new Error("Unknown build message type");
|
|
}
|
|
})}
|
|
</div>
|
|
<div className="BunError-footer">
|
|
<div id="BunError-poweredBy"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const BuildFailureMessageContainer = ({
|
|
messages,
|
|
}: {
|
|
messages: Message[];
|
|
}) => {
|
|
return (
|
|
<div id="BunErrorOverlay-container">
|
|
<div className="BunError-content">
|
|
<div className="BunError-header">
|
|
<Summary onClose={onClose} errorCount={messages.length} />
|
|
</div>
|
|
<div className={`BunError-list`}>
|
|
{messages.map((buildMessage, index) => {
|
|
if (buildMessage.on.build) {
|
|
return <BuildError key={index} message={buildMessage} />;
|
|
} else if (buildMessage.on.resolve) {
|
|
return <ResolveError key={index} message={buildMessage} />;
|
|
} else {
|
|
throw new Error("Unknown build message type");
|
|
}
|
|
})}
|
|
</div>
|
|
<div className="BunError-footer">
|
|
<div id="BunError-poweredBy"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
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(() => (
|
|
<ErrorGroupContext.Provider value={fallback}>
|
|
<OverlayMessageContainer {...fallback} />
|
|
</ErrorGroupContext.Provider>
|
|
));
|
|
}
|
|
|
|
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(() => (
|
|
<ErrorGroupContext.Provider value={{ cwd }}>
|
|
<BuildFailureMessageContainer messages={failure.log.msgs} />
|
|
</ErrorGroupContext.Provider>
|
|
));
|
|
};
|
|
|
|
export const clearBuildFailure = dismissError;
|
|
globalThis.__BunClearBuildFailure = dismissError;
|