Skip to content

Commit b5306c8

Browse files
clydinKeen Yee Liau
authored andcommitted
fix(@angular-devkit/build-angular): reintroduce fast sourcemap path
1 parent 0587153 commit b5306c8

File tree

1 file changed

+79
-11
lines changed

1 file changed

+79
-11
lines changed

packages/angular_devkit/build_angular/src/utils/process-bundle.ts

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
import { createHash } from 'crypto';
1818
import * as fs from 'fs';
1919
import * as path from 'path';
20-
import { RawSourceMap } from 'source-map';
20+
import { RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map';
2121
import { minify } from 'terser';
2222
import * as v8 from 'v8';
2323
import { SourceMapSource } from 'webpack-sources';
@@ -27,6 +27,9 @@ import { I18nOptions } from './i18n-options';
2727
const cacache = require('cacache');
2828
const deserialize = ((v8 as unknown) as { deserialize(buffer: Buffer): unknown }).deserialize;
2929

30+
// If code size is larger than 500KB, consider lower fidelity but faster sourcemap merge
31+
const FAST_SOURCEMAP_THRESHOLD = 500 * 1024;
32+
3033
export interface ProcessBundleOptions {
3134
filename: string;
3235
code: string;
@@ -143,12 +146,18 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
143146
downlevelCode = transformResult.code;
144147

145148
if (sourceMap && transformResult.map) {
146-
downlevelMap = mergeSourceMaps(
149+
// String length is used as an estimate for byte length
150+
const fastSourceMaps = sourceCode.length > FAST_SOURCEMAP_THRESHOLD;
151+
152+
downlevelMap = await mergeSourceMaps(
147153
sourceCode,
148154
sourceMap,
149155
downlevelCode,
150156
transformResult.map,
151157
filename,
158+
// When not optimizing, the sourcemaps are significantly less complex
159+
// and can use the higher fidelity merge
160+
!!options.optimize && fastSourceMaps,
152161
);
153162
}
154163
}
@@ -173,14 +182,19 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
173182
return result;
174183
}
175184

176-
// SourceMapSource produces high-quality sourcemaps
177-
function mergeSourceMaps(
185+
async function mergeSourceMaps(
178186
inputCode: string,
179187
inputSourceMap: RawSourceMap,
180188
resultCode: string,
181189
resultSourceMap: RawSourceMap,
182190
filename: string,
183-
): RawSourceMap {
191+
fast = false,
192+
): Promise<RawSourceMap> {
193+
if (fast) {
194+
return mergeSourceMapsFast(inputSourceMap, resultSourceMap);
195+
}
196+
197+
// SourceMapSource produces high-quality sourcemaps
184198
// The last argument is not yet in the typings
185199
// tslint:disable-next-line: no-any
186200
return new (SourceMapSource as any)(
@@ -193,6 +207,58 @@ function mergeSourceMaps(
193207
).map();
194208
}
195209

210+
async function mergeSourceMapsFast(first: RawSourceMap, second: RawSourceMap) {
211+
const sourceRoot = first.sourceRoot;
212+
const generator = new SourceMapGenerator();
213+
214+
// sourcemap package adds the sourceRoot to all position source paths if not removed
215+
delete first.sourceRoot;
216+
217+
await SourceMapConsumer.with(first, null, originalConsumer => {
218+
return SourceMapConsumer.with(second, null, newConsumer => {
219+
newConsumer.eachMapping(mapping => {
220+
if (mapping.originalLine === null) {
221+
return;
222+
}
223+
const originalPosition = originalConsumer.originalPositionFor({
224+
line: mapping.originalLine,
225+
column: mapping.originalColumn,
226+
});
227+
if (
228+
originalPosition.line === null ||
229+
originalPosition.column === null ||
230+
originalPosition.source === null
231+
) {
232+
return;
233+
}
234+
generator.addMapping({
235+
generated: {
236+
line: mapping.generatedLine,
237+
column: mapping.generatedColumn,
238+
},
239+
name: originalPosition.name || undefined,
240+
original: {
241+
line: originalPosition.line,
242+
column: originalPosition.column,
243+
},
244+
source: originalPosition.source,
245+
});
246+
});
247+
});
248+
});
249+
250+
const map = generator.toJSON();
251+
map.file = second.file;
252+
map.sourceRoot = sourceRoot;
253+
254+
// Put the sourceRoot back
255+
if (sourceRoot) {
256+
first.sourceRoot = sourceRoot;
257+
}
258+
259+
return map;
260+
}
261+
196262
async function processBundle(
197263
options: Omit<ProcessBundleOptions, 'map'> & { isOriginal: boolean; map?: string | RawSourceMap },
198264
): Promise<ProcessBundleFile> {
@@ -220,7 +286,7 @@ async function processBundle(
220286
}
221287

222288
if (optimize) {
223-
result = terserMangle(code, {
289+
result = await terserMangle(code, {
224290
filename,
225291
map: rawMap,
226292
compress: !isOriginal, // We only compress bundles which are downlevelled.
@@ -265,7 +331,7 @@ async function processBundle(
265331
return fileResult;
266332
}
267333

268-
function terserMangle(
334+
async function terserMangle(
269335
code: string,
270336
options: { filename?: string; map?: RawSourceMap; compress?: boolean; ecma?: 5 | 6 } = {},
271337
) {
@@ -301,12 +367,13 @@ function terserMangle(
301367

302368
let outputMap;
303369
if (options.map && minifyOutput.map) {
304-
outputMap = mergeSourceMaps(
370+
outputMap = await mergeSourceMaps(
305371
code,
306372
options.map,
307373
outputCode,
308374
minifyOutput.map as unknown as RawSourceMap,
309375
options.filename || '0',
376+
code.length > FAST_SOURCEMAP_THRESHOLD,
310377
);
311378
}
312379

@@ -479,7 +546,7 @@ export async function inlineLocales(options: InlineOptions) {
479546
// If locale data is provided, load it and prepend to file
480547
const localeDataPath = i18n.locales[locale] && i18n.locales[locale].dataPath;
481548
if (localeDataPath) {
482-
const localDataContent = loadLocaleData(localeDataPath, true);
549+
const localDataContent = await loadLocaleData(localeDataPath, true);
483550
// The semicolon ensures that there is no syntax error between statements
484551
content.prepend(localDataContent + ';');
485552
}
@@ -501,6 +568,7 @@ export async function inlineLocales(options: InlineOptions) {
501568
output,
502569
contentMap,
503570
options.filename,
571+
options.code.length > FAST_SOURCEMAP_THRESHOLD,
504572
);
505573

506574
fs.writeFileSync(outputPath + '.map', JSON.stringify(outputMap));
@@ -612,13 +680,13 @@ function findLocalizePositions(
612680
return positions;
613681
}
614682

615-
function loadLocaleData(path: string, optimize: boolean): string {
683+
async function loadLocaleData(path: string, optimize: boolean): Promise<string> {
616684
// The path is validated during option processing before the build starts
617685
const content = fs.readFileSync(path, 'utf8');
618686

619687
// NOTE: This can be removed once the locale data files are preprocessed in the framework
620688
if (optimize) {
621-
const result = terserMangle(content, {
689+
const result = await terserMangle(content, {
622690
compress: true,
623691
ecma: 5,
624692
});

0 commit comments

Comments
 (0)