diff options
Diffstat (limited to 'packages/bun-framework-next')
-rw-r--r-- | packages/bun-framework-next/bun-error.css | 289 | ||||
-rw-r--r-- | packages/bun-framework-next/bun-error.tsx | 809 | ||||
-rw-r--r-- | packages/bun-framework-next/bun-error/close.png | bin | 0 -> 757 bytes | |||
-rw-r--r-- | packages/bun-framework-next/bun-error/error.png | bin | 0 -> 717 bytes | |||
-rw-r--r-- | packages/bun-framework-next/bun-error/powered-by.png | bin | 0 -> 2863 bytes | |||
-rw-r--r-- | packages/bun-framework-next/bun-error/powered-by.webp | bin | 0 -> 1316 bytes | |||
-rw-r--r-- | packages/bun-framework-next/bun-runtime-error.ts | 163 | ||||
-rw-r--r-- | packages/bun-framework-next/client.development.tsx | 5 | ||||
-rw-r--r-- | packages/bun-framework-next/fallback.development.tsx | 62 | ||||
-rw-r--r-- | packages/bun-framework-next/next-image-polyfill.tsx | 36 | ||||
-rw-r--r-- | packages/bun-framework-next/package.json | 3 | ||||
-rw-r--r-- | packages/bun-framework-next/page-loader.ts | 5 | ||||
-rw-r--r-- | packages/bun-framework-next/polyfills.tsx | 23 | ||||
-rw-r--r-- | packages/bun-framework-next/renderDocument.tsx | 19 | ||||
-rw-r--r-- | packages/bun-framework-next/server.development.tsx | 38 |
15 files changed, 1404 insertions, 48 deletions
diff --git a/packages/bun-framework-next/bun-error.css b/packages/bun-framework-next/bun-error.css new file mode 100644 index 000000000..c5ed9881b --- /dev/null +++ b/packages/bun-framework-next/bun-error.css @@ -0,0 +1,289 @@ +:host { + --bun-error-color: #e33737; + --bun-error-monospace: ui-monospace, Menlo, Monaco, "Cascadia Mono", + "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", + "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace; +} + +a { + color: inherit; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} +#BunErrorOverlay-container { + box-shadow: 0px 16px 24px rgba(0, 0, 0, 0.06), 0px 2px 6px rgba(0, 0, 0, 0.1), + 0px 0px 1px rgba(0, 0, 0, 0.04); + backdrop-filter: blur(42px); + backface-visibility: visible; + border: inset 1px solid rgba(0, 0, 0, 0.2); + border-radius: 17px; + background-color: rgba(255, 255, 255, 0.92); + width: 480px; + + position: fixed; + top: 120px; + right: 48px; + z-index: 999999; + + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, + Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; +} + +#BunErrorOverlay-container a { + color: inherit; +} + +.BunError-Summary-ErrorIcon { + content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAgCAYAAAB3j6rJAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJiSURBVHgBzZdLUttAEIa7RwpVeKUD4Ip8hFSB7V3kG8AJgBPgG+CcAG4QcwPfIFpiigU5gRWcqmy1AVwVaTo9Y+yyiEeakc3jX7j0GEmfe7r/6QH4IELYUJNuN/LyPMwRU2g04lYcp1BDtUEYIPSJvvNhtHI5QcTB3vX1FThKQE0xxI8XEEohEQ2n+/sROKoWyP3BwYn6qHGA553DW4CgEFUfin6328fwmiDTbldBhFXjiHNlEkUBWMoJRCUoSNm3HB6Kx0fbsW4gnpQDQLT/l4hntlGxBlHR4NJ0mndW4D08WCWuNYgv5YXhVsJmNASi0bqbnNj9P2pKtwGiyxXxcM2tJNvd/bI3Hp82b26OJOK3dc9nc+PbHMRUrhyJgqV7QowMr4iqTK4SpNK8VvRXSnNiVphcKYhO0BLzIkvAZ5WaXCmID6AeDGFLYpO7NJWzEUSbF9EAHKTagYohgcnkjCDavKoVgqNMJrcWpKZ5AQlh46LBp6enCysQ36LuFy8tnBFZ2Tkn+cnLcv6vQ1PlypViC6I+PsiEUB3ZomMLLZ+Mm+Nxzwgy7XQmsMVKKVWe95q3t7E6LEyNi3ktRZSWrTWl8rxl5JcgVeZlUJI1Gq3FWsM+cQpuCn91Ov0CiEd0Bo7RkERXq2tNPps5R4UBzlU5axAdDQDrbmr5EsSvhQs7OxG4S5ucTtb7dntYxzeeNWLzi3MhWpwnxy4d3IpSX/0yxGeor0OGmPcqWHu/FixypNY2cZvSINxZ/YT3VTIHmc0u1Qm8k9R+WYO07u7SDLHH+1bnzfNGYjPkhfJIbdr/AfAS02LsKGiuAAAAAElFTkSuQmCC"); + width: 20px; + height: 19px; + + margin-right: 6px; + display: block; +} + +.BunError-Summary-CloseIcon { + content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAmCAYAAACoPemuAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAKKSURBVHgBxZjpTsMwEISnaTmLKLf4AX94/7fiFlRcJaUKl6exK8e1k7XTY6QVUbDjj/V4bdNBe2Uquio2nPdfKn6RqA7StKuir2JHxXZNO4LlKkb65zeWBLavYxdpelMxhABQCkaQI6QDuRrqCEoCdqjiFItXoeIagex1Gzqfo8zUMsSxByo+Vfz4fokaqH0sVx09xhxcCOwY5RSuQgbuHVZ58YGx0RlWK8LtoYT74wsXjEXyHM3eW4Y4JgFZ76ZV2xancAPrE+3T44MNRiCp2bnd5MK29A0rfyFsP7VRz3pxLOxoF8ctFZeYz7wRPfOgn/mHX6B5Rui1zP7gDmQaWc8TlEXytwGKKpy+dRoYMG41Um+52fHBuVBG0jH6ZpAtyMVV20MYLgRFq+xBpm1TFrjtbAo7dfUAI1SzxMr9ocF8UFIPUx2TsQxx4pTQ9G7migVAVYBiwagQnK0kKBsotdITblDz7eRDgAH7QZrop9CBj/67gbywVmTAxGdxVKEeGtoQKgkusz4QoxDUbK+zZOCibkzGW/yYtMawTNx73tPkJ/CXEj7zMEjPSY7zRWYNJtUwAGVWX2i1sgi/QabcgJn7X4p8JSEEJ139467TqS/oxAvup34+QrhOmR1ijHLV89uS2xY9+WjPN7N3hbRiu0hNj1U2BKfzGesVs/XCBzc7r0j32iLEbE1Xs2/aHtHivzQtxEzNTia+VUIoGnaA1Yl3iEptDC1fblGcb2nRbSNC3cGZpbq6MtGdCJf6f7Qm0c+38BwiJANKbzexeoJegT7FZOIA5SbdFjDXUJO6RrFTRChe82Jv7PQPbcFyMJZ0aOMd3qx47SMoyw5B6VnjF2aECyjXMFEl6B+GYop4atKAigAAAABJRU5ErkJggg=="); + width: 22px; + height: 22px; + border-radius: 50%; + cursor: pointer; +} + +.BunError-Summary-CloseIcon:hover { + transform: scale(1.2); + + background-color: rgb(255, 255, 255); +} + +.BunError-Summary { + display: grid; + grid-template-columns: min-content auto min-content; + grid-template-rows: 46px; + align-items: center; + padding: 0 18px; + border-bottom: 1px solid rgb(220, 220, 220); +} + +.BunError-footer { + display: grid; + padding: 12px 18px; + justify-content: flex-end; + border-top: 1px solid rgb(220, 220, 220); + align-items: center; +} + +.BunError-Summary-Title { + font-weight: 500; + letter-spacing: 0.36px; +} + +.BunError-ErrorTag, +.BunError-error-code { + color: rgb(165, 165, 165); + font-weight: 500; + font-size: 12pt; +} + +.BunError-ErrorTag { + font-size: 14px; + text-transform: uppercase; + font-weight: 300; +} + +.BunError-error-header { + display: flex; + align-items: center; + gap: 0.5ch; +} + +.BunError-error-message { + color: var(--bun-error-color); + font-size: 16pt; + font-weight: bold; +} + +.BunError-list { + margin-top: 14px; + gap: 14px; +} + +.BunError-error-subtitle, +.BunError-error-header, +.BunError-error-message { + padding-left: 18px; + padding-right: 18px; +} + +.BunError-error-subtitle { + font-size: 500; +} + +.BunError-NativeStackTrace { + margin-top: 0; + width: 100%; +} + +.BunError-NativeStackTrace-filename { + padding: 8px 18px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + font-weight: 500; + letter-spacing: 0.36px; + margin-bottom: 8px; + display: block; +} + +.BunError-NativeStackTrace-filename:hover { + text-decoration: underline; +} + +.BunError-SourceLines-lines { +} + +.BunError-SourceLines { + display: grid; + grid-template-columns: min-content auto; + grid-template-rows: repeat(16px, 6); + column-gap: 13px; + font-size: 14px; + padding-left: 24px; + align-items: center; + position: relative; + overflow-x: auto; +} + +.BunError-SourceLine-text { + white-space: pre; + display: block; + + font-family: var(--bun-error-monospace); +} + +.BunError-SourceLine-number { + font-variant: tabular-nums; + text-align: right; + user-select: none; + -webkit-user-select: none; +} + +.BunError-SourceLine-number--empty { + color: rgb(165, 165, 165); +} + +.BunError-SourceLine-number, +.BunError-SourceLine-text { + height: 18px; +} + +.BunError-SourceLines-highlighter--0, +.BunError-SourceLines-highlighter--1, +.BunError-SourceLines-highlighter--2, +.BunError-SourceLines-highlighter--3, +.BunError-SourceLines-highlighter--4, +.BunError-SourceLines-highlighter--5 { + position: absolute; +} + +.BunError-SourceLine-text--highlight { + color: #e33737; +} + +#BunError-poweredBy { + height: 16px; + + content: url("data:image/webp;base64,UklGRhwFAABXRUJQVlA4TBAFAAAv3MAHEBeioG0jx3f8MY+fGAgkbdz7Zz4Ekjbu/TMftpEkOaFfC5vgSJ4sHswjm7TZk7R34AJUQAFAQwYAQAUkQAZUAFTUz5XDIaPd77AAN2DlBrAAG/Ai8CFwcKICP6L+BzgTEBEBp6xqWyZJejDAWERMQEwHEuEip1yEmPDc/w1qRs7/OqL/EyBbtu22bQ4ViCHTKM0gUUNmIjMmZfu6s7nz3v//KwIgRfe3iP5PAP9bdVNLfR/hfdqK3bxLjDgr9Q4JGpfk7xHxPPu8KRdApEaiIpdSr8iU9id7Cn+vE6WbWqRuTORqm7pujCV+LKW8jyymEZHGALqpRer76LU4NXya9fhb5XgQZ2ks02uxtgBmtXSrBEAr1jO04mxfiazx06Lp71TgQXzbzoPYWSMoxV4pGLEWaMXz/xuHIyPevACm4r6BEXcaNBZeGPGt1NvmKij9pFL47cE48TgLaksRlF6SgnhtsKbrV/w0rVdO2yhdg+lKg+naB9OvK/+Ko+2KrNfeTfuV0/pp2q5rj3/G9OtK22C7ou3Laj6EXEvfHMYnOvM4D8R+Lf434PQ9/din0Vrt6wYsundZj+7QABzNPoeumpMa4Gj2JQ7mqlq3735vqjUCWmxzoq9D9C/217cCWbOtVPXsNvXOt7pD0NxVLV2xAklbflW9A6BF/YFD/clVawBi1XYX3+WbrHdRa4SiE8uWaDJcXbo4GhmrzhM41BWo+jtw6DtAsQJRfQegmSFqjcCpaeC9ALFaI5A0AVnvCWL1G2uEeOsJSXUnJFpjuI1xyWZsUVfok56wa6ZPesChEXZLc4VFN8ga6Zt1FAF23emL70DQGgGW7yJAbL4DQbedOdGvceRjyE2vbRD0hpfOg6h/wKw7vDyyJxwa4PZmmDV2hb74Zph0ZtHM8P6mMMw6Q5EmAA4pwzhOxrDpNw9oVijKWCtQzVCddwsUC6DWYfvi1jpsurLrMcrfnKOkOyySBkKi9UjOnkUcVd9QvzqtzFaCBprH6HPlSfVhjd3+J6VnNNoCmih8weaRVigaPhVg0XiYobgvOgPNm8dPKk93PUavb/KDDUhEExzSFiPJ/e7GcgwWLXDqOlg0A6G5FzdInocV4Pb9YYuPXhpGa4RZy6h+8x4VnYGZSIdE87GkPnJbj6WG7qU7LFoGWTeAYtYAi7WYu6THIHk/OjQNkm+g6dolv/HoVq30W9pqosleymsXay/vPVnXuL60RqDoayaeWukPtQA0detC831MxFPzo1A1RaZTbyBp3eeYbF+Z5pia7gNNlNEae/EtMHsOp+MNIFbHNQ6imrqsho7f/FjjI+bquEaA4rDmb87q8IfhZEdEP8ZyimkfHkXcq/qzMIy5qe2aGBdb7BY9GcfStTQBFM8RMXftFRmmpv7Ewxo+heYWs9oSH5dEGQa6mPXhCEHjdzkbZOGjBjAvkafzMk88XCaGcebhtCwzH2eeLss88XCeJ2CZeBhnYFrmic+a6M8AdaeaNj2OAeRenPy1HQeNi/U3jwIf/sszorAfTjqSf6u8rgBgWnrwcVBaOELuKhA0ruzVW7d/RGsMGJedKklKF5+rDuLSwad4ki4fA8G1jRdAUtkKZQn/Xb8pURYOgbhgEb5BfMYsIlwdwBmfMYtwdYBpwSLCxwCgzpiFCw0AScEsfB4BxHLw3x1vs1WIgXWaagBQOs1NAu94kadaAUCS5hsFe7wwCZx6sVF4/wI="); +} + +#BunError-SourceLine-text-highlightExpression { + font-weight: bold; + text-decoration-style: wavy; +} + +.BunError-JSException--TypeError #BunError-SourceLine-text-highlightExpression { + border: 1px solid rgba(0, 0, 0, 0.2); +} +.BunError-Indented { + display: inline-block; + user-select: none; + -webkit-user-select: none; +} + +.BunError-divet { + vertical-align: bottom; + user-select: none; + -webkit-user-select: none; +} + +.BunError-error-typename { + font-family: var(--bun-error-monospace); + color: #e39437; + font-weight: bold; +} + +.BunError-error-muted { + font-weight: normal; + user-select: none; + -webkit-user-select: none; +} + +.BunError-error-muted, +.BunError-StackFrame--muted { + color: rgb(165, 165, 165); +} + +.BunError-NativeStackTrace .BunError-error-typename { + user-select: none; + -webkit-user-select: none; +} + +.BunError-StackFrame-link { +} + +.BunError-StackFrame-link-content { + display: flex; + gap: 0.25ch; + white-space: nowrap; +} + +.BunError-StackFrame { + display: table-row; +} + +.BunError-StackFrame-identifier { + padding-right: 18px; + font-size: 0.8em; + font-family: var(--bun-error-monospace); + letter-spacing: 0.49px; +} + +.BunError-error-message--mono { + font-family: var(--bun-error-monospace); +} +.BunError-StackFrame-identifier, +.BunError-StackFrame-link { + display: table-cell; + font-weight: 500; +} + +.BunError-BuildError { + padding-bottom: 18px; +} + +.BunError-StackFrame-link-content { + font-size: 0.8em; +} + +.BunError-StackFrames { + display: table; + table-layout: auto; + padding: 13px 10px; + margin: 8px auto; + border-radius: 4px; + + background-color: rgb(244, 244, 244); +} diff --git a/packages/bun-framework-next/bun-error.tsx b/packages/bun-framework-next/bun-error.tsx new file mode 100644 index 000000000..5d57dd0ea --- /dev/null +++ b/packages/bun-framework-next/bun-error.tsx @@ -0,0 +1,809 @@ +import type { + FallbackMessageContainer, + JSException as JSExceptionType, + Message, + SourceLine, + StackFrame, + Problems, + FallbackStep, + StackTrace, + Location, + JSException, + WebsocketMessageBuildFailure, +} from "../../../src/api/schema"; + +import ReactDOM from "react-dom"; +import { + useCallback, + useState, + useEffect, + useLayoutEffect, + createContext, + useContext, + Children, +} from "react"; + +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: "open" }); + shadowRoot.appendChild(link); + shadowRoot.appendChild(reactRoot); + + document.body.appendChild(root); + ReactDOM.render(func(), reactRoot); + + debugger; + } else { + ReactDOM.render(func(), reactRoot); + } +} + +export function renderFallbackError(fallback: FallbackMessageContainer) { + return renderWithFunc(() => ( + <ErrorGroupContext.Provider value={fallback}> + <OverlayMessageContainer {...fallback} /> + </ErrorGroupContext.Provider> + )); +} + +export function dismissError() { + if (reactRoot) { + ReactDOM.unmountComponentAtNode(reactRoot); + const root = document.getElementById("__bun__error-root"); + if (root) root.remove(); + reactRoot = null; + } +} + +globalThis.renderBuildFailure = ( + failure: WebsocketMessageBuildFailure, + cwd: string +) => { + renderWithFunc(() => ( + <ErrorGroupContext.Provider value={{ cwd }}> + <BuildFailureMessageContainer messages={failure.log.msgs} /> + </ErrorGroupContext.Provider> + )); +}; diff --git a/packages/bun-framework-next/bun-error/close.png b/packages/bun-framework-next/bun-error/close.png Binary files differnew file mode 100644 index 000000000..11e513a1b --- /dev/null +++ b/packages/bun-framework-next/bun-error/close.png diff --git a/packages/bun-framework-next/bun-error/error.png b/packages/bun-framework-next/bun-error/error.png Binary files differnew file mode 100644 index 000000000..c35e01a2b --- /dev/null +++ b/packages/bun-framework-next/bun-error/error.png diff --git a/packages/bun-framework-next/bun-error/powered-by.png b/packages/bun-framework-next/bun-error/powered-by.png Binary files differnew file mode 100644 index 000000000..7e71f1357 --- /dev/null +++ b/packages/bun-framework-next/bun-error/powered-by.png diff --git a/packages/bun-framework-next/bun-error/powered-by.webp b/packages/bun-framework-next/bun-error/powered-by.webp Binary files differnew file mode 100644 index 000000000..0f48488ea --- /dev/null +++ b/packages/bun-framework-next/bun-error/powered-by.webp diff --git a/packages/bun-framework-next/bun-runtime-error.ts b/packages/bun-framework-next/bun-runtime-error.ts new file mode 100644 index 000000000..331040b36 --- /dev/null +++ b/packages/bun-framework-next/bun-runtime-error.ts @@ -0,0 +1,163 @@ +// Based on https://github.com/stacktracejs/error-stack-parser/blob/master/error-stack-parser.js + +import type { + StackFrame as StackFrameType, + StackFramePosition, + StackFrameScope, +} from "../../../src/api/schema"; + +export class StackFrame implements StackFrameType { + function_name: string; + file: string; + position: StackFramePosition; + scope: StackFrameScope; + lineText: string = ""; + constructor({ + functionName: function_name = "", + fileName: file = "", + lineNumber: line = -1, + columnNumber: column = -1, + source = "", + }) { + this.function_name = function_name; + this.file = file; + if (source) this.lineText = source; + this.scope = 3; + this.position = { + line: line, + source_offset: -1, + line_start: -1, + line_stop: -1, + column_start: column, + column_stop: -1, + expression_start: -1, + expression_stop: -1, + }; + } +} + +const FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+:\d+/; +const CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m; +const SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code])?$/; + +export default class RuntimeError { + original: Error; + stack: StackFrame[]; + + static from(error: Error) { + const runtime = new RuntimeError(); + runtime.original = error; + runtime.stack = this.parseStack(error); + return RuntimeError; + } + + /** + * Given an Error object, extract the most information from it. + * + * @param {Error} error object + * @return {Array} of StackFrames + */ + static parseStack(error) { + if (error.stack && error.stack.match(CHROME_IE_STACK_REGEXP)) { + return this.parseV8OrIE(error); + } else if (error.stack) { + return this.parseFFOrSafari(error); + } else { + return []; + } + } + + // Separate line and column numbers from a string of the form: (URI:Line:Column) + static extractLocation(urlLike) { + // Fail-fast but return locations like "(native)" + if (urlLike.indexOf(":") === -1) { + return [urlLike]; + } + + var regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/; + var parts = regExp.exec(urlLike.replace(/[()]/g, "")); + return [parts[1], parts[2] || undefined, parts[3] || undefined]; + } + + static parseV8OrIE(error) { + var filtered = error.stack.split("\n").filter(function (line) { + return !!line.match(CHROME_IE_STACK_REGEXP); + }, this); + + return filtered.map(function (line) { + if (line.indexOf("(eval ") > -1) { + // Throw away eval information until we implement stacktrace.js/stackframe#8 + line = line + .replace(/eval code/g, "eval") + .replace(/(\(eval at [^()]*)|(\),.*$)/g, ""); + } + var sanitizedLine = line.replace(/^\s+/, "").replace(/\(eval code/g, "("); + + // capture and preseve the parenthesized location "(/foo/my bar.js:12:87)" in + // case it has spaces in it, as the string is split on \s+ later on + var location = sanitizedLine.match(/ (\((.+):(\d+):(\d+)\)$)/); + + // remove the parenthesized location from the line, if it was matched + sanitizedLine = location + ? sanitizedLine.replace(location[0], "") + : sanitizedLine; + + var tokens = sanitizedLine.split(/\s+/).slice(1); + // if a location was matched, pass it to extractLocation() otherwise pop the last token + var locationParts = this.extractLocation( + location ? location[1] : tokens.pop() + ); + var functionName = tokens.join(" ") || undefined; + var fileName = + ["eval", "<anonymous>"].indexOf(locationParts[0]) > -1 + ? undefined + : locationParts[0]; + + return new StackFrame({ + functionName: functionName, + fileName: fileName, + lineNumber: locationParts[1], + columnNumber: locationParts[2], + source: line, + }); + }, this); + } + + static parseFFOrSafari(error) { + var filtered = error.stack.split("\n").filter(function (line) { + return !line.match(SAFARI_NATIVE_CODE_REGEXP); + }, this); + + return filtered.map(function (line) { + // Throw away eval information until we implement stacktrace.js/stackframe#8 + if (line.indexOf(" > eval") > -1) { + line = line.replace( + / line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g, + ":$1" + ); + } + + if (line.indexOf("@") === -1 && line.indexOf(":") === -1) { + // Safari eval frames only have function names and nothing else + return new StackFrame({ + functionName: line, + }); + } else { + var functionNameRegex = /((.*".+"[^@]*)?[^@]*)(?:@)/; + var matches = line.match(functionNameRegex); + var functionName = matches && matches[1] ? matches[1] : undefined; + var locationParts = this.extractLocation( + line.replace(functionNameRegex, "") + ); + + return new StackFrame({ + functionName: functionName, + fileName: locationParts[0], + lineNumber: locationParts[1], + columnNumber: locationParts[2], + source: line, + }); + } + }, this); + } +} diff --git a/packages/bun-framework-next/client.development.tsx b/packages/bun-framework-next/client.development.tsx index b93b1fcce..c08eb513e 100644 --- a/packages/bun-framework-next/client.development.tsx +++ b/packages/bun-framework-next/client.development.tsx @@ -1,5 +1,6 @@ globalThis.global = globalThis; globalThis.Bun_disableCSSImports = true; +import "./bun-error"; import * as React from "react"; var onlyChildPolyfill = React.Children.only; @@ -367,7 +368,7 @@ export async function _boot(EntryPointNamespace, isError) { <TopLevelRender App={CachedApp} Component={PageComponent} - props={{ pageProps: hydrateProps }} + props={hydrateProps} />, document.querySelector("#__next") ); @@ -376,7 +377,7 @@ export async function _boot(EntryPointNamespace, isError) { <TopLevelRender App={CachedApp} Component={PageComponent} - props={{ pageProps: hydrateProps }} + props={hydrateProps} />, document.querySelector("#__next") ); diff --git a/packages/bun-framework-next/fallback.development.tsx b/packages/bun-framework-next/fallback.development.tsx index 34e6cb349..b42835d36 100644 --- a/packages/bun-framework-next/fallback.development.tsx +++ b/packages/bun-framework-next/fallback.development.tsx @@ -1,43 +1,83 @@ import { insertStyleSheet } from "./page-loader"; +import type { + FallbackMessageContainer, + FallbackStep, +} from "../../../src/api/schema"; -const globalCSSQueue = []; -function insertGlobalStyleSheet({ detail }) { - globalCSSQueue.push(insertStyleSheet(detail)); +var once = false; +function insertGlobalStyleSheet(detail) { + if (!once) { + document.head.insertAdjacentHTML( + "beforeend", + `<meta name="next-head-count" content="${document.head.childElementCount}">` + ); + once = true; + } + pageLoader.cssQueue.push(insertStyleSheet(detail).then(() => {})); } +[...globalThis["__BUN"].allImportedStyles].map((detail) => + insertGlobalStyleSheet(detail) +); + document.addEventListener("onimportcss", insertGlobalStyleSheet, { passive: true, }); import { renderError, _boot, pageLoader } from "./client.development"; +import { renderFallbackError } from "bun-error"; -export default function render({ router, reason, problems }) { +function renderFallback({ + router, + reason, + problems, +}: FallbackMessageContainer) { const route = router.routes[router.route]; + if (!document.getElementById("__next")) { const next = document.createElement("div"); next.id = "__next"; document.body.prepend(next); - document.head.insertAdjacentHTML( - "beforeend", - `<meta name="next-head-count" content="2">` - ); } document.removeEventListener("onimportcss", insertGlobalStyleSheet); document.addEventListener("onimportcss", pageLoader.onImportCSS, { passive: true, }); - import(route) + + globalThis.__NEXT_DATA__.pages["/_app"] = [ + ...globalThis.__NEXT_DATA__.pages["/_app"], + ...globalThis["__BUN"].allImportedStyles, + ]; + + return import(route) .then((Namespace) => { return _boot(Namespace, true); }) .then(() => { - const cssQueue = pageLoader.cssQueue; + const cssQueue = pageLoader.cssQueue.slice(); pageLoader.cssQueue = []; - return Promise.all([...cssQueue, ...globalCSSQueue]); + return Promise.all([...cssQueue]); }) .finally(() => { document.body.style.visibility = "visible"; document.removeEventListener("onimportcss", pageLoader.onImportCSS); }); } + +export default function render(props: FallbackMessageContainer) { + renderFallback(props).then( + () => { + Promise.all(pageLoader.cssQueue).finally(() => { + renderFallbackError(props); + document.body.style.visibility = "visible"; + }); + }, + (err) => { + console.error(err); + Promise.all(pageLoader.cssQueue).finally(() => { + renderFallbackError(props); + }); + } + ); +} diff --git a/packages/bun-framework-next/next-image-polyfill.tsx b/packages/bun-framework-next/next-image-polyfill.tsx new file mode 100644 index 000000000..edc3775d7 --- /dev/null +++ b/packages/bun-framework-next/next-image-polyfill.tsx @@ -0,0 +1,36 @@ +function NextImagePolyfill({ + src, + width, + height, + objectFit, + style, + layout, + ...otherProps +}) { + var _style = style; + if (layout === "fit") { + objectFit = "contain"; + } else if (layout === "fill") { + objectFit = "cover"; + } + + if (objectFit) { + if (!_style) { + _style = { objectFit: objectFit }; + } else { + _style.objectFit = objectFit; + } + } + + return ( + <img + src={src} + width={width} + height={height} + style={_style} + {...otherProps} + /> + ); +} + +export default NextImagePolyfill; diff --git a/packages/bun-framework-next/package.json b/packages/bun-framework-next/package.json index af6286343..1f937589a 100644 --- a/packages/bun-framework-next/package.json +++ b/packages/bun-framework-next/package.json @@ -24,6 +24,9 @@ "fallback": "fallback.development.tsx", "server": "server.development.tsx", "css": "onimportcss", + "override": { + "next/dist/client/image.js": "next-image-polyfill.tsx" + }, "define": { "client": { ".env": "NEXT_PUBLIC_", diff --git a/packages/bun-framework-next/page-loader.ts b/packages/bun-framework-next/page-loader.ts index 98e132a5f..fc07578db 100644 --- a/packages/bun-framework-next/page-loader.ts +++ b/packages/bun-framework-next/page-loader.ts @@ -15,7 +15,12 @@ export function insertStyleSheet(url: string) { link.onerror = () => reject(); link.href = url; + + // if (headCount) { + // document.head.insertBefore(headCount, link); + // } else { document.head.appendChild(link); + // } }); } diff --git a/packages/bun-framework-next/polyfills.tsx b/packages/bun-framework-next/polyfills.tsx new file mode 100644 index 000000000..b000c1f54 --- /dev/null +++ b/packages/bun-framework-next/polyfills.tsx @@ -0,0 +1,23 @@ +globalThis.global = globalThis; + +import { Buffer } from "buffer"; + +globalThis.Buffer = Buffer; + +import * as React from "react"; + +class URL { + constructor(base, source) { + this.pathname = source; + this.href = base + source; + } +} +var onlyChildPolyfill = React.Children.only; +React.Children.only = function (children) { + if (children && typeof children === "object" && children.length == 1) { + return onlyChildPolyfill(children[0]); + } + + return onlyChildPolyfill(children); +}; +globalThis.URL = URL; diff --git a/packages/bun-framework-next/renderDocument.tsx b/packages/bun-framework-next/renderDocument.tsx index 97a65fff8..957615047 100644 --- a/packages/bun-framework-next/renderDocument.tsx +++ b/packages/bun-framework-next/renderDocument.tsx @@ -71,10 +71,6 @@ const notImplementedProxy = (base) => } ); -globalThis.fetch = (url, options) => { - return Promise.reject(new Error(`fetch is not implemented yet. sorry!!`)); -}; - function getScripts(files: DocumentFiles) { const { context, props } = this; const { @@ -574,6 +570,9 @@ export async function render({ ctx, }); + const pageProps = Object.assign({}, props.pageProps || {}); + // This isn't correct. + // We don't call getServerSideProps on clients. // This isn't correct. // We don't call getServerSideProps on clients. const getServerSideProps = PageNamespace.getServerSideProps; @@ -594,7 +593,7 @@ export async function render({ if (result) { if ("props" in result) { if (typeof result.props === "object") { - Object.assign(props, result.props); + Object.assign(pageProps, result.props); } } } @@ -615,7 +614,7 @@ export async function render({ if (result) { if ("props" in result) { if (typeof result.props === "object") { - Object.assign(props, result.props); + Object.assign(pageProps, result.props); } } } @@ -623,6 +622,7 @@ export async function render({ const renderToString = ReactDOMServer.renderToString; const ErrorDebug = null; + props.pageProps = pageProps; const renderPage: RenderPage = ( options: ComponentsEnhancer = {} @@ -648,7 +648,12 @@ export async function render({ const htmlOrPromise = renderToString( <AppContainer> - <EnhancedApp Component={EnhancedComponent} router={router} {...props} /> + <EnhancedApp + Component={EnhancedComponent} + router={router} + {...props} + pageProps={pageProps} + /> </AppContainer> ); return typeof htmlOrPromise === "string" diff --git a/packages/bun-framework-next/server.development.tsx b/packages/bun-framework-next/server.development.tsx index c6a7beebf..7391d9f32 100644 --- a/packages/bun-framework-next/server.development.tsx +++ b/packages/bun-framework-next/server.development.tsx @@ -1,23 +1,4 @@ -import * as React from "react"; -import { Buffer } from "buffer"; -globalThis.Buffer = Buffer; - -class URL { - constructor(base, source) { - this.pathname = source; - this.href = base + source; - } -} -var onlyChildPolyfill = React.Children.only; -React.Children.only = function (children) { - if (children && typeof children === "object" && children.length == 1) { - return onlyChildPolyfill(children[0]); - } - - return onlyChildPolyfill(children); -}; -globalThis.URL = URL; -globalThis.global = globalThis; +import "./polyfills"; import { render } from "./renderDocument"; let buildId = 0; @@ -40,14 +21,6 @@ import(Bun.routesDir + "_document").then( ); addEventListener("fetch", async (event: FetchEvent) => { - var appRoute; - - try { - appRoute = await import(Bun.routesDir + "_app"); - } catch (exception) { - appRoute = null; - } - const appStylesheets = (Bun.getImportedStyles() as string[]).slice(); var route = Bun.match(event); // This imports the currently matched route. @@ -57,6 +30,15 @@ addEventListener("fetch", async (event: FetchEvent) => { // It's recursive, so any file that imports a CSS file will be included. const pageStylesheets = (Bun.getImportedStyles() as string[]).slice(); + var appRoute; + + try { + appRoute = await import(Bun.routesDir + "_app"); + } catch (exception) { + appRoute = null; + } + const appStylesheets = (Bun.getImportedStyles() as string[]).slice(); + event.respondWith( render({ route, |