app/@overflow/core/source-map-support.ts
crusader ea0af6c32b ing
2018-10-02 10:32:32 +09:00

139 lines
4.3 KiB
TypeScript

import * as Path from 'path';
import * as Fs from 'fs';
import * as sourceMapSupport from 'source-map-support';
/**
* This array tells the source map logic which files that we can expect to
* be able to resolve a source map for and they should reflect the chunks
* entry names from our webpack config.
*
* Note that we explicitly don't enable source maps for the crash process
* since it's possible that the error which caused us to spawn the crash
* process was related to source maps.
*/
const knownFilesWithSourceMap = ['renderer.js', 'main.js'];
function retrieveSourceMap(source: string) {
const fileUriToPath: (uri: string) => string = require('file-uri-to-path');
// This is a happy path in case we know for certain that we won't be
// able to resolve a source map for the given location.
if (!knownFilesWithSourceMap.some(file => source.endsWith(file))) {
return null;
}
// We get a file uri when we're inside a renderer, convert to a path
if (source.startsWith('file://')) {
source = fileUriToPath(source);
}
// We store our source maps right next to the bundle
const path = `${source}.map`;
if (__DEV__ && path.startsWith('http://')) {
try {
const xhr = new XMLHttpRequest();
xhr.open('GET', path, false);
xhr.send(null);
if (xhr.readyState === 4 && xhr.status === 200) {
return { url: Path.basename(path), map: xhr.responseText };
}
} catch (error) {
return;
}
return;
}
// We don't have an option here, see
// https://github.com/v8/v8/wiki/Stack-Trace-API#customizing-stack-traces
// This happens on-demand when someone accesses the stack
// property on an error object and has to be synchronous :/
// eslint-disable-next-line no-sync
if (!Fs.existsSync(path)) {
return;
}
try {
// eslint-disable-next-line no-sync
const map = Fs.readFileSync(path, 'utf8');
return { url: Path.basename(path), map };
} catch (error) {
return;
}
}
/** A map from errors to their stack frames. */
const stackFrameMap = new WeakMap<Error, ReadonlyArray<any>>();
/**
* The `prepareStackTrace` that comes from the `source-map-support` module.
* We'll use this when the user explicitly wants the stack source mapped.
*/
let prepareStackTraceWithSourceMap: (
error: Error,
frames: ReadonlyArray<any>
) => string;
/**
* Capture the error's stack frames and return a standard, un-source mapped
* stack trace.
*/
function prepareStackTrace(error: Error, frames: ReadonlyArray<any>) {
stackFrameMap.set(error, frames);
// Ideally we'd use the default `Error.prepareStackTrace` here but it's
// undefined so V8 must doing something fancy. Instead we'll do a decent
// impression.
return error + frames.map(frame => `\n at ${frame}`).join('');
}
/** Enable source map support in the current process. */
export function enableSourceMaps() {
sourceMapSupport.install({
environment: 'node',
handleUncaughtExceptions: false,
retrieveSourceMap,
});
const AnyError = Error as any;
// We want to keep `source-map-support`s `prepareStackTrace` around to use
// later, but our cheaper `prepareStackTrace` should be the default.
prepareStackTraceWithSourceMap = AnyError.prepareStackTrace;
AnyError.prepareStackTrace = prepareStackTrace;
}
/**
* Make a copy of the error with a source-mapped stack trace. If it couldn't
* perform the source mapping, it'll use the original error stack.
*/
export function withSourceMappedStack(error: Error): Error {
return {
name: error.name,
message: error.message,
stack: sourceMappedStackTrace(error),
};
}
/** Get the source mapped stack trace for the error. */
function sourceMappedStackTrace(error: Error): string | undefined {
let frames = stackFrameMap.get(error);
if (!frames) {
// At this point there's no guarantee that anyone has actually retrieved the
// stack on this error which means that our custom prepareStackTrace handler
// hasn't run and as a result of that we don't have the native frames stored
// in our weak map. In order to get around that we'll eagerly access the
// stack, forcing our handler to run which should ensure that the native
// frames are stored in our weak map.
(error.stack || '').toString();
frames = stackFrameMap.get(error);
}
if (!frames) {
return error.stack;
}
return prepareStackTraceWithSourceMap(error, frames);
}