@stacktrace-lite/core
Version:
> Parse, filter, and format JavaScript stack traces with plugins for browsers and Node.js.
79 lines (78 loc) • 3.1 kB
JavaScript
import { bold, dim, yellow, gray } from 'colorette';
/**
* Format an array of {@link StackFrame} entries into a string based on the desired output mode.
*
* @param frames - Parsed stack frames to format.
* @param mode - Output style:
* - `"cli"` – styled for terminal (default)
* - `"html"` – safe for rendering into web UIs
* - `"json"` – structured JSON
* - `"raw"` – concatenated original lines (`frame.raw`)
* @returns A formatted string representing the stack trace.
*
* @remarks
* **Terminal styling (`"cli"`)**
* Uses the [colorette](https://www.npmjs.com/package/colorette) library to render ANSI styling:
* - `bold(...)` for function names
* - `dim('at')` for the “at” constant
* - `yellow(...)` for file names
* - `gray(...)` for line/column position
* Colorette is chosen for its speed and zero dependencies—efficient and NO_COLOR friendly.:contentReference[oaicite:1]{index=1}
*
* **HTML mode (`"html"`)**
* Safely escapes HTML entities and assembles a `<pre>` block with semantic `<span>` tags (`function`, `at`, `file`, `pos`)—great for embedding in browser error overlays or dev UI.
*
* **JSON mode (`"json"`)**
* Outputs the full frames array as pretty JSON (`null, 2` spacing), ideal for logging or backend ingestion.
*
* **Raw mode (`"raw"`)**
* Joins the `raw` values of frames, preserving their original formatting without transformation.
*
* @example
* ```ts
* const trace = formatStack(frames, 'cli');
* console.log(trace);
*
* const htmlTrace = formatStack(frames, 'html');
* document.body.innerHTML = htmlTrace;
*
* const jsonTrace = formatStack(frames, 'json');
* sendToServer(jsonTrace);
* ```
*
* @throws Error when an unsupported mode string is provided.
*/
export function formatStack(frames, mode = 'cli') {
if (mode === 'cli') {
return frames
.map((f) => bold(f.functionName ?? '<anonymous>') +
' ' +
dim('at') +
' ' +
yellow(f.fileName ?? '') +
gray(f.lineNumber != null && f.columnNumber != null
? `:${f.lineNumber}:${f.columnNumber}`
: ''))
.join('\n');
}
if (mode === 'html') {
const esc = (s) => s.replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
return ('<pre class="stacktrace-lite">\n' +
frames
.map((f) => `<span class="function">${esc(f.functionName ?? '<anonymous>')}</span> ` +
`<span class="at">at</span> ` +
`<span class="file">${esc(f.fileName ?? '')}</span>` +
(f.lineNumber != null && f.columnNumber != null
? `<span class="pos">:${f.lineNumber}:${f.columnNumber}</span>`
: ''))
.join('<br>\n') +
'\n</pre>');
}
if (mode === 'json') {
return JSON.stringify(frames, null, 2);
}
if (mode === 'raw') {
return frames.map((f) => f.raw).join('\n');
}
throw new Error(`Unknown format mode: ${mode}`);
}