From 2d3b8107bf1c670d76d2cab3de52ef8023557259 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Mon, 22 Jul 2013 09:21:10 -0700 Subject: [PATCH] cache source map data forever by default --- package.json | 2 +- source-map-support.js | 60 ++++++++++++++++++++++++++++--------------- test.js | 4 ++- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 8b84d6a..eff5e2f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "source-map-support", "description": "Fixes stack traces for files with source maps", - "version": "0.2.0", + "version": "0.2.1", "main": "./source-map-support.js", "scripts": { "test": "node_modules/mocha/bin/mocha" diff --git a/source-map-support.js b/source-map-support.js index 562e0ad..41da7fb 100644 --- a/source-map-support.js +++ b/source-map-support.js @@ -2,25 +2,42 @@ var SourceMapConsumer = require('source-map').SourceMapConsumer; var path = require('path'); var fs = require('fs'); +// If true, the caches are reset before a stack trace formatting operation +var emptyCacheBetweenOperations = false; + +// Maps a file path to a string containing the file contents +var fileContentsCache = {}; + +// Maps a file path to a source map for that file +var sourceMapCache = {}; + function isInBrowser() { return typeof window !== 'undefined'; } function retrieveFile(path) { + if (path in fileContentsCache) { + return fileContentsCache[path]; + } + // Use SJAX if we are in the browser if (isInBrowser()) { var xhr = new XMLHttpRequest(); xhr.open('GET', path, false); xhr.send(null); - return xhr.readyState === 4 ? xhr.responseText : null; + var contents = xhr.readyState === 4 ? xhr.responseText : null; } // Otherwise, use the filesystem - try { - return fs.readFileSync(path, 'utf8'); - } catch (e) { - return null; + else { + try { + var contents = fs.readFileSync(path, 'utf8'); + } catch (e) { + var contents = null; + } } + + return fileContentsCache[path] = contents; } // Support URLs relative to a directory, but be careful about a protocol prefix @@ -66,13 +83,13 @@ var retrieveSourceMap = function (source) { }; }; -var mapSourcePosition = exports.mapSourcePosition = function(cache, position) { - var sourceMap = cache[position.source]; +var mapSourcePosition = exports.mapSourcePosition = function(position) { + var sourceMap = sourceMapCache[position.source]; if (!sourceMap) { // Call the (overrideable) retrieveSourceMap function to get the source map. var urlAndMap = retrieveSourceMap(position.source); if (urlAndMap) { - sourceMap = cache[position.source] = { + sourceMap = sourceMapCache[position.source] = { url: urlAndMap.url, map: new SourceMapConsumer(urlAndMap.map) }; @@ -102,11 +119,11 @@ var mapSourcePosition = exports.mapSourcePosition = function(cache, position) { // Parses code generated by FormatEvalOrigin(), a function inside V8: // https://code.google.com/p/v8/source/browse/trunk/src/messages.js -function mapEvalOrigin(cache, origin) { +function mapEvalOrigin(origin) { // Most eval() calls are in this format var match = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/.exec(origin); if (match) { - var position = mapSourcePosition(cache, { + var position = mapSourcePosition({ source: match[2], line: match[3], column: match[4] @@ -118,20 +135,20 @@ function mapEvalOrigin(cache, origin) { // Parse nested eval() calls using recursion match = /^eval at ([^(]+) \((.+)\)$/.exec(origin); if (match) { - return 'eval at ' + match[1] + ' (' + mapEvalOrigin(cache, match[2]) + ')'; + return 'eval at ' + match[1] + ' (' + mapEvalOrigin(match[2]) + ')'; } // Make sure we still return useful information if we didn't find anything return origin; } -function wrapCallSite(cache, frame) { +function wrapCallSite(frame) { // Most call sites will return the source file from getFileName(), but code // passed to eval() ending in "//# sourceURL=..." will return the source file // from getScriptNameOrSourceURL() instead var source = frame.getFileName() || frame.getScriptNameOrSourceURL(); if (source) { - var position = mapSourcePosition(cache, { + var position = mapSourcePosition({ source: source, line: frame.getLineNumber(), column: frame.getColumnNumber() @@ -148,7 +165,7 @@ function wrapCallSite(cache, frame) { // Code called using eval() needs special handling var origin = frame.isEval() && frame.getEvalOrigin(); if (origin) { - origin = mapEvalOrigin(cache, origin); + origin = mapEvalOrigin(origin); return { __proto__: frame, getEvalOrigin: function() { return origin; } @@ -162,12 +179,12 @@ function wrapCallSite(cache, frame) { // This function is part of the V8 stack trace API, for more info see: // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi function prepareStackTrace(error, stack) { - // Store source maps in a cache so we don't load them more than once when - // formatting a single stack trace (don't cache them forever though in case - // the files change on disk and the user wants to see the updated mapping) - var cache = {}; + if (emptyCacheBetweenOperations) { + fileContentsCache = {}; + sourceMapCache = {}; + } return error + stack.map(function(frame) { - return '\n at ' + wrapCallSite(cache, frame); + return '\n at ' + wrapCallSite(frame); }).join(''); } @@ -179,8 +196,7 @@ function handleUncaughtExceptions(error) { } var match = /\n at [^(]+ \((.*):(\d+):(\d+)\)/.exec(error.stack); if (match) { - var cache = {}; - var position = mapSourcePosition(cache, { + var position = mapSourcePosition({ source: match[1], line: match[2], column: match[3] @@ -206,6 +222,8 @@ exports.install = function(options) { options = options || {}; var installHandler = 'handleUncaughtExceptions' in options ? options.handleUncaughtExceptions : true; + emptyCacheBetweenOperations = 'emptyCacheBetweenOperations' in options ? + options.emptyCacheBetweenOperations : false; // Allow source maps to be found by methods other than reading the files // directly from disk. diff --git a/test.js b/test.js index ae6205a..9717f1d 100644 --- a/test.js +++ b/test.js @@ -1,4 +1,6 @@ -require('./source-map-support').install(); +require('./source-map-support').install({ + emptyCacheBetweenOperations: true // Needed to be able to test for failure +}); var SourceMapGenerator = require('source-map').SourceMapGenerator; var child_process = require('child_process');