Skip to content
This repository was archived by the owner on Jan 11, 2023. It is now read-only.

[FEAT] Map stacktraces #773

Closed
wants to merge 1 commit into from
Closed
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
52 changes: 49 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"html-minifier": "^4.0.0",
"http-link-header": "^1.0.2",
"shimport": "^1.0.1",
"source-map": "^0.7.3",
"sourcemap-codec": "^1.4.6",
"string-hash": "^1.1.3"
},
Expand Down
5 changes: 5 additions & 0 deletions runtime/src/server/middleware/get_page_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import devalue from 'devalue';
import fetch from 'node-fetch';
import URL from 'url';
import { Manifest, Page, Req, Res } from './types';
import { sourcemapStacktrace } from './sourcemap_stacktrace';
import { build_dir, dev, src_dir } from '@sapper/internal/manifest-server';
import App from '@sapper/internal/App.svelte';

Expand Down Expand Up @@ -223,6 +224,10 @@ export function get_page_handler(
l++;
});

if (error instanceof Error && error.stack) {
error.stack = sourcemapStacktrace(error.stack);
}

const props = {
stores: {
page: {
Expand Down
95 changes: 95 additions & 0 deletions runtime/src/server/middleware/sourcemap_stacktrace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import fs from 'fs';
import path from 'path';
import { SourceMapConsumer, RawSourceMap } from 'source-map';

function retrieveSourceMapURL(contents: string) {
const reversed = contents
.split('\n')
.reverse()
.join('\n');

const match = /\/[/*]#[ \t]+sourceMappingURL=([^\s'"]+?)(?:[ \t]+|$)/gm.exec(reversed);
if (match) return match[1];

return undefined;
}

const fileCache = new Map<string, string>();

function getFileContents(path: string) {
if (fileCache.has(path)) {
return fileCache.get(path);
}
if (fs.existsSync(path)) {
try {
const data = fs.readFileSync(path, 'utf8');
fileCache.set(path, data);
return data;
} catch {
return undefined;
}
}
}

function sourcemapStacktrace(stack: string) {
const replaceFn = (line: string) =>
line.replace(
/^ {4}at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?)\)?/,
(input, varName, filePath, line, column) => {
if (!filePath) return input;

const contents = getFileContents(filePath);
if (!contents) return input;

const srcMapPathOrBase64 = retrieveSourceMapURL(contents);
if (!srcMapPathOrBase64) return input;

let dir = path.dirname(filePath);
let srcMapData: string;

if (/^data:application\/json[^,]+base64,/.test(srcMapPathOrBase64)) {
const rawData = srcMapPathOrBase64.slice(srcMapPathOrBase64.indexOf(',') + 1);
try {
srcMapData = Buffer.from(rawData, 'base64').toString();
} catch {
return input;
}
} else {
const absSrcMapPath = path.resolve(dir, srcMapPathOrBase64);
const data = getFileContents(absSrcMapPath);
if (!data) return input;

srcMapData = data;
dir = path.dirname(absSrcMapPath);
}

let rawSourceMap: RawSourceMap;
try {
rawSourceMap = JSON.parse(srcMapData);
} catch {
return input;
}

const consumer = new SourceMapConsumer(rawSourceMap);
const pos = consumer.originalPositionFor({
Copy link
Contributor

@habibrosyad habibrosyad Aug 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, with the latest version of source-map used here (0.7.3), consumer returns promise, therefore call to originalPositionFor will fail 😕

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, it should be version 0.6.1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm planing to continue the works that you've done if you don't mind, especially adding tests for this...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at all, I mentioned in one of my previous comments that I've been quite busy lately.

line: Number(line),
column: Number(column)
});
if (!pos.source) return input;

const absSrcPath = path.resolve(dir, pos.source);
const urlPart = `${absSrcPath}:${pos.line || 0}:${pos.column || 0}`;

if (!varName) return ` at ${urlPart}`;
return ` at ${varName} (${urlPart})`;
}
);

fileCache.clear();
return stack
.split('\n')
.map(replaceFn)
.join('\n');
}

export { sourcemapStacktrace };