diff --git a/Makefile b/Makefile index 6f74d8593f..45d402c99d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ TS_FILES = \ + tsconfig.json \ main.ts \ msg.pb.d.ts \ msg.pb.js \ diff --git a/deno_dir.go b/deno_dir.go index b3a8c78b91..185f7abe05 100644 --- a/deno_dir.go +++ b/deno_dir.go @@ -3,7 +3,6 @@ package main import ( "crypto/md5" "encoding/hex" - "github.com/ry/v8worker2" "io" "io/ioutil" "net/http" @@ -84,13 +83,6 @@ func UserHomeDir() string { return os.Getenv("HOME") } -func loadAsset(w *v8worker2.Worker, path string) { - data, err := Asset(path) - check(err) - err = w.Load(path, string(data)) - check(err) -} - func createDirs() { DenoDir = path.Join(UserHomeDir(), ".deno") CompileDir = path.Join(DenoDir, "compile") diff --git a/main.go b/main.go index 1c2a99720d..9b4b6c120d 100644 --- a/main.go +++ b/main.go @@ -42,6 +42,12 @@ func ResolveModule(moduleSpecifier string, containingFile string) ( return } +func stringAsset(path string) string { + data, err := Asset("dist/" + path) + check(err) + return string(data) +} + func main() { flag.Parse() args := flag.Args() @@ -53,7 +59,11 @@ func main() { createDirs() worker := v8worker2.New(recv) - loadAsset(worker, "dist/main.js") + + main_js := stringAsset("main.js") + check(worker.Load("/main.js", main_js)) + main_map := stringAsset("main.map") + cwd, err := os.Getwd() check(err) @@ -66,6 +76,8 @@ func main() { Cwd: cwd, Argv: args, DebugFlag: *flagDebug, + MainJs: main_js, + MainMap: main_map, }, }, }) diff --git a/main.ts b/main.ts index 356fe329a8..28c1ecdff2 100644 --- a/main.ts +++ b/main.ts @@ -8,10 +8,18 @@ import * as util from "./util"; // Set with the -debug command-line flag. export let debug = false; -function start(cwd: string, argv: string[], debugFlag: boolean): void { +function start( + cwd: string, + argv: string[], + debugFlag: boolean, + mainJs: string, + mainMap: string +): void { debug = debugFlag; util.log("start", { cwd, argv, debugFlag }); + runtime.setup(mainJs, mainMap); + const inputFn = argv[0]; const mod = runtime.resolveModule(inputFn, cwd + "/"); mod.compileAndRun(); @@ -21,7 +29,13 @@ V8Worker2.recv((ab: ArrayBuffer) => { const msg = pb.Msg.decode(new Uint8Array(ab)); switch (msg.payload) { case "start": - start(msg.start.cwd, msg.start.argv, msg.start.debugFlag); + start( + msg.start.cwd, + msg.start.argv, + msg.start.debugFlag, + msg.start.mainJs, + msg.start.mainMap + ); break; case "timerReady": timers.timerReady(msg.timerReady.id, msg.timerReady.done); diff --git a/msg.proto b/msg.proto index dba8cab942..821d00a20c 100644 --- a/msg.proto +++ b/msg.proto @@ -18,6 +18,8 @@ message StartMsg { string cwd = 1; repeated string argv = 2; bool debug_flag = 3; + string main_js = 4; // The contents of dist/main.js + string main_map = 5; // The contents of dist/main.map } message SourceCodeFetchMsg { diff --git a/runtime.ts b/runtime.ts index 9ca3b4944d..57b556c216 100644 --- a/runtime.ts +++ b/runtime.ts @@ -19,16 +19,24 @@ const EOL = "\n"; type AmdFactory = (...args: any[]) => undefined | object; type AmdDefine = (deps: string[], factory: AmdFactory) => void; -sourceMaps.install({ - installPrepareStackTrace: true, - getGeneratedContents: (filename: string): string => { - util.log("getGeneratedContents", filename); - if (filename === "dist/main.js") { - return null; +export function setup(mainJs: string, mainMap: string): void { + sourceMaps.install({ + installPrepareStackTrace: true, + getGeneratedContents: (filename: string): string => { + if (filename === "/main.js") { + return mainJs; + } else if (filename === "/main.map") { + return mainMap; + } else { + const mod = FileModule.load(filename); + if (!mod) { + console.error("getGeneratedContents cannot find", filename); + } + return mod.outputCode; + } } - return FileModule.load(filename).outputCode; - } -}); + }); +} // This class represents a module. We call it FileModule to make it explicit // that each module represents a single file. diff --git a/testdata/008_stack_trace.ts b/testdata/008_stack_trace.ts new file mode 100644 index 0000000000..6aa0fcc3b6 --- /dev/null +++ b/testdata/008_stack_trace.ts @@ -0,0 +1,7 @@ +import { throwsError } from "./subdir/mod1.ts"; + +function foo() { + throwsError(); +} + +foo(); diff --git a/testdata/subdir/mod1.ts b/testdata/subdir/mod1.ts index c09755a3b0..393535588a 100644 --- a/testdata/subdir/mod1.ts +++ b/testdata/subdir/mod1.ts @@ -11,3 +11,7 @@ export function returnsFoo2(): string { export function printHello3(): void { printHello2(); } + +export function throwsError(): void { + throw Error("exception from mod1"); +} diff --git a/tsconfig.json b/tsconfig.json index d598b8ce04..4de82936ce 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "allowJs": true, "module": "commonjs", "noImplicitAny": true, - "sourceMap": true, + "baseUrl": ".", "removeComments": true, "preserveConstEnums": true, "declaration": true, diff --git a/v8_source_maps.ts b/v8_source_maps.ts index d80efb9210..5f5b1c7796 100644 --- a/v8_source_maps.ts +++ b/v8_source_maps.ts @@ -1,6 +1,8 @@ import { SourceMapConsumer, MappedPosition } from "source-map"; import * as base64 from "base64-js"; +const consumers = new Map(); + interface Options { // A callback the returns generated file contents. getGeneratedContents: GetGeneratedContentsCallback; @@ -60,20 +62,17 @@ export function wrapCallSite(frame: CallSite): CallSite { // passed to eval() ending in "//# sourceURL=..." will return the source file // from getScriptNameOrSourceURL() instead const source = frame.getFileName() || frame.getScriptNameOrSourceURL(); + if (source) { const line = frame.getLineNumber(); const column = frame.getColumnNumber() - 1; - - const position = mapSourcePosition({ - source, - line, - column - }); + const position = mapSourcePosition({ source, line, column }); frame = cloneCallSite(frame); frame.getFileName = () => position.source; frame.getLineNumber = () => position.line; frame.getColumnNumber = () => Number(position.column) + 1; frame.getScriptNameOrSourceURL = () => position.source; + frame.toString = () => CallSiteToString(frame); return frame; } @@ -96,7 +95,7 @@ function cloneCallSite(frame: CallSite): CallSite { const frame_ = frame as any; const props = Object.getOwnPropertyNames(Object.getPrototypeOf(frame)); props.forEach(name => { - obj[name] = /^(?:is|get|toString)/.test(name) + obj[name] = /^(?:is|get)/.test(name) ? () => frame_[name].call(frame) : frame_[name]; }); @@ -104,6 +103,79 @@ function cloneCallSite(frame: CallSite): CallSite { // tslint:enable:no-any } +// Taken from source-map-support, original copied from V8's messages.js +// MIT License. Copyright (c) 2014 Evan Wallace +function CallSiteToString(frame: CallSite): string { + let fileName; + let fileLocation = ""; + if (frame.isNative()) { + fileLocation = "native"; + } else { + fileName = frame.getScriptNameOrSourceURL(); + if (!fileName && frame.isEval()) { + fileLocation = frame.getEvalOrigin(); + fileLocation += ", "; // Expecting source position to follow. + } + + if (fileName) { + fileLocation += fileName; + } else { + // Source code does not originate from a file and is not native, but we + // can still get the source position inside the source string, e.g. in + // an eval string. + fileLocation += ""; + } + const lineNumber = frame.getLineNumber(); + if (lineNumber != null) { + fileLocation += ":" + String(lineNumber); + const columnNumber = frame.getColumnNumber(); + if (columnNumber) { + fileLocation += ":" + String(columnNumber); + } + } + } + + let line = ""; + const functionName = frame.getFunctionName(); + let addSuffix = true; + const isConstructor = frame.isConstructor(); + const isMethodCall = !(frame.isToplevel() || isConstructor); + if (isMethodCall) { + let typeName = frame.getTypeName(); + // Fixes shim to be backward compatable with Node v0 to v4 + if (typeName === "[object Object]") { + typeName = "null"; + } + const methodName = frame.getMethodName(); + if (functionName) { + if (typeName && functionName.indexOf(typeName) !== 0) { + line += typeName + "."; + } + line += functionName; + if ( + methodName && + functionName.indexOf("." + methodName) !== + functionName.length - methodName.length - 1 + ) { + line += " [as " + methodName + "]"; + } + } else { + line += typeName + "." + (methodName || ""); + } + } else if (isConstructor) { + line += "new " + (functionName || ""); + } else if (functionName) { + line += functionName; + } else { + line += fileLocation; + addSuffix = false; + } + if (addSuffix) { + line += " (" + fileLocation + ")"; + } + return line; +} + // Regex for detecting source maps const reSourceMap = /^data:application\/json[^,]+base64,/; @@ -124,16 +196,16 @@ function loadConsumer(source: string): SourceMapConsumer { if (reSourceMap.test(sourceMappingURL)) { // Support source map URL as a data url const rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(",") + 1); - //sourceMapData = bufferFrom(rawData, "base64").toString(); const ui8 = base64.toByteArray(rawData); sourceMapData = arrayToStr(ui8); sourceMappingURL = source; } else { // Support source map URLs relative to the source URL //sourceMappingURL = supportRelativeURL(source, sourceMappingURL); - //sourceMapData = retrieveFile(sourceMappingURL); + sourceMapData = getGeneratedContents(sourceMappingURL); } + //console.log("sourceMapData", sourceMapData); const rawSourceMap = JSON.parse(sourceMapData); consumer = new SourceMapConsumer(rawSourceMap); consumers.set(source, consumer); @@ -141,8 +213,6 @@ function loadConsumer(source: string): SourceMapConsumer { return consumer; } -const consumers = new Map(); - function retrieveSourceMapURL(fileData: string): string { // Get the URL of the source map // tslint:disable-next-line:max-line-length