);
};
-var thisCwd = "";
+export var thisCwd = "";
const ErrorGroupContext = createContext<{ cwd: string }>(null);
var reactRoot;
@@ -1513,8 +1213,15 @@ export function renderFallbackError(fallback: FallbackMessageContainer) {
}
import { parse as getStackTrace } from "./stack-trace-parser";
+var runtimeErrorController: AbortController;
+var pending = [];
+var onIdle = globalThis.requestIdleCallback || ((cb) => setTimeout(cb, 32));
+function clearSourceMappings() {
+ sourceMappings.clear();
+}
export function renderRuntimeError(error: Error) {
+ runtimeErrorController = new AbortController();
if (typeof error === "string") {
error = {
name: "Error",
@@ -1580,6 +1287,7 @@ export function renderRuntimeError(error: Error) {
}
}
}
+ const signal = runtimeErrorController.signal;
const fallback: FallbackMessageContainer = {
message: error.message,
@@ -1595,11 +1303,86 @@ export function renderRuntimeError(error: Error) {
exceptions: [exception],
},
};
- return renderWithFunc(() => (
-
-
-
- ));
+
+ var stopThis = { stopped: false };
+ pending.push(stopThis);
+
+ const BunError = () => {
+ return (
+
+
+
+ );
+ };
+
+ // Remap the sourcemaps
+ // But! If we've already fetched the source mappings in this page load before
+ // Rely on the cached ones
+ // and don't fetch them again
+ const framePromises = exception.stack.frames
+ .map((frame, i) => {
+ if (stopThis.stopped) return null;
+ return [
+ fetchMappings(normalizedFilename(frame.file, thisCwd), signal),
+ i,
+ ];
+ })
+ .map((result) => {
+ if (!result) return;
+ const [mappings, frameIndex] = result;
+ if (mappings?.then) {
+ return mappings.then((mappings) => {
+ if (!mappings || stopThis.stopped) {
+ return null;
+ }
+ var frame = exception.stack.frames[frameIndex];
+
+ const { line, column_start } = frame.position;
+ const remapped = remapPosition(mappings, line, column_start);
+ if (!remapped) return null;
+ frame.position.line_start = frame.position.line = remapped[0];
+ frame.position.column_stop =
+ frame.position.expression_stop =
+ frame.position.expression_start =
+ frame.position.column_start =
+ remapped[1];
+ }, console.error);
+ } else {
+ var frame = exception.stack.frames[frameIndex];
+ const { line, column_start } = frame.position;
+ const remapped = remapPosition(mappings, line, column_start);
+ if (!remapped) return null;
+ frame.position.line_start = frame.position.line = remapped[0];
+ frame.position.column_stop =
+ frame.position.expression_stop =
+ frame.position.expression_start =
+ frame.position.column_start =
+ remapped[1];
+ }
+ });
+
+ var anyPromises = false;
+ for (let i = 0; i < framePromises.length; i++) {
+ if (framePromises[i] && framePromises[i].then) {
+ anyPromises = true;
+ break;
+ }
+ }
+
+ if (anyPromises) {
+ Promise.allSettled(framePromises).finally(() => {
+ if (stopThis.stopped || signal.aborted) return;
+ onIdle(clearSourceMappings);
+ return renderWithFunc(() => {
+ return
;
+ });
+ });
+ } else {
+ onIdle(clearSourceMappings);
+ renderWithFunc(() => {
+ return
;
+ });
+ }
}
export function dismissError() {
@@ -1608,6 +1391,12 @@ export function dismissError() {
const root = document.getElementById("__bun__error-root");
if (root) root.remove();
reactRoot = null;
+ if (runtimeErrorController) {
+ runtimeErrorController.abort();
+ runtimeErrorController = null;
+ }
+
+ while (pending.length > 0) pending.shift().stopThis = true;
}
}
diff --git a/packages/bun-error/markdown.ts b/packages/bun-error/markdown.ts
new file mode 100644
index 0000000000..72f36674cf
--- /dev/null
+++ b/packages/bun-error/markdown.ts
@@ -0,0 +1,389 @@
+import {
+ normalizedFilename,
+ StackFrameIdentifier,
+ thisCwd,
+ StackFrameScope,
+} from "./index";
+import type {
+ FallbackMessageContainer,
+ JSException,
+ JSException as JSExceptionType,
+ Location,
+ Message,
+ Problems,
+ SourceLine,
+ StackFrame,
+ WebsocketMessageBuildFailure,
+} from "../../src/api/schema";
+
+export function problemsToMarkdown(problems: Problems) {
+ var markdown = "";
+ if (problems?.build?.msgs?.length) {
+ markdown += messagesToMarkdown(problems.build.msgs);
+ }
+
+ if (problems?.exceptions?.length) {
+ markdown += exceptionsToMarkdown(problems.exceptions);
+ }
+
+ return markdown;
+}
+
+export function messagesToMarkdown(messages: Message[]): string {
+ return messages
+ .map(messageToMarkdown)
+ .map((a) => a.trim())
+ .join("\n");
+}
+
+export function exceptionsToMarkdown(exceptions: JSExceptionType[]): string {
+ return exceptions
+ .map(exceptionToMarkdown)
+ .map((a) => a.trim())
+ .join("\n");
+}
+
+function exceptionToMarkdown(exception: JSException): string {
+ const { name: name_, message: message_, stack } = exception;
+ var name = String(name_).trim();
+ var message = String(message_).trim();
+
+ let markdown = "";
+
+ if (
+ name === "Error" ||
+ name === "RangeError" ||
+ name === "TypeError" ||
+ name === "ReferenceError" ||
+ name === "DOMException"
+ ) {
+ markdown += `**${message}**\n`;
+ } else {
+ markdown += `**${name}**\n${message}\n`;
+ }
+
+ if (stack.frames.length > 0) {
+ var frames = stack.frames;
+ if (stack.source_lines.length > 0) {
+ const {
+ file: _file = "",
+ function_name = "",
+ position: {
+ line = -1,
+ column_start: column = -1,
+ column_stop: columnEnd = column,
+ } = {
+ line: -1,
+ column_start: -1,
+ column_stop: -1,
+ },
+ scope = 0,
+ } = stack.frames[0];
+ const file = normalizedFilename(_file, thisCwd);
+
+ if (file) {
+ if (function_name.length > 0) {
+ markdown += `In \`${function_name}\` – ${file}`;
+ } else if (scope > 0 && scope < StackFrameScope.Constructor + 1) {
+ markdown += `${StackFrameIdentifier({
+ functionName: function_name,
+ scope,
+ markdown: true,
+ })} ${file}`;
+ } else {
+ markdown += `In ${file}`;
+ }
+
+ if (line > -1) {
+ markdown += `:${line}`;
+ if (column > -1) {
+ markdown += `:${column}`;
+ }
+ }
+
+ if (stack.source_lines.length > 0) {
+ // TODO: include loader
+ const extnameI = file.lastIndexOf(".");
+ const extname = extnameI > -1 ? file.slice(extnameI + 1) : "";
+
+ markdown += "\n```";
+ markdown += extname;
+ markdown += "\n";
+ stack.source_lines.forEach((sourceLine) => {
+ const lineText = sourceLine.text.trimEnd();
+ markdown += lineText + "\n";
+ if (sourceLine.line === line && stack.source_lines.length > 1) {
+ // the comment should start at the first non-whitespace character
+ // ideally it should be length the original line
+ // but it may not be
+ var prefix = "".padStart(
+ lineText.length - lineText.trimStart().length,
+ " "
+ );
+
+ prefix +=
+ "/* ".padEnd(column - 1 - prefix.length, " ") +
+ "^ happend here ";
+ markdown +=
+ prefix.padEnd(Math.max(lineText.length, 1) - 1, " ") + "*/\n";
+ }
+ });
+ markdown = markdown.trimEnd() + "\n```";
+ }
+ }
+ }
+
+ if (frames.length > 0) {
+ markdown += "\nStack trace:\n";
+ var padding = 0;
+ // Limit to 8 frames because it may be a huge stack trace
+ // and we want to not hit the message limit
+ const framesToDisplay = frames.slice(0, Math.min(frames.length, 8));
+ for (let frame of framesToDisplay) {
+ const {
+ function_name = "",
+ position: { line = -1, column_start: column = -1 } = {
+ line: -1,
+ column_start: -1,
+ },
+ scope = 0,
+ } = frame;
+ padding = Math.max(
+ padding,
+ StackFrameIdentifier({
+ scope,
+ functionName: function_name,
+ markdown: true,
+ }).length
+ );
+ }
+
+ markdown += "```js\n";
+
+ for (let frame of framesToDisplay) {
+ const {
+ file = "",
+ function_name = "",
+ position: { line = -1, column_start: column = -1 } = {
+ line: -1,
+ column_start: -1,
+ },
+ scope = 0,
+ } = frame;
+
+ markdown += `
+ ${StackFrameIdentifier({
+ scope,
+ functionName: function_name,
+ markdown: true,
+ }).padEnd(padding, " ")}`;
+
+ if (file) {
+ markdown += ` ${normalizedFilename(file, thisCwd)}`;
+ if (line > -1) {
+ markdown += `:${line}`;
+ if (column > -1) {
+ markdown += `:${column}`;
+ }
+ }
+ }
+ }
+
+ markdown += "\n```\n";
+ }
+ }
+
+ return markdown;
+}
+
+function messageToMarkdown(message: Message): string {
+ var tag = "Error";
+ if (message.on.build) {
+ tag = "BuildError";
+ }
+ var lines = (message.data.text ?? "").split("\n");
+
+ var markdown = "";
+ if (message?.on?.resolve) {
+ markdown += `**ResolveError**: "${message.on.resolve}" failed to resolve\n`;
+ } else {
+ var firstLine = lines[0];
+ lines = lines.slice(1);
+ if (firstLine.length > 120) {
+ const words = firstLine.split(" ");
+ var end = 0;
+ for (let i = 0; i < words.length; i++) {
+ if (end + words[i].length >= 120) {
+ firstLine = words.slice(0, i).join(" ");
+ lines.unshift(words.slice(i).join(" "));
+ break;
+ }
+ }
+ }
+
+ markdown += `**${tag}**${firstLine.length > 0 ? ": " + firstLine : ""}\n`;
+ }
+
+ if (message.data?.location?.file) {
+ markdown += `In ${normalizedFilename(message.data.location.file, thisCwd)}`;
+ if (message.data.location.line > -1) {
+ markdown += `:${message.data.location.line}`;
+ if (message.data.location.column > -1) {
+ markdown += `:${message.data.location.column}`;
+ }
+ }
+
+ if (message.data.location.line_text.length) {
+ const extnameI = message.data.location.file.lastIndexOf(".");
+ const extname =
+ extnameI > -1 ? message.data.location.file.slice(extnameI + 1) : "";
+
+ markdown +=
+ "\n```" + extname + "\n" + message.data.location.line_text + "\n```\n";
+ } else {
+ markdown += "\n";
+ }
+
+ if (lines.length > 0) {
+ markdown += lines.join("\n");
+ }
+ }
+
+ return markdown;
+}
+
+export const withBunInfo = (text) => {
+ const bunInfo = getBunInfo();
+
+ const trimmed = text.trim();
+
+ if (bunInfo && "then" in bunInfo) {
+ return bunInfo.then(
+ (info) => {
+ const markdown = bunInfoToMarkdown(info).trim();
+ return trimmed + "\n" + markdown + "\n";
+ },
+ () => trimmed + "\n"
+ );
+ }
+
+ if (bunInfo) {
+ const markdown = bunInfoToMarkdown(bunInfo).trim();
+
+ return trimmed + "\n" + markdown + "\n";
+ }
+
+ return trimmed + "\n";
+};
+
+function bunInfoToMarkdown(_info) {
+ if (!_info) return;
+ const info = { ..._info, platform: { ..._info.platform } };
+
+ var operatingSystemVersion = info.platform.version;
+
+ if (info.platform.os.toLowerCase() === "macos") {
+ const [major, minor, patch] = operatingSystemVersion.split(".");
+ switch (major) {
+ case "22": {
+ operatingSystemVersion = `13.${minor}.${patch}`;
+ break;
+ }
+ case "21": {
+ operatingSystemVersion = `12.${minor}.${patch}`;
+ break;
+ }
+ case "20": {
+ operatingSystemVersion = `11.${minor}.${patch}`;
+ break;
+ }
+
+ case "19": {
+ operatingSystemVersion = `10.15.${patch}`;
+ break;
+ }
+
+ case "18": {
+ operatingSystemVersion = `10.14.${patch}`;
+ break;
+ }
+
+ case "17": {
+ operatingSystemVersion = `10.13.${patch}`;
+ break;
+ }
+
+ case "16": {
+ operatingSystemVersion = `10.12.${patch}`;
+ break;
+ }
+
+ case "15": {
+ operatingSystemVersion = `10.11.${patch}`;
+ break;
+ }
+ }
+ info.platform.os = "macOS";
+ }
+
+ if (info.platform.arch === "arm" && info.platform.os === "macOS") {
+ info.platform.arch = "Apple Silicon";
+ } else if (info.platform.arch === "arm") {
+ info.platform.arch = "aarch64";
+ }
+
+ var base = `Info:
+ > bun v${info.bun_version}
+ `;
+
+ if (info.framework && info.framework_version) {
+ base += `> framework: ${info.framework}@${info.framework_version}`;
+ } else if (info.framework) {
+ base += `> framework: ${info.framework}`;
+ }
+
+ base =
+ base.trim() +
+ `
+ > ${info.platform.os} ${operatingSystemVersion} (${info.platform.arch})
+ > User-Agent: ${globalThis.navigator.userAgent}
+ > Pathname: ${globalThis.location.pathname}
+ `;
+
+ return base;
+}
+
+var bunInfoMemoized;
+function getBunInfo() {
+ if (bunInfoMemoized) return bunInfoMemoized;
+ if ("sessionStorage" in globalThis) {
+ try {
+ const bunInfoMemoizedString = sessionStorage.getItem("__bunInfo");
+ if (bunInfoMemoizedString) {
+ bunInfoMemoized = JSON.parse(bunInfoMemoized);
+ return bunInfoMemoized;
+ }
+ } catch (exception) {}
+ }
+ const controller = new AbortController();
+ const timeout = 1000;
+ const id = setTimeout(() => controller.abort(), timeout);
+ return fetch("/bun:info", {
+ signal: controller.signal,
+ headers: {
+ Accept: "application/json",
+ },
+ })
+ .then((resp) => resp.json())
+ .then((bunInfo) => {
+ clearTimeout(id);
+ bunInfoMemoized = bunInfo;
+ if ("sessionStorage" in globalThis) {
+ try {
+ sessionStorage.setItem("__bunInfo", JSON.stringify(bunInfo));
+ } catch (exception) {}
+ }
+
+ return bunInfo;
+ });
+}
diff --git a/packages/bun-error/runtime-error.ts b/packages/bun-error/runtime-error.ts
index 331040b36c..c9aa3b1b2f 100644
--- a/packages/bun-error/runtime-error.ts
+++ b/packages/bun-error/runtime-error.ts
@@ -12,6 +12,8 @@ export class StackFrame implements StackFrameType {
position: StackFramePosition;
scope: StackFrameScope;
lineText: string = "";
+ remapped: boolean = false;
+
constructor({
functionName: function_name = "",
fileName: file = "",
diff --git a/packages/bun-error/sourcemap.ts b/packages/bun-error/sourcemap.ts
new file mode 100644
index 0000000000..827231490a
--- /dev/null
+++ b/packages/bun-error/sourcemap.ts
@@ -0,0 +1,300 @@
+// Accelerate VLQ decoding with a lookup table
+const vlqTable = new Uint8Array(128);
+const vlqChars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+vlqTable.fill(0xff);
+for (let i = 0; i < vlqChars.length; i++) vlqTable[vlqChars.charCodeAt(i)] = i;
+
+export function parseSourceMap(json) {
+ if (json.version !== 3) {
+ throw new Error("Invalid source map");
+ }
+
+ if (
+ !(json.sources instanceof Array) ||
+ json.sources.some((x) => typeof x !== "string")
+ ) {
+ throw new Error("Invalid source map");
+ }
+
+ if (typeof json.mappings !== "string") {
+ throw new Error("Invalid source map");
+ }
+
+ const { sources, sourcesContent, names, mappings } = json;
+ const emptyData = new Int32Array(0);
+ for (let i = 0; i < sources.length; i++) {
+ sources[i] = {
+ name: sources[i],
+ content: (sourcesContent && sourcesContent[i]) || "",
+ data: emptyData,
+ dataLength: 0,
+ };
+ }
+ const data = decodeMappings(mappings, sources.length);
+ return { sources, names, data };
+}
+
+// ripped from https://github.com/evanw/source-map-visualization/blob/gh-pages/code.js#L179
+export function decodeMappings(mappings, sourcesCount) {
+ const n = mappings.length;
+ let data = new Int32Array(1024);
+ let dataLength = 0;
+ let generatedLine = 0;
+ let generatedLineStart = 0;
+ let generatedColumn = 0;
+ let originalSource = 0;
+ let originalLine = 0;
+ let originalColumn = 0;
+ let originalName = 0;
+ let needToSortGeneratedColumns = false;
+ let i = 0;
+
+ function decodeError(text) {
+ const error = `Invalid VLQ data at index ${i}: ${text}`;
+ throw new Error(error);
+ }
+
+ function decodeVLQ() {
+ let shift = 0;
+ let vlq = 0;
+
+ // Scan over the input
+ while (true) {
+ // Read a byte
+ if (i >= mappings.length) decodeError("Expected extra data");
+ const c = mappings.charCodeAt(i);
+ if ((c & 0x7f) !== c) decodeError("Invalid character");
+ const index = vlqTable[c & 0x7f];
+ if (index === 0xff) decodeError("Invalid character");
+ i++;
+
+ // Decode the byte
+ vlq |= (index & 31) << shift;
+ shift += 5;
+
+ // Stop if there's no continuation bit
+ if ((index & 32) === 0) break;
+ }
+
+ // Recover the signed value
+ return vlq & 1 ? -(vlq >> 1) : vlq >> 1;
+ }
+
+ while (i < n) {
+ let c = mappings.charCodeAt(i);
+
+ // Handle a line break
+ if (c === 59 /* ; */) {
+ // The generated columns are very rarely out of order. In that case,
+ // sort them with insertion since they are very likely almost ordered.
+ if (needToSortGeneratedColumns) {
+ for (let j = generatedLineStart + 6; j < dataLength; j += 6) {
+ const genL = data[j];
+ const genC = data[j + 1];
+ const origS = data[j + 2];
+ const origL = data[j + 3];
+ const origC = data[j + 4];
+ const origN = data[j + 5];
+ let k = j - 6;
+ for (; k >= generatedLineStart && data[k + 1] > genC; k -= 6) {
+ data[k + 6] = data[k];
+ data[k + 7] = data[k + 1];
+ data[k + 8] = data[k + 2];
+ data[k + 9] = data[k + 3];
+ data[k + 10] = data[k + 4];
+ data[k + 11] = data[k + 5];
+ }
+ data[k + 6] = genL;
+ data[k + 7] = genC;
+ data[k + 8] = origS;
+ data[k + 9] = origL;
+ data[k + 10] = origC;
+ data[k + 11] = origN;
+ }
+ }
+
+ generatedLine++;
+ generatedColumn = 0;
+ generatedLineStart = dataLength;
+ needToSortGeneratedColumns = false;
+ i++;
+ continue;
+ }
+
+ // Ignore stray commas
+ if (c === 44 /* , */) {
+ i++;
+ continue;
+ }
+
+ // Read the generated column
+ const generatedColumnDelta = decodeVLQ();
+ if (generatedColumnDelta < 0) needToSortGeneratedColumns = true;
+ generatedColumn += generatedColumnDelta;
+ if (generatedColumn < 0) decodeError("Invalid generated column");
+
+ // It's valid for a mapping to have 1, 4, or 5 variable-length fields
+ let isOriginalSourceMissing = true;
+ let isOriginalNameMissing = true;
+ if (i < n) {
+ c = mappings.charCodeAt(i);
+ if (c === 44 /* , */) {
+ i++;
+ } else if (c !== 59 /* ; */) {
+ isOriginalSourceMissing = false;
+
+ // Read the original source
+ const originalSourceDelta = decodeVLQ();
+ originalSource += originalSourceDelta;
+ if (originalSource < 0 || originalSource >= sourcesCount)
+ decodeError("Invalid original source");
+
+ // Read the original line
+ const originalLineDelta = decodeVLQ();
+ originalLine += originalLineDelta;
+ if (originalLine < 0) decodeError("Invalid original line");
+
+ // Read the original column
+ const originalColumnDelta = decodeVLQ();
+ originalColumn += originalColumnDelta;
+ if (originalColumn < 0) decodeError("Invalid original column");
+
+ // Check for the optional name index
+ if (i < n) {
+ c = mappings.charCodeAt(i);
+ if (c === 44 /* , */) {
+ i++;
+ } else if (c !== 59 /* ; */) {
+ isOriginalNameMissing = false;
+
+ // Read the optional name index
+ const originalNameDelta = decodeVLQ();
+ originalName += originalNameDelta;
+ if (originalName < 0) decodeError("Invalid original name");
+
+ // Handle the next character
+ if (i < n) {
+ c = mappings.charCodeAt(i);
+ if (c === 44 /* , */) {
+ i++;
+ } else if (c !== 59 /* ; */) {
+ decodeError("Invalid character after mapping");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Append the mapping to the typed array
+ if (dataLength + 6 > data.length) {
+ const newData = new Int32Array(data.length << 1);
+ newData.set(data);
+ data = newData;
+ }
+ data[dataLength] = generatedLine;
+ data[dataLength + 1] = generatedColumn;
+ if (isOriginalSourceMissing) {
+ data[dataLength + 2] = -1;
+ data[dataLength + 3] = -1;
+ data[dataLength + 4] = -1;
+ } else {
+ data[dataLength + 2] = originalSource;
+ data[dataLength + 3] = originalLine;
+ data[dataLength + 4] = originalColumn;
+ }
+ data[dataLength + 5] = isOriginalNameMissing ? -1 : originalName;
+ dataLength += 6;
+ }
+
+ return data.subarray(0, dataLength);
+}
+
+export function remapPosition(
+ decodedMappings: Int32Array,
+ line: number,
+ column: number
+) {
+ if (!(decodedMappings instanceof Int32Array)) {
+ throw new Error("decodedMappings must be an Int32Array");
+ }
+
+ if (!Number.isFinite(line)) {
+ throw new Error("line must be a finite number");
+ }
+
+ if (!Number.isFinite(column)) {
+ throw new Error("column must be a finite number");
+ }
+
+ if (decodedMappings.length === 0 || line < 0 || column < 0) return null;
+
+ const index = indexOfMapping(decodedMappings, line, column);
+ if (index === -1) return null;
+
+ return [decodedMappings[index + 3], decodedMappings[index + 4]];
+}
+
+async function fetchRemoteSourceMap(file: string, signal) {
+ const response = await globalThis.fetch(file + ".map", {
+ signal,
+ headers: {
+ Accept: "application/json",
+ "Mappings-Only": "1",
+ },
+ });
+
+ if (response.ok) {
+ return await response.json();
+ }
+
+ return null;
+}
+
+export var sourceMappings = new Map();
+
+export function fetchMappings(file, signal) {
+ if (sourceMappings.has(file)) {
+ return sourceMappings.get(file);
+ }
+
+ return fetchRemoteSourceMap(file, signal).then((json) => {
+ if (!json) return null;
+ const { data } = parseSourceMap(json);
+ sourceMappings.set(file, data);
+ return data;
+ });
+}
+
+function indexOfMapping(mappings: Int32Array, line: number, column: number) {
+ // the array is [generatedLine, generatedColumn, sourceIndex, sourceLine, sourceColumn, nameIndex]
+ // 0 - generated line
+ var count = mappings.length / 6;
+ var index = 0;
+ while (count > 0) {
+ var step = (count / 2) | 0;
+ var i = index + step;
+ // this multiply is slow but it's okay for now
+ var j = i * 6;
+ if (
+ mappings[j] < line ||
+ (mappings[j] == line && mappings[j + 1] <= column)
+ ) {
+ index = i + 1;
+ count -= step + 1;
+ } else {
+ count = step;
+ }
+ }
+
+ index = index | 0;
+
+ if (index > 0) {
+ if (mappings[(index - 1) * 6] == line) {
+ return (index - 1) * 6;
+ }
+ }
+
+ return null;
+}
diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts
index 3e84747872..fb761317d4 100644
--- a/src/api/schema.d.ts
+++ b/src/api/schema.d.ts
@@ -616,6 +616,7 @@ export interface WebsocketMessageWelcome {
epoch: uint32;
javascriptReloader: Reloader;
cwd: string;
+ assetPrefix: string;
}
export interface WebsocketMessageFileChangeNotification {
diff --git a/src/api/schema.js b/src/api/schema.js
index 4dfa2e2455..2117e8c6de 100644
--- a/src/api/schema.js
+++ b/src/api/schema.js
@@ -2667,6 +2667,7 @@ function decodeWebsocketMessageWelcome(bb) {
result["epoch"] = bb.readUint32();
result["javascriptReloader"] = Reloader[bb.readByte()];
result["cwd"] = bb.readString();
+ result["assetPrefix"] = bb.readString();
return result;
}
@@ -2696,6 +2697,13 @@ function encodeWebsocketMessageWelcome(message, bb) {
} else {
throw new Error('Missing required field "cwd"');
}
+
+ var value = message["assetPrefix"];
+ if (value != null) {
+ bb.writeString(value);
+ } else {
+ throw new Error('Missing required field "assetPrefix"');
+ }
}
function decodeWebsocketMessageFileChangeNotification(bb) {
diff --git a/src/api/schema.peechy b/src/api/schema.peechy
index 9fe2fdc7a6..9dce087563 100644
--- a/src/api/schema.peechy
+++ b/src/api/schema.peechy
@@ -501,6 +501,7 @@ struct WebsocketMessageWelcome {
uint32 epoch;
Reloader javascriptReloader;
string cwd;
+ string assetPrefix;
}
struct WebsocketMessageFileChangeNotification {
@@ -584,4 +585,4 @@ message BunInstall {
bool disable_manifest_cache = 16;
string global_dir = 17;
string global_bin_dir = 18;
-}
\ No newline at end of file
+}
diff --git a/src/api/schema.zig b/src/api/schema.zig
index 2374103ebb..bbec8a98cd 100644
--- a/src/api/schema.zig
+++ b/src/api/schema.zig
@@ -2575,12 +2575,16 @@ pub const Api = struct {
/// cwd
cwd: []const u8,
+ /// assetPrefix
+ asset_prefix: []const u8,
+
pub fn decode(reader: anytype) anyerror!WebsocketMessageWelcome {
var this = std.mem.zeroes(WebsocketMessageWelcome);
this.epoch = try reader.readValue(u32);
this.javascript_reloader = try reader.readValue(Reloader);
this.cwd = try reader.readValue([]const u8);
+ this.asset_prefix = try reader.readValue([]const u8);
return this;
}
@@ -2588,6 +2592,7 @@ pub const Api = struct {
try writer.writeInt(this.epoch);
try writer.writeEnum(this.javascript_reloader);
try writer.writeValue(@TypeOf(this.cwd), this.cwd);
+ try writer.writeValue(@TypeOf(this.asset_prefix), this.asset_prefix);
}
};
diff --git a/src/http.zig b/src/http.zig
index 06aef30947..2e1f153c05 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -1818,6 +1818,7 @@ pub const RequestContext = struct {
}
const welcome_message = Api.WebsocketMessageWelcome{
+ .asset_prefix = handler.ctx.bundler.options.routes.asset_prefix_path,
.epoch = WebsocketHandler.toTimestamp(
@intCast(u64, (handler.ctx.timer.started.timestamp.tv_sec * std.time.ns_per_s)) + @intCast(u64, handler.ctx.timer.started.timestamp.tv_nsec),
),
@@ -2251,7 +2252,12 @@ pub const RequestContext = struct {
}
if (this.rctx.has_called_done) return;
this.buffer.reset();
- this.buffer = try chunk.printSourceMapContents(source, this.buffer, false);
+ this.buffer = try chunk.printSourceMapContents(
+ source,
+ this.buffer,
+ this.rctx.header("Mappings-Only") == null,
+ false,
+ );
defer {
this.buffer.reset();
SocketPrinterInternal.buffer.?.* = this.buffer;
@@ -2893,6 +2899,11 @@ pub const RequestContext = struct {
}
}
+ if (ctx.bundler.options.routes.asset_prefix_path.len > 0 and
+ strings.hasPrefix(input_path, ctx.bundler.options.routes.asset_prefix_path))
+ {
+ input_path = input_path[ctx.bundler.options.routes.asset_prefix_path.len..];
+ }
if (input_path.len == 0) return ctx.sendNotFound();
const pathname = Fs.PathName.init(input_path);
diff --git a/src/javascript/jsc/bindings/exports.zig b/src/javascript/jsc/bindings/exports.zig
index 75a7c96be8..0a0a045a3a 100644
--- a/src/javascript/jsc/bindings/exports.zig
+++ b/src/javascript/jsc/bindings/exports.zig
@@ -389,7 +389,12 @@ pub const ZigStackTrace = extern struct {
frames_ptr: [*c]ZigStackFrame,
frames_len: u8,
- pub fn toAPI(this: *const ZigStackTrace, allocator: std.mem.Allocator) !Api.StackTrace {
+ pub fn toAPI(
+ this: *const ZigStackTrace,
+ allocator: std.mem.Allocator,
+ root_path: string,
+ origin: ?*const ZigURL,
+ ) !Api.StackTrace {
var stack_trace: Api.StackTrace = std.mem.zeroes(Api.StackTrace);
{
var source_lines_iter = this.sourceLineIterator();
@@ -424,7 +429,11 @@ pub const ZigStackTrace = extern struct {
stack_trace.frames = stack_frames;
for (_frames) |frame, i| {
- stack_frames[i] = try frame.toAPI(allocator);
+ stack_frames[i] = try frame.toAPI(
+ root_path,
+ origin,
+ allocator,
+ );
}
}
}
@@ -480,18 +489,24 @@ pub const ZigStackFrame = extern struct {
position: ZigStackFramePosition,
code_type: ZigStackFrameCode,
- pub fn toAPI(this: *const ZigStackFrame, allocator: std.mem.Allocator) !Api.StackFrame {
+ /// This informs formatters whether to display as a blob URL or not
+ remapped: bool = false,
+
+ pub fn toAPI(this: *const ZigStackFrame, root_path: string, origin: ?*const ZigURL, allocator: std.mem.Allocator) !Api.StackFrame {
var frame: Api.StackFrame = std.mem.zeroes(Api.StackFrame);
if (this.function_name.len > 0) {
frame.function_name = try allocator.dupe(u8, this.function_name.slice());
}
if (this.source_url.len > 0) {
- frame.file = try allocator.dupe(u8, this.source_url.slice());
+ frame.file = try std.fmt.allocPrint(allocator, "{any}", .{this.sourceURLFormatter(root_path, origin, true, false)});
}
frame.position.source_offset = this.position.source_offset;
- frame.position.line = this.position.line;
+
+ // For remapped code, we add 1 to the line number
+ frame.position.line = this.position.line + @as(i32, @boolToInt(this.remapped));
+
frame.position.line_start = this.position.line_start;
frame.position.line_stop = this.position.line_stop;
frame.position.column_start = this.position.column_start;
@@ -508,7 +523,8 @@ pub const ZigStackFrame = extern struct {
position: ZigStackFramePosition,
enable_color: bool,
origin: ?*const ZigURL,
-
+ exclude_line_column: bool = false,
+ remapped: bool = false,
root_path: string = "",
pub fn format(this: SourceURLFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
if (this.enable_color) {
@@ -516,16 +532,19 @@ pub const ZigStackFrame = extern struct {
}
var source_slice = this.source_url.slice();
- if (this.origin) |origin| {
- try writer.writeAll(origin.displayProtocol());
- try writer.writeAll("://");
- try writer.writeAll(origin.displayHostname());
- try writer.writeAll(":");
- try writer.writeAll(origin.port);
- try writer.writeAll("/blob:");
- if (strings.startsWith(source_slice, this.root_path)) {
- source_slice = source_slice[this.root_path.len..];
+ if (!this.remapped) {
+ if (this.origin) |origin| {
+ try writer.writeAll(origin.displayProtocol());
+ try writer.writeAll("://");
+ try writer.writeAll(origin.displayHostname());
+ try writer.writeAll(":");
+ try writer.writeAll(origin.port);
+ try writer.writeAll("/blob:");
+
+ if (strings.startsWith(source_slice, this.root_path)) {
+ source_slice = source_slice[this.root_path.len..];
+ }
}
}
@@ -539,30 +558,32 @@ pub const ZigStackFrame = extern struct {
}
}
- if (this.position.line > -1 and this.position.column_start > -1) {
- if (this.enable_color) {
- try std.fmt.format(
- writer,
- // :
- comptime Output.prettyFmt("
:{d}:{d}", true),
- .{ this.position.line + 1, this.position.column_start },
- );
- } else {
- try std.fmt.format(writer, ":{d}:{d}", .{ this.position.line + 1, this.position.column_start });
- }
- } else if (this.position.line > -1) {
- if (this.enable_color) {
- try std.fmt.format(
- writer,
- comptime Output.prettyFmt(":{d}", true),
- .{
+ if (!this.exclude_line_column) {
+ if (this.position.line > -1 and this.position.column_start > -1) {
+ if (this.enable_color) {
+ try std.fmt.format(
+ writer,
+ // :
+ comptime Output.prettyFmt(":{d}:{d}", true),
+ .{ this.position.line + 1, this.position.column_start },
+ );
+ } else {
+ try std.fmt.format(writer, ":{d}:{d}", .{ this.position.line + 1, this.position.column_start });
+ }
+ } else if (this.position.line > -1) {
+ if (this.enable_color) {
+ try std.fmt.format(
+ writer,
+ comptime Output.prettyFmt(":{d}", true),
+ .{
+ this.position.line + 1,
+ },
+ );
+ } else {
+ try std.fmt.format(writer, ":{d}", .{
this.position.line + 1,
- },
- );
- } else {
- try std.fmt.format(writer, ":{d}", .{
- this.position.line + 1,
- });
+ });
+ }
}
}
}
@@ -621,8 +642,16 @@ pub const ZigStackFrame = extern struct {
return NameFormatter{ .function_name = this.function_name, .code_type = this.code_type, .enable_color = enable_color };
}
- pub fn sourceURLFormatter(this: *const ZigStackFrame, root_path: string, origin: ?*const ZigURL, comptime enable_color: bool) SourceURLFormatter {
- return SourceURLFormatter{ .source_url = this.source_url, .origin = origin, .root_path = root_path, .position = this.position, .enable_color = enable_color };
+ pub fn sourceURLFormatter(this: *const ZigStackFrame, root_path: string, origin: ?*const ZigURL, exclude_line_column: bool, comptime enable_color: bool) SourceURLFormatter {
+ return SourceURLFormatter{
+ .source_url = this.source_url,
+ .exclude_line_column = exclude_line_column,
+ .origin = origin,
+ .root_path = root_path,
+ .position = this.position,
+ .enable_color = enable_color,
+ .remapped = this.remapped,
+ };
}
};
@@ -670,6 +699,8 @@ pub const ZigException = extern struct {
exception: ?*anyopaque,
+ remapped: bool = false,
+
pub const shim = Shimmer("Zig", "Exception", @This());
pub const name = "ZigException";
pub const namespace = shim.namespace;
@@ -736,7 +767,12 @@ pub const ZigException = extern struct {
return shim.cppFn("fromException", .{exception});
}
- pub fn addToErrorList(this: *ZigException, error_list: *std.ArrayList(Api.JsException)) !void {
+ pub fn addToErrorList(
+ this: *ZigException,
+ error_list: *std.ArrayList(Api.JsException),
+ root_path: string,
+ origin: ?*const ZigURL,
+ ) !void {
const _name: string = @field(this, "name").slice();
const message: string = @field(this, "message").slice();
@@ -757,7 +793,7 @@ pub const ZigException = extern struct {
}
if (this.stack.frames_len > 0) {
- api_exception.stack = try this.stack.toAPI(error_list.allocator);
+ api_exception.stack = try this.stack.toAPI(error_list.allocator, root_path, origin);
is_empty = false;
}
diff --git a/src/javascript/jsc/bindings/headers-handwritten.h b/src/javascript/jsc/bindings/headers-handwritten.h
index 56d5c07737..8e2bafc760 100644
--- a/src/javascript/jsc/bindings/headers-handwritten.h
+++ b/src/javascript/jsc/bindings/headers-handwritten.h
@@ -68,6 +68,7 @@ typedef struct ZigStackFrame {
ZigString source_url;
ZigStackFramePosition position;
ZigStackFrameCode code_type;
+ bool remapped;
} ZigStackFrame;
typedef struct ZigStackTrace {
@@ -90,6 +91,7 @@ typedef struct ZigException {
ZigString message;
ZigStackTrace stack;
void* exception;
+ bool remapped;
} ZigException;
typedef uint8_t JSErrorCode;
diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig
index 36c8174add..622364a6cf 100644
--- a/src/javascript/jsc/javascript.zig
+++ b/src/javascript/jsc/javascript.zig
@@ -2921,7 +2921,7 @@ pub const VirtualMachine = struct {
}
if (exception_list) |list| {
- zig_exception.addToErrorList(list) catch {};
+ zig_exception.addToErrorList(list, this.bundler.fs.top_level_dir, &this.origin) catch {};
}
}
}
@@ -3067,6 +3067,7 @@ pub const VirtualMachine = struct {
frame.sourceURLFormatter(
dir,
origin,
+ false,
allow_ansi_colors,
),
},
@@ -3081,6 +3082,7 @@ pub const VirtualMachine = struct {
frame.sourceURLFormatter(
dir,
origin,
+ false,
allow_ansi_colors,
),
},
@@ -3095,10 +3097,13 @@ pub const VirtualMachine = struct {
exception: *ZigException,
error_instance: JSValue,
exception_list: ?*ExceptionList,
- ) !void {
+ ) void {
error_instance.toZigException(this.global, exception);
- if (exception_list) |list| {
- try exception.addToErrorList(list);
+ // defer this so that it copies correctly
+ defer {
+ if (exception_list) |list| {
+ exception.addToErrorList(list, this.bundler.fs.top_level_dir, &this.origin) catch unreachable;
+ }
}
var frames: []JSC.ZigStackFrame = exception.stack.frames_ptr[0..exception.stack.frames_len];
@@ -3108,14 +3113,22 @@ pub const VirtualMachine = struct {
if (this.source_mappings.resolveMapping(
top.source_url.slice(),
@maximum(top.position.line, 0),
- @maximum(top.position.column_stop, 0),
+ @maximum(top.position.column_start, 0),
)) |mapping| {
var log = logger.Log.init(default_allocator);
var original_source = _fetch(this.global, top.source_url.slice(), "", &log, true) catch return;
const code = original_source.source_code.slice();
top.position.line = mapping.original.lines;
+ top.position.line_start = mapping.original.lines;
+ top.position.line_stop = mapping.original.lines + 1;
top.position.column_start = mapping.original.columns;
+ top.position.column_stop = mapping.original.columns + 1;
+ exception.remapped = true;
+ top.remapped = true;
+ // This expression range is no longer accurate
top.position.expression_start = mapping.original.columns;
+ top.position.expression_stop = mapping.original.columns + 1;
+
if (strings.getLinesInText(
code,
@intCast(u32, top.position.line),
@@ -3133,6 +3146,13 @@ pub const VirtualMachine = struct {
}
exception.stack.source_lines_len = @intCast(u8, lines_.len);
+
+ top.position.column_stop = @intCast(i32, source_lines[lines_.len - 1].len);
+ top.position.line_stop = top.position.column_stop;
+
+ // This expression range is no longer accurate
+ top.position.expression_start = mapping.original.columns;
+ top.position.expression_stop = top.position.column_stop;
}
}
@@ -3145,6 +3165,7 @@ pub const VirtualMachine = struct {
@maximum(frame.position.column_start, 0),
)) |mapping| {
frame.position.line = mapping.original.lines;
+ frame.remapped = true;
frame.position.column_start = mapping.original.columns;
}
}
@@ -3154,7 +3175,7 @@ pub const VirtualMachine = struct {
pub fn printErrorInstance(this: *VirtualMachine, error_instance: JSValue, exception_list: ?*ExceptionList, comptime Writer: type, writer: Writer, comptime allow_ansi_color: bool) !void {
var exception_holder = ZigException.Holder.init();
var exception = exception_holder.zigException();
- try this.remapZigException(exception, error_instance, exception_list);
+ this.remapZigException(exception, error_instance, exception_list);
this.had_errors = true;
var line_numbers = exception.stack.source_lines_numbers[0..exception.stack.source_lines_len];
@@ -3217,7 +3238,7 @@ pub const VirtualMachine = struct {
writer.writeByteNTimes(' ', pad) catch unreachable;
const top = exception.stack.frames()[0];
var remainder = std.mem.trim(u8, source.text, "\n");
- if (@intCast(usize, top.position.column_stop) > remainder.len) {
+ if (@intCast(usize, top.position.column_stop) > remainder.len or (top.position.column_stop - top.position.column_start) <= 0) {
writer.print(
comptime Output.prettyFmt(
"{d} | {s}\n",
diff --git a/src/javascript/jsc/webcore/encoding.zig b/src/javascript/jsc/webcore/encoding.zig
index 79c3125f71..8d9486907f 100644
--- a/src/javascript/jsc/webcore/encoding.zig
+++ b/src/javascript/jsc/webcore/encoding.zig
@@ -486,8 +486,7 @@ pub const TextDecoder = struct {
}
}
}
-
- return str.toValueGC(ctx.ptr()).asObjectRef();
+ return str.toValue(ctx.ptr()).asObjectRef();
},
EncodingLabel.@"UTF-16LE" => {
diff --git a/src/js_ast.zig b/src/js_ast.zig
index 331f9a3c7d..902028de38 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -631,6 +631,7 @@ pub const G = struct {
class_name: ?LocRef = null,
extends: ?ExprNodeIndex = null,
body_loc: logger.Loc = logger.Loc.Empty,
+ close_brace_loc: logger.Loc = logger.Loc.Empty,
properties: []Property = &([_]Property{}),
};
@@ -1058,6 +1059,7 @@ pub const E = struct {
is_single_line: bool = false,
is_parenthesized: bool = false,
was_originally_macro: bool = false,
+ close_bracket_loc: logger.Loc = logger.Loc.Empty,
pub fn push(this: *Array, allocator: std.mem.Allocator, item: Expr) !void {
try this.items.push(allocator, item);
@@ -1113,6 +1115,8 @@ pub const E = struct {
// True if there is a comment containing "@__PURE__" or "#__PURE__" preceding
// this call expression. See the comment inside ECall for more details.
can_be_unwrapped_if_unused: bool = false,
+
+ close_parens_loc: logger.Loc,
};
pub const NewTarget = struct {
range: logger.Range,
@@ -1125,6 +1129,7 @@ pub const E = struct {
args: ExprNodeList = ExprNodeList{},
optional_chain: ?OptionalChain = null,
is_direct_eval: bool = false,
+ close_paren_loc: logger.Loc = logger.Loc.Empty,
// True if there is a comment containing "@__PURE__" or "#__PURE__" preceding
// this call expression. This is an annotation used for tree shaking, and
@@ -1292,6 +1297,8 @@ pub const E = struct {
flags: Flags.JSXElement = Flags.JSXElement{},
+ close_tag_loc: logger.Loc = logger.Loc.Empty,
+
pub const SpecialProp = enum {
__self, // old react transform used this as a prop
__source,
@@ -1360,6 +1367,7 @@ pub const E = struct {
is_single_line: bool = false,
is_parenthesized: bool = false,
was_originally_macro: bool = false,
+ close_brace_loc: logger.Loc = logger.Loc.Empty,
pub const Rope = struct {
head: Expr,
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index 603bef93e3..1945f9fa4c 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -46,7 +46,10 @@ fn _disabledAssert(_: bool) void {
}
const assert = if (Environment.allow_assert) std.debug.assert else _disabledAssert;
-
+const ExprListLoc = struct {
+ list: ExprNodeList,
+ loc: logger.Loc,
+};
pub const LocRef = js_ast.LocRef;
pub const S = js_ast.S;
pub const B = js_ast.B;
@@ -174,6 +177,87 @@ const TransposeState = struct {
loc: logger.Loc,
};
+const JSXTag = struct {
+ pub const TagType = enum { fragment, tag };
+ pub const Data = union(TagType) {
+ fragment: u8,
+ tag: Expr,
+
+ pub fn asExpr(d: *const Data) ?ExprNodeIndex {
+ switch (d.*) {
+ .tag => |tag| {
+ return tag;
+ },
+ else => {
+ return null;
+ },
+ }
+ }
+ };
+ data: Data,
+ range: logger.Range,
+ name: string = "",
+
+ pub fn parse(comptime P: type, p: *P) anyerror!JSXTag {
+ const loc = p.lexer.loc();
+
+ // A missing tag is a fragment
+ if (p.lexer.token == .t_greater_than) {
+ return JSXTag{
+ .range = logger.Range{ .loc = loc, .len = 0 },
+ .data = Data{ .fragment = 1 },
+ .name = "",
+ };
+ }
+
+ // The tag is an identifier
+ var name = p.lexer.identifier;
+ var tag_range = p.lexer.range();
+ try p.lexer.expectInsideJSXElement(.t_identifier);
+
+ // Certain identifiers are strings
+ // = 'a' and name[0] <= 'z')) {
+ return JSXTag{
+ .data = Data{ .tag = p.e(E.String{
+ .utf8 = name,
+ }, loc) },
+ .range = tag_range,
+ };
+ }
+
+ // Otherwise, this is an identifier
+ //