Skip to content

Commit 5b2d63f

Browse files
clydinmgechev
authored andcommitted
fix(@angular-devkit/build-angular): improve quality of differential loading sourcemaps
1 parent 648e23a commit 5b2d63f

File tree

1 file changed

+36
-80
lines changed

1 file changed

+36
-80
lines changed

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

Lines changed: 36 additions & 80 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, SourceMapConsumer, SourceMapGenerator } from 'source-map';
20+
import { RawSourceMap } from 'source-map';
2121
import { minify } from 'terser';
2222
import * as v8 from 'v8';
2323
import { SourceMapSource } from 'webpack-sources';
@@ -108,23 +108,19 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
108108
const filename = path.basename(options.filename);
109109
const downlevelFilename = filename.replace(/\-es20\d{2}/, '-es5');
110110
const downlevel = !options.optimizeOnly;
111-
112-
// if code size is larger than 500kB, manually handle sourcemaps with newer source-map package.
113-
// babel currently uses an older version that still supports sync calls
114-
const codeSize = Buffer.byteLength(options.code);
115-
const mapSize = options.map ? Buffer.byteLength(options.map) : 0;
116-
const manualSourceMaps = codeSize >= 500 * 1024 || mapSize >= 500 * 1024;
117111
const sourceCode = options.code;
118-
const sourceMap = options.map ? JSON.parse(options.map) : false;
112+
const sourceMap = options.map ? JSON.parse(options.map) : undefined;
119113

120114
let downlevelCode;
121115
let downlevelMap;
122116
if (downlevel) {
123117
// Downlevel the bundle
124118
const transformResult = await transformAsync(sourceCode, {
125-
filename: options.filename,
119+
filename,
126120
// using false ensures that babel will NOT search and process sourcemap comments (large memory usage)
127-
inputSourceMap: manualSourceMaps ? false : sourceMap,
121+
// The types do not include the false option even though it is valid
122+
// tslint:disable-next-line: no-any
123+
inputSourceMap: false as any,
128124
babelrc: false,
129125
presets: [[
130126
require.resolve('@babel/preset-env'),
@@ -147,11 +143,14 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
147143
}
148144
downlevelCode = transformResult.code;
149145

150-
if (manualSourceMaps && sourceMap && transformResult.map) {
151-
downlevelMap = await mergeSourceMapsFast(sourceMap, transformResult.map);
152-
} else {
153-
// undefined is needed here to normalize the property type
154-
downlevelMap = transformResult.map || undefined;
146+
if (sourceMap && transformResult.map) {
147+
downlevelMap = mergeSourceMaps(
148+
sourceCode,
149+
sourceMap,
150+
downlevelCode,
151+
transformResult.map,
152+
filename,
153+
);
155154
}
156155
}
157156

@@ -175,15 +174,14 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
175174
return result;
176175
}
177176

177+
// SourceMapSource produces high-quality sourcemaps
178178
function mergeSourceMaps(
179179
inputCode: string,
180180
inputSourceMap: RawSourceMap,
181181
resultCode: string,
182182
resultSourceMap: RawSourceMap,
183183
filename: string,
184184
): RawSourceMap {
185-
// More accurate but significantly more costly
186-
187185
// The last argument is not yet in the typings
188186
// tslint:disable-next-line: no-any
189187
return new (SourceMapSource as any)(
@@ -196,58 +194,6 @@ function mergeSourceMaps(
196194
).map();
197195
}
198196

199-
async function mergeSourceMapsFast(first: RawSourceMap, second: RawSourceMap) {
200-
const sourceRoot = first.sourceRoot;
201-
const generator = new SourceMapGenerator();
202-
203-
// sourcemap package adds the sourceRoot to all position source paths if not removed
204-
delete first.sourceRoot;
205-
206-
await SourceMapConsumer.with(first, null, originalConsumer => {
207-
return SourceMapConsumer.with(second, null, newConsumer => {
208-
newConsumer.eachMapping(mapping => {
209-
if (mapping.originalLine === null) {
210-
return;
211-
}
212-
const originalPosition = originalConsumer.originalPositionFor({
213-
line: mapping.originalLine,
214-
column: mapping.originalColumn,
215-
});
216-
if (
217-
originalPosition.line === null ||
218-
originalPosition.column === null ||
219-
originalPosition.source === null
220-
) {
221-
return;
222-
}
223-
generator.addMapping({
224-
generated: {
225-
line: mapping.generatedLine,
226-
column: mapping.generatedColumn,
227-
},
228-
name: originalPosition.name || undefined,
229-
original: {
230-
line: originalPosition.line,
231-
column: originalPosition.column,
232-
},
233-
source: originalPosition.source,
234-
});
235-
});
236-
});
237-
});
238-
239-
const map = generator.toJSON();
240-
map.file = second.file;
241-
map.sourceRoot = sourceRoot;
242-
243-
// Put the sourceRoot back
244-
if (sourceRoot) {
245-
first.sourceRoot = sourceRoot;
246-
}
247-
248-
return map;
249-
}
250-
251197
async function processBundle(
252198
options: Omit<ProcessBundleOptions, 'map'> & { isOriginal: boolean; map?: string | RawSourceMap },
253199
): Promise<ProcessBundleFile> {
@@ -270,6 +216,10 @@ async function processBundle(
270216
map: RawSourceMap | undefined,
271217
};
272218

219+
if (rawMap) {
220+
rawMap.file = filename;
221+
}
222+
273223
if (optimize) {
274224
result = terserMangle(code, {
275225
filename,
@@ -278,10 +228,6 @@ async function processBundle(
278228
ecma: isOriginal ? 6 : 5,
279229
});
280230
} else {
281-
if (rawMap) {
282-
rawMap.file = filename;
283-
}
284-
285231
result = {
286232
map: rawMap,
287233
code,
@@ -328,7 +274,7 @@ function terserMangle(
328274
// estree -> terser is already supported; need babel -> estree/terser
329275

330276
// Mangle downlevel code
331-
const minifyOutput = minify(code, {
277+
const minifyOutput = minify(options.filename ? { [options.filename]: code } : code, {
332278
compress: options.compress || false,
333279
ecma: options.ecma || 5,
334280
mangle: !manglingDisabled,
@@ -340,10 +286,6 @@ function terserMangle(
340286
sourceMap:
341287
!!options.map &&
342288
({
343-
filename: options.filename,
344-
// terser uses an old version of the sourcemap typings
345-
// tslint:disable-next-line: no-any
346-
content: options.map as any,
347289
asObject: true,
348290
// typings don't include asObject option
349291
// tslint:disable-next-line: no-any
@@ -355,7 +297,20 @@ function terserMangle(
355297
}
356298

357299
// tslint:disable-next-line: no-non-null-assertion
358-
return { code: minifyOutput.code!, map: minifyOutput.map as RawSourceMap | undefined };
300+
const outputCode = minifyOutput.code!;
301+
302+
let outputMap;
303+
if (options.map && minifyOutput.map) {
304+
outputMap = mergeSourceMaps(
305+
code,
306+
options.map,
307+
outputCode,
308+
minifyOutput.map as unknown as RawSourceMap,
309+
options.filename || '0',
310+
);
311+
}
312+
313+
return { code: outputCode, map: outputMap };
359314
}
360315

361316
function createFileEntry(
@@ -493,7 +448,8 @@ export async function inlineLocales(options: InlineOptions) {
493448
return inlineCopyOnly(options);
494449
}
495450

496-
let content = new MagicString(options.code);
451+
// tslint:disable-next-line: no-any
452+
let content = new MagicString(options.code, { filename: options.filename } as any);
497453
const inputMap = options.map && (JSON.parse(options.map) as RawSourceMap);
498454
let contentClone;
499455
for (const locale of i18n.inlineLocales) {

0 commit comments

Comments
 (0)