Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dev-toolbar-error-stack-parser-es.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/start": patch
---

Parse stack traces in the dev toolbar's error overlay with `error-stack-parser-es/lite` instead of `error-stack-parser`. The lite entry point is a much smaller, ESM-only parser that returns plain frame objects rather than class instances, and it no longer pulls in the `stackframe` package. Errors that carry no stack are now handled as an empty frame list instead of throwing.
7 changes: 7 additions & 0 deletions .changeset/dev-toolbar-redesign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@solidjs/start": patch
---

Redesign the dev toolbar. The toolbar and its panels share a common set of design tokens, giving them a consistent dark palette, translucent surfaces, and elevation. The server function inspector is now a master-detail split with a persistent call list beside the request/response pane, and the error overlay places the stack frame list beside a code preview that fills the panel. Headers, form data, and URL search params render as aligned key-value tables, the hex viewer gains an offset gutter with the ASCII column aligned per row, and blobs are shown as file cards with their type and size. The seroval body inspector is now an expandable tree with collapsed previews, syntax-colored values, cycle detection, and live promise and stream state, replacing the previous column-based drill-down.

Along with the redesign, the toolbar only starts a drag from the toolbar itself rather than from its panels, unhandled promise rejections are captured by the error overlay, the code preview shows more surrounding lines, and a stack frame whose source cannot be loaded now stays listed and reports that its source is unavailable instead of silently rendering nothing.
5 changes: 5 additions & 0 deletions .changeset/dev-toolbar-trace-mapping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/start": patch
---

Use `@jridgewell/trace-mapping` instead of `source-map-js` to resolve original sources in the dev toolbar's error overlay. It decodes mappings lazily, so only the positions actually inspected are resolved, it is significantly smaller in the browser bundle, and it understands indexed source maps.
6 changes: 3 additions & 3 deletions packages/start/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@
"@babel/core": "^7.29.7",
"@babel/traverse": "^7.29.7",
"@babel/types": "^7.29.7",
"@jridgewell/trace-mapping": "^0.3.31",
"@solidjs/meta": "^0.29.4",
"@types/babel__traverse": "^7.28.0",
"@types/micromatch": "^4.0.10",
"cookie-es": "^3.1.1",
"defu": "^6.1.7",
"error-stack-parser": "^2.1.4",
"error-stack-parser-es": "^2.0.1",
"fast-glob": "^3.3.3",
"h3": "^2.0.1-rc.26",
"html-to-image": "^1.11.13",
Expand All @@ -76,9 +77,8 @@
"seroval-plugins": "^1.6.0",
"shiki": "^4.3.1",
"solid-js": "^1.9.15",
"source-map-js": "^1.2.1",
"srvx": "^0.12.4",
"terracotta": "^1.1.1",
"terracotta": "^1.2.4",
"vite-plugin-solid": "^2.11.13"
},
"devDependencies": {
Expand Down
52 changes: 0 additions & 52 deletions packages/start/src/config/dev-toolbar-deps.spec.ts

This file was deleted.

8 changes: 0 additions & 8 deletions packages/start/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,6 @@ export interface SolidStartOptions {
};
}

const DEV_TOOLBAR_COMMONJS_DEPENDENCIES = [
"@solidjs/start > source-map-js",
"@solidjs/start > error-stack-parser",
];

const absolute = (path: string, root: string) =>
path ? (isAbsolute(path) ? path : join(root, path)) : path;

Expand Down Expand Up @@ -296,9 +291,6 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
environments: {
[VITE_ENVIRONMENTS.client]: {
consumer: "client",
...(start.devOverlay
? { optimizeDeps: { include: DEV_TOOLBAR_COMMONJS_DEPENDENCIES } }
: {}),
build: {
write: true,
manifest: true,
Expand Down
15 changes: 11 additions & 4 deletions packages/start/src/shared/dev-toolbar/error-viewer/CodeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface CodeViewProps {
line: number;
}

const RANGE = 8;
const RANGE = 15;

export function CodeView(props: CodeViewProps): JSX.Element | null {
const lines = () =>
Expand All @@ -51,9 +51,16 @@ export function CodeView(props: CodeViewProps): JSX.Element | null {
async value => {
const highlighter = await loadHighlighter();
const fileExtension = props.fileName.split(/[#?]/)[0]!.split(".").pop()?.trim();
let lang = fileExtension as BuiltinLanguage;
if (fileExtension === "mjs" || fileExtension === "cjs") {
lang = "js";
// Only these grammars are loaded — anything else would make shiki
// throw. Fall back to plain JS highlighting for unknown sources.
let lang: BuiltinLanguage = "js";
if (
fileExtension === "jsx" ||
fileExtension === "ts" ||
fileExtension === "tsx" ||
fileExtension === "js"
) {
lang = fileExtension;
}
return highlighter.codeToHtml(value, {
theme: "dark-plus",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { originalPositionFor, sourceContentFor } from "@jridgewell/trace-mapping";
import type { StackFrameLite } from "error-stack-parser-es/lite";
import { type Accessor, createMemo, createResource } from "solid-js";
import getSourceMap from "./get-source-map.ts";

Expand Down Expand Up @@ -30,31 +32,39 @@ function getActualFileSource(path: string): string {
return path;
}

export function createStackFrame(stackframe: StackFrame, isCompiled: () => boolean) {
export function createStackFrame(stackframe: StackFrameLite, isCompiled: () => boolean) {
const [data] = createResource(
() => ({
fileName: stackframe.fileName,
line: stackframe.lineNumber,
column: stackframe.columnNumber,
functionName: stackframe.functionName,
fileName: stackframe.file,
line: stackframe.line,
column: stackframe.col,
functionName: stackframe.function,
}),
async source => {
if (!source.fileName) {
return null;
}
const url = getActualFileSource(source.fileName);
const response = await fetch(url);
if (!response.ok) {
// Sources can be unreachable — node internals, extension scripts,
// files outside the dev server's allowlist. Treat any failure as
// "no source" instead of throwing into the error boundary.
try {
const url = getActualFileSource(source.fileName);
const response = await fetch(url);
if (!response.ok) {
return null;
}
const content = await response.text();
const sourceMap = await getSourceMap(url, content);
return {
source,
content,
sourceMap,
isServer: isServerSource(source.fileName),
};
} catch (error) {
console.warn("[start dev toolbar] failed to load source for stack frame", error);
return null;
}
const content = await response.text();
const sourceMap = await getSourceMap(url, content);
return {
source,
content,
sourceMap,
isServer: isServerSource(source.fileName),
};
},
);

Expand All @@ -69,9 +79,10 @@ export function createStackFrame(stackframe: StackFrame, isCompiled: () => boole
if (isServer) {
// The position is already original; only the original content needs
// to be pulled out of the source map.
const originalContent = sourceMap.sources.length
? sourceMap.sourceContentFor(sourceMap.sources[0]!, true)
: null;
const originalContent =
sourceMap.sources.length && sourceMap.sources[0] != null
? sourceContentFor(sourceMap, sourceMap.sources[0])
: null;
if (originalContent) {
return {
source: source.fileName,
Expand All @@ -82,14 +93,14 @@ export function createStackFrame(stackframe: StackFrame, isCompiled: () => boole
} as StackFrameSource;
}
} else {
const result = sourceMap.originalPositionFor({
const result = originalPositionFor(sourceMap, {
line: source.line,
column: source.column,
});
if (result.source) {
return {
...result,
content: sourceMap.sourceContentFor(result.source, true),
content: sourceContentFor(sourceMap, result.source),
} as StackFrameSource;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { type RawSourceMap, SourceMapConsumer } from "source-map-js";
import { AnyMap, type SourceMapInput, type TraceMap } from "@jridgewell/trace-mapping";

const INLINE_SOURCEMAP_REGEX = /^data:application\/json[^,]+base64,/;
const SOURCEMAP_REGEX =
/(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^*]+?)[ \t]*(?:\*\/)[ \t]*$)/;

export default async function getSourceMap(
url: string,
content: string,
): Promise<SourceMapConsumer | null> {
export default async function getSourceMap(url: string, content: string): Promise<TraceMap | null> {
const lines = content.split("\n");
let sourceMapUrl: string | undefined;
for (let i = lines.length - 1; i >= 0 && !sourceMapUrl; i--) {
Expand All @@ -28,6 +25,7 @@ export default async function getSourceMap(
sourceMapUrl = parsedURL.join("/");
}
const response = await fetch(sourceMapUrl);
const rawSourceMap: RawSourceMap = await response.json();
return new SourceMapConsumer(rawSourceMap);
const rawSourceMap: SourceMapInput = await response.json();
// AnyMap also handles indexed ("sections") source maps
return new AnyMap(rawSourceMap);
}
Loading
Loading