Skip to content

Commit 9136bb1

Browse files
authored
Generate SourceMap mappings string using array of character codes and fewer String.fromCharCode calls (microsoft#44031)
* Test normal char code array for source mappings * Limit buffer size, minor performance tweaks * Always commit at exactly chunk size Co-authored-by: David Michon <dmichon-msft@users.noreply.github.com>
1 parent 271e069 commit 9136bb1

File tree

1 file changed

+52
-36
lines changed

1 file changed

+52
-36
lines changed

src/compiler/sourcemap.ts

+52-36
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace ts {
1717

1818
const names: string[] = [];
1919
let nameToNameIndexMap: ESMap<string, number> | undefined;
20+
const mappingCharCodes: number[] = [];
2021
let mappings = "";
2122

2223
// Last recorded and encoded mappings
@@ -210,6 +211,15 @@ namespace ts {
210211
|| lastNameIndex !== pendingNameIndex;
211212
}
212213

214+
function appendMappingCharCode(charCode: number) {
215+
mappingCharCodes.push(charCode);
216+
// String.fromCharCode accepts its arguments on the stack, so we have to chunk the input,
217+
// otherwise we can get stack overflows for large source maps
218+
if (mappingCharCodes.length >= 1024) {
219+
flushMappingBuffer();
220+
}
221+
}
222+
213223
function commitPendingMapping() {
214224
if (!hasPending || !shouldCommitMapping()) {
215225
return;
@@ -221,40 +231,41 @@ namespace ts {
221231
if (lastGeneratedLine < pendingGeneratedLine) {
222232
// Emit line delimiters
223233
do {
224-
mappings += ";";
234+
appendMappingCharCode(CharacterCodes.semicolon);
225235
lastGeneratedLine++;
226-
lastGeneratedCharacter = 0;
227236
}
228237
while (lastGeneratedLine < pendingGeneratedLine);
238+
// Only need to set this once
239+
lastGeneratedCharacter = 0;
229240
}
230241
else {
231242
Debug.assertEqual(lastGeneratedLine, pendingGeneratedLine, "generatedLine cannot backtrack");
232243
// Emit comma to separate the entry
233244
if (hasLast) {
234-
mappings += ",";
245+
appendMappingCharCode(CharacterCodes.comma);
235246
}
236247
}
237248

238249
// 1. Relative generated character
239-
mappings += base64VLQFormatEncode(pendingGeneratedCharacter - lastGeneratedCharacter);
250+
appendBase64VLQ(pendingGeneratedCharacter - lastGeneratedCharacter);
240251
lastGeneratedCharacter = pendingGeneratedCharacter;
241252

242253
if (hasPendingSource) {
243254
// 2. Relative sourceIndex
244-
mappings += base64VLQFormatEncode(pendingSourceIndex - lastSourceIndex);
255+
appendBase64VLQ(pendingSourceIndex - lastSourceIndex);
245256
lastSourceIndex = pendingSourceIndex;
246257

247258
// 3. Relative source line
248-
mappings += base64VLQFormatEncode(pendingSourceLine - lastSourceLine);
259+
appendBase64VLQ(pendingSourceLine - lastSourceLine);
249260
lastSourceLine = pendingSourceLine;
250261

251262
// 4. Relative source character
252-
mappings += base64VLQFormatEncode(pendingSourceCharacter - lastSourceCharacter);
263+
appendBase64VLQ(pendingSourceCharacter - lastSourceCharacter);
253264
lastSourceCharacter = pendingSourceCharacter;
254265

255266
if (hasPendingName) {
256267
// 5. Relative nameIndex
257-
mappings += base64VLQFormatEncode(pendingNameIndex - lastNameIndex);
268+
appendBase64VLQ(pendingNameIndex - lastNameIndex);
258269
lastNameIndex = pendingNameIndex;
259270
}
260271
}
@@ -263,8 +274,16 @@ namespace ts {
263274
exit();
264275
}
265276

277+
function flushMappingBuffer(): void {
278+
if (mappingCharCodes.length > 0) {
279+
mappings += String.fromCharCode.apply(undefined, mappingCharCodes);
280+
mappingCharCodes.length = 0;
281+
}
282+
}
283+
266284
function toJSON(): RawSourceMap {
267285
commitPendingMapping();
286+
flushMappingBuffer();
268287
return {
269288
version: 3,
270289
file,
@@ -275,6 +294,31 @@ namespace ts {
275294
sourcesContent,
276295
};
277296
}
297+
298+
function appendBase64VLQ(inValue: number): void {
299+
// Add a new least significant bit that has the sign of the value.
300+
// if negative number the least significant bit that gets added to the number has value 1
301+
// else least significant bit value that gets added is 0
302+
// eg. -1 changes to binary : 01 [1] => 3
303+
// +1 changes to binary : 01 [0] => 2
304+
if (inValue < 0) {
305+
inValue = ((-inValue) << 1) + 1;
306+
}
307+
else {
308+
inValue = inValue << 1;
309+
}
310+
311+
// Encode 5 bits at a time starting from least significant bits
312+
do {
313+
let currentDigit = inValue & 31; // 11111
314+
inValue = inValue >> 5;
315+
if (inValue > 0) {
316+
// There are still more digits to decode, set the msb (6th bit)
317+
currentDigit = currentDigit | 32;
318+
}
319+
appendMappingCharCode(base64FormatEncode(currentDigit));
320+
} while (inValue > 0);
321+
}
278322
}
279323

280324
// Sometimes tools can see the following line as a source mapping url comment, so we mangle it a bit (the [M])
@@ -544,34 +588,6 @@ namespace ts {
544588
-1;
545589
}
546590

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-
575591
interface MappedPosition {
576592
generatedPosition: number;
577593
source: string | undefined;

0 commit comments

Comments
 (0)