diff --git a/lib/base64-vlq.js b/lib/base64-vlq.js index 612b4040..7da12cb0 100644 --- a/lib/base64-vlq.js +++ b/lib/base64-vlq.js @@ -37,6 +37,9 @@ var base64 = require('./base64'); +// Inlined base64 encode lookup for performance (avoids function call + bounds check) +var base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, // the next four bits are the actual value, and the 6th bit is the @@ -103,7 +106,7 @@ exports.encode = function base64VLQ_encode(aValue) { // continuation bit is marked. digit |= VLQ_CONTINUATION_BIT; } - encoded += base64.encode(digit); + encoded += base64Chars[digit]; } while (vlq > 0); return encoded; diff --git a/lib/source-map-consumer.js b/lib/source-map-consumer.js index ee661146..621aa26d 100644 --- a/lib/source-map-consumer.js +++ b/lib/source-map-consumer.js @@ -80,7 +80,12 @@ Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', { enumerable: true, get: function () { if (!this.__originalMappings) { - this._parseMappings(this._mappings, this.sourceRoot); + // Ensure generatedMappings are parsed first (may also set __originalMappings for IndexedSourceMapConsumer) + var generatedMappings = this._generatedMappings; + // Build originalMappings lazily if not already set (BasicSourceMapConsumer) + if (!this.__originalMappings) { + this._buildOriginalMappings(); + } } return this.__originalMappings; @@ -479,13 +484,25 @@ function sortGenerated(array, start) { let n = array.length - start; if (n <= 1) { return; - } else if (n == 2) { - let a = array[start]; - let b = array[start + 1]; - if (compareGenerated(a, b) > 0) { - array[start] = b; - array[start + 1] = a; + } + + // Check if already sorted (common case for well-formed source maps) + let sorted = true; + for (let i = start + 1; i < l; i++) { + if (compareGenerated(array[i - 1], array[i]) > 0) { + sorted = false; + break; } + } + if (sorted) { + return; + } + + if (n == 2) { + // Already checked above, must be out of order + let a = array[start]; + array[start] = array[start + 1]; + array[start + 1] = a; } else if (n < 20) { for (let i = start; i < l; i++) { for (let j = i; j > start; j--) { @@ -502,6 +519,25 @@ function sortGenerated(array, start) { quickSort(array, compareGenerated, start); } } +// Lookup table for single-byte VLQ decode (no continuation bit) +// Maps base64 char code -> decoded signed value, or undefined if multi-byte +var vlqTable = []; +// Base64 decode table for multi-byte VLQ +var base64Table = new Int8Array(128); +(function() { + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + base64Table.fill(-1); + for (var i = 0; i < 64; i++) base64Table[chars.charCodeAt(i)] = i; + for (var i = 0; i < 128; i++) vlqTable[i] = undefined; + // Only first 32 base64 values (A-f) are single-byte VLQ (no continuation bit) + for (var i = 0; i < 32; i++) { + var charCode = chars.charCodeAt(i); + // Single-byte VLQ: bit 0 is sign, bits 1-4 are value + var value = i >> 1; + vlqTable[charCode] = (i & 1) ? -value : value; + } +})(); + BasicSourceMapConsumer.prototype._parseMappings = function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { var generatedLine = 1; @@ -512,15 +548,16 @@ BasicSourceMapConsumer.prototype._parseMappings = var previousName = 0; var length = aStr.length; var index = 0; - var cachedSegments = {}; - var temp = {}; - var originalMappings = []; var generatedMappings = []; - var mapping, str, segment, end, value; + var mapping, value, charCode, digit, result, shift; + // Reuse segment array to avoid allocations per mapping + var segment = [0, 0, 0, 0, 0]; + var segmentLength = 0; let subarrayStart = 0; while (index < length) { - if (aStr.charAt(index) === ';') { + charCode = aStr.charCodeAt(index); + if (charCode === 59) { // ';' generatedLine++; index++; previousGeneratedColumn = 0; @@ -528,33 +565,48 @@ BasicSourceMapConsumer.prototype._parseMappings = sortGenerated(generatedMappings, subarrayStart); subarrayStart = generatedMappings.length; } - else if (aStr.charAt(index) === ',') { + else if (charCode === 44) { // ',' index++; } else { - mapping = new Mapping(); - mapping.generatedLine = generatedLine; + mapping = { + generatedLine: generatedLine, + generatedColumn: 0, + source: null, + originalLine: null, + originalColumn: null, + name: null + }; - for (end = index; end < length; end++) { - if (this._charIsMappingSeparator(aStr, end)) { - break; + // Decode VLQ values until we hit a separator + segmentLength = 0; + while (index < length) { + charCode = aStr.charCodeAt(index); + if (charCode === 44 || charCode === 59) break; // ',' or ';' + // Fast path for single-byte VLQ (most common case) + value = vlqTable[charCode]; + if (value !== undefined) { + index++; + } else { + // Inline multi-byte VLQ decode + result = 0; + shift = 0; + do { + digit = base64Table[aStr.charCodeAt(index++)]; + result += (digit & 31) << shift; + shift += 5; + } while (digit >= 32); + // Convert from VLQ signed + value = (result & 1) ? -(result >> 1) : (result >> 1); } - } - str = aStr.slice(index, end); - - segment = []; - while (index < end) { - base64VLQ.decode(aStr, index, temp); - value = temp.value; - index = temp.rest; - segment.push(value); + segment[segmentLength++] = value; } - if (segment.length === 2) { + if (segmentLength === 2) { throw new Error('Found a source, but no line and column'); } - if (segment.length === 3) { + if (segmentLength === 3) { throw new Error('Found a source and line, but no column'); } @@ -562,7 +614,7 @@ BasicSourceMapConsumer.prototype._parseMappings = mapping.generatedColumn = previousGeneratedColumn + segment[0]; previousGeneratedColumn = mapping.generatedColumn; - if (segment.length > 1) { + if (segmentLength > 1) { // Original source. mapping.source = previousSource + segment[1]; previousSource += segment[1]; @@ -577,7 +629,7 @@ BasicSourceMapConsumer.prototype._parseMappings = mapping.originalColumn = previousOriginalColumn + segment[3]; previousOriginalColumn = mapping.originalColumn; - if (segment.length > 4) { + if (segmentLength > 4) { // Original name. mapping.name = previousName + segment[4]; previousName += segment[4]; @@ -585,21 +637,34 @@ BasicSourceMapConsumer.prototype._parseMappings = } generatedMappings.push(mapping); - if (typeof mapping.originalLine === 'number') { - let currentSource = mapping.source; - while (originalMappings.length <= currentSource) { - originalMappings.push(null); - } - if (originalMappings[currentSource] === null) { - originalMappings[currentSource] = []; - } - originalMappings[currentSource].push(mapping); - } } } sortGenerated(generatedMappings, subarrayStart); this.__generatedMappings = generatedMappings; + }; + +/** + * Build originalMappings lazily from generatedMappings. + */ +BasicSourceMapConsumer.prototype._buildOriginalMappings = + function SourceMapConsumer_buildOriginalMappings() { + var generatedMappings = this.__generatedMappings; + var originalMappings = []; + + for (var i = 0; i < generatedMappings.length; i++) { + var mapping = generatedMappings[i]; + if (typeof mapping.originalLine === 'number') { + var currentSource = mapping.source; + while (originalMappings.length <= currentSource) { + originalMappings.push(null); + } + if (originalMappings[currentSource] === null) { + originalMappings[currentSource] = []; + } + originalMappings[currentSource].push(mapping); + } + } for (var i = 0; i < originalMappings.length; i++) { if (originalMappings[i] != null) { diff --git a/lib/source-map-generator.js b/lib/source-map-generator.js index bab04ff8..fb9a8fa4 100644 --- a/lib/source-map-generator.js +++ b/lib/source-map-generator.js @@ -323,6 +323,17 @@ SourceMapGenerator.prototype._validateMapping = } }; +// Fast VLQ encode lookup for values -15 to 15 (single char output) +var vlqEncodeTable = []; +(function() { + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + for (var i = -15; i <= 15; i++) { + // VLQ signed: negative becomes odd, positive becomes even + var vlq = i < 0 ? ((-i) << 1) + 1 : (i << 1); + vlqEncodeTable[i + 15] = chars[vlq]; + } +})(); + /** * Serialize the accumulated mappings in to the stream of base 64 VLQs * specified by the source map format. @@ -336,7 +347,7 @@ SourceMapGenerator.prototype._serializeMappings = var previousName = 0; var previousSource = 0; var result = ''; - var next; + var next, val; var mapping; var nameIdx; var sourceIdx; @@ -362,27 +373,29 @@ SourceMapGenerator.prototype._serializeMappings = } } - next += base64VLQ.encode(mapping.generatedColumn - - previousGeneratedColumn); + val = mapping.generatedColumn - previousGeneratedColumn; + next += (val >= -15 && val <= 15) ? vlqEncodeTable[val + 15] : base64VLQ.encode(val); previousGeneratedColumn = mapping.generatedColumn; if (mapping.source != null) { sourceIdx = this._sources.indexOf(mapping.source); - next += base64VLQ.encode(sourceIdx - previousSource); + val = sourceIdx - previousSource; + next += (val >= -15 && val <= 15) ? vlqEncodeTable[val + 15] : base64VLQ.encode(val); previousSource = sourceIdx; // lines are stored 0-based in SourceMap spec version 3 - next += base64VLQ.encode(mapping.originalLine - 1 - - previousOriginalLine); + val = mapping.originalLine - 1 - previousOriginalLine; + next += (val >= -15 && val <= 15) ? vlqEncodeTable[val + 15] : base64VLQ.encode(val); previousOriginalLine = mapping.originalLine - 1; - next += base64VLQ.encode(mapping.originalColumn - - previousOriginalColumn); + val = mapping.originalColumn - previousOriginalColumn; + next += (val >= -15 && val <= 15) ? vlqEncodeTable[val + 15] : base64VLQ.encode(val); previousOriginalColumn = mapping.originalColumn; if (mapping.name != null) { nameIdx = this._names.indexOf(mapping.name); - next += base64VLQ.encode(nameIdx - previousName); + val = nameIdx - previousName; + next += (val >= -15 && val <= 15) ? vlqEncodeTable[val + 15] : base64VLQ.encode(val); previousName = nameIdx; } }