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

Commit 2e5a510

Browse files
committed
sourcemap stacktraces
1 parent f3e9fc4 commit 2e5a510

File tree

4 files changed

+150
-3
lines changed

4 files changed

+150
-3
lines changed

package-lock.json

+49-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"html-minifier": "^4.0.0",
2222
"http-link-header": "^1.0.2",
2323
"shimport": "^1.0.1",
24+
"source-map": "^0.7.3",
2425
"sourcemap-codec": "^1.4.6",
2526
"string-hash": "^1.1.3"
2627
},

runtime/src/server/middleware/get_page_handler.ts

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import devalue from 'devalue';
66
import fetch from 'node-fetch';
77
import URL from 'url';
88
import { Manifest, Page, Req, Res } from './types';
9+
import { sourcemapStacktrace } from './sourcemap_stacktrace';
910
import { build_dir, dev, src_dir } from '@sapper/internal/manifest-server';
1011
import App from '@sapper/internal/App.svelte';
1112

@@ -223,6 +224,10 @@ export function get_page_handler(
223224
l++;
224225
});
225226

227+
if (error instanceof Error && error.stack) {
228+
error.stack = sourcemapStacktrace(error.stack);
229+
}
230+
226231
const props = {
227232
stores: {
228233
page: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import { SourceMapConsumer, RawSourceMap } from 'source-map';
4+
5+
function retrieveSourceMapURL(contents: string) {
6+
const reversed = contents
7+
.split('\n')
8+
.reverse()
9+
.join('\n');
10+
11+
const match = /\/[/*]#[ \t]+sourceMappingURL=([^\s'"]+?)(?:[ \t]+|$)/gm.exec(reversed);
12+
if (match) return match[1];
13+
14+
return undefined;
15+
}
16+
17+
const fileCache = new Map<string, string>();
18+
19+
function getFileContents(path: string) {
20+
if (fileCache.has(path)) {
21+
return fileCache.get(path);
22+
}
23+
if (fs.existsSync(path)) {
24+
try {
25+
const data = fs.readFileSync(path, 'utf8');
26+
fileCache.set(path, data);
27+
return data;
28+
} catch {
29+
return undefined;
30+
}
31+
}
32+
}
33+
34+
function sourcemapStacktrace(stack: string) {
35+
const replaceFn = (line: string) =>
36+
line.replace(
37+
/^ {4}at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?)\)?/,
38+
(input, varName, filePath, line, column) => {
39+
if (!filePath) return input;
40+
41+
const contents = getFileContents(filePath);
42+
if (!contents) return input;
43+
44+
const srcMapPathOrBase64 = retrieveSourceMapURL(contents);
45+
if (!srcMapPathOrBase64) return input;
46+
47+
let dir = path.dirname(filePath);
48+
let srcMapData: string;
49+
50+
if (/^data:application\/json[^,]+base64,/.test(srcMapPathOrBase64)) {
51+
const rawData = srcMapPathOrBase64.slice(srcMapPathOrBase64.indexOf(',') + 1);
52+
try {
53+
srcMapData = Buffer.from(rawData, 'base64').toString();
54+
} catch {
55+
return input;
56+
}
57+
} else {
58+
const absSrcMapPath = path.resolve(dir, srcMapPathOrBase64);
59+
const data = getFileContents(absSrcMapPath);
60+
if (!data) return input;
61+
62+
srcMapData = data;
63+
dir = path.dirname(absSrcMapPath);
64+
}
65+
66+
let rawSourceMap: RawSourceMap;
67+
try {
68+
rawSourceMap = JSON.parse(srcMapData);
69+
} catch {
70+
return input;
71+
}
72+
73+
const consumer = new SourceMapConsumer(rawSourceMap);
74+
const pos = consumer.originalPositionFor({
75+
line: Number(line),
76+
column: Number(column)
77+
});
78+
if (!pos.source) return input;
79+
80+
const absSrcPath = path.resolve(dir, pos.source);
81+
const urlPart = `${absSrcPath}:${pos.line || 0}:${pos.column || 0}`;
82+
83+
if (!varName) return ` at ${urlPart}`;
84+
return ` at ${varName} (${urlPart})`;
85+
}
86+
);
87+
88+
fileCache.clear();
89+
return stack
90+
.split('\n')
91+
.map(replaceFn)
92+
.join('\n');
93+
}
94+
95+
export { sourcemapStacktrace };

0 commit comments

Comments
 (0)