Skip to content

Commit c36edb3

Browse files
Experiment with Uint8Array to reduce memory consumption of sourcemap generation.
1 parent 71f338b commit c36edb3

File tree

2 files changed

+52
-37
lines changed

2 files changed

+52
-37
lines changed

‎src/compiler/emitter.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,12 +532,14 @@ namespace ts {
532532
const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined;
533533
const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined;
534534
const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile!];
535+
const guessedLength = sourceFile?.text.length ?? reduceLeft(sourceFiles, (acc, file) => acc + file.text.length, 0);
535536

536537
let sourceMapGenerator: SourceMapGenerator | undefined;
537538
if (shouldEmitSourceMaps(mapOptions, sourceFileOrBundle)) {
538539
sourceMapGenerator = createSourceMapGenerator(
539540
host,
540541
getBaseFileName(normalizeSlashes(jsFilePath)),
542+
guessedLength,
541543
getSourceRoot(mapOptions),
542544
getSourceMapDirectory(mapOptions, jsFilePath, sourceFile),
543545
mapOptions);

‎src/compiler/sourcemap.ts‎

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ namespace ts {
44
extendedDiagnostics?: boolean;
55
}
66

7-
export function createSourceMapGenerator(host: EmitHost, file: string, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator {
7+
declare var TextDecoder: new() => { decode(buffer: ArrayBuffer | ArrayBufferView): string };
8+
const decoder = new TextDecoder();
9+
10+
export function createSourceMapGenerator(host: EmitHost, file: string, guessedInputLength: number, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator {
811
const { enter, exit } = generatorOptions.extendedDiagnostics
912
? performance.createTimer("Source Map", "beforeSourcemap", "afterSourcemap")
1013
: performance.nullTimer;
@@ -17,7 +20,44 @@ namespace ts {
1720

1821
const names: string[] = [];
1922
let nameToNameIndexMap: ESMap<string, number> | undefined;
20-
let mappings = "";
23+
let mappingsBuffer = new Uint8Array(guessedInputLength + 1 >> 1);
24+
let lastMappings: string | undefined;
25+
let mappingsPos = 0;
26+
function setMapping(charCode: number) {
27+
// resize to 1.5 length
28+
if (mappingsPos >= mappingsBuffer.length) {
29+
const oldLength = mappingsBuffer.length + 1;
30+
let replacementBuffer = new Uint8Array(oldLength + ((oldLength + 1) >> 1));
31+
replacementBuffer.set(mappingsBuffer);
32+
mappingsBuffer = replacementBuffer;
33+
}
34+
mappingsBuffer[mappingsPos++] = charCode;
35+
}
36+
37+
function base64VLQFormatEncode(inValue: number) {
38+
// Add a new least significant bit that has the sign of the value.
39+
// if negative number the least significant bit that gets added to the number has value 1
40+
// else least significant bit value that gets added is 0
41+
// eg. -1 changes to binary : 01 [1] => 3
42+
// +1 changes to binary : 01 [0] => 2
43+
if (inValue < 0) {
44+
inValue = ((-inValue) << 1) + 1;
45+
}
46+
else {
47+
inValue = inValue << 1;
48+
}
49+
50+
// Encode 5 bits at a time starting from least significant bits
51+
do {
52+
let currentDigit = inValue & 31; // 11111
53+
inValue = inValue >> 5;
54+
if (inValue > 0) {
55+
// There are still more digits to decode, set the msb (6th bit)
56+
currentDigit = currentDigit | 32;
57+
}
58+
setMapping(base64FormatEncode(currentDigit));
59+
} while (inValue > 0);
60+
}
2161

2262
// Last recorded and encoded mappings
2363
let lastGeneratedLine = 0;
@@ -221,7 +261,7 @@ namespace ts {
221261
if (lastGeneratedLine < pendingGeneratedLine) {
222262
// Emit line delimiters
223263
do {
224-
mappings += ";";
264+
setMapping(CharacterCodes.semicolon);
225265
lastGeneratedLine++;
226266
lastGeneratedCharacter = 0;
227267
}
@@ -231,30 +271,30 @@ namespace ts {
231271
Debug.assertEqual(lastGeneratedLine, pendingGeneratedLine, "generatedLine cannot backtrack");
232272
// Emit comma to separate the entry
233273
if (hasLast) {
234-
mappings += ",";
274+
setMapping(CharacterCodes.comma);
235275
}
236276
}
237277

238278
// 1. Relative generated character
239-
mappings += base64VLQFormatEncode(pendingGeneratedCharacter - lastGeneratedCharacter);
279+
base64VLQFormatEncode(pendingGeneratedCharacter - lastGeneratedCharacter);
240280
lastGeneratedCharacter = pendingGeneratedCharacter;
241281

242282
if (hasPendingSource) {
243283
// 2. Relative sourceIndex
244-
mappings += base64VLQFormatEncode(pendingSourceIndex - lastSourceIndex);
284+
base64VLQFormatEncode(pendingSourceIndex - lastSourceIndex);
245285
lastSourceIndex = pendingSourceIndex;
246286

247287
// 3. Relative source line
248-
mappings += base64VLQFormatEncode(pendingSourceLine - lastSourceLine);
288+
base64VLQFormatEncode(pendingSourceLine - lastSourceLine);
249289
lastSourceLine = pendingSourceLine;
250290

251291
// 4. Relative source character
252-
mappings += base64VLQFormatEncode(pendingSourceCharacter - lastSourceCharacter);
292+
base64VLQFormatEncode(pendingSourceCharacter - lastSourceCharacter);
253293
lastSourceCharacter = pendingSourceCharacter;
254294

255295
if (hasPendingName) {
256296
// 5. Relative nameIndex
257-
mappings += base64VLQFormatEncode(pendingNameIndex - lastNameIndex);
297+
base64VLQFormatEncode(pendingNameIndex - lastNameIndex);
258298
lastNameIndex = pendingNameIndex;
259299
}
260300
}
@@ -265,6 +305,7 @@ namespace ts {
265305

266306
function toJSON(): RawSourceMap {
267307
commitPendingMapping();
308+
const mappings = (lastMappings ??= decoder.decode(mappingsBuffer.slice(0, mappingsPos)));
268309
return {
269310
version: 3,
270311
file,
@@ -544,34 +585,6 @@ namespace ts {
544585
-1;
545586
}
546587

547-
function base64VLQFormatEncode(inValue: number) {
548-
// Add a new least significant bit that has the sign of the value.
549-
// if negative number the least significant bit that gets added to the number has value 1
550-
// else least significant bit value that gets added is 0
551-
// eg. -1 changes to binary : 01 [1] => 3
552-
// +1 changes to binary : 01 [0] => 2
553-
if (inValue < 0) {
554-
inValue = ((-inValue) << 1) + 1;
555-
}
556-
else {
557-
inValue = inValue << 1;
558-
}
559-
560-
// Encode 5 bits at a time starting from least significant bits
561-
let encodedStr = "";
562-
do {
563-
let currentDigit = inValue & 31; // 11111
564-
inValue = inValue >> 5;
565-
if (inValue > 0) {
566-
// There are still more digits to decode, set the msb (6th bit)
567-
currentDigit = currentDigit | 32;
568-
}
569-
encodedStr = encodedStr + String.fromCharCode(base64FormatEncode(currentDigit));
570-
} while (inValue > 0);
571-
572-
return encodedStr;
573-
}
574-
575588
interface MappedPosition {
576589
generatedPosition: number;
577590
source: string | undefined;

0 commit comments

Comments
 (0)