Skip to content

Commit 5bd2e6b

Browse files
alan-agius4vikerman
authored andcommitted
fix(@angular-devkit/build-angular): add sourceMappingURL comment for ES2015 during differential loading
When having differential loading enabled we only add the `sourceMappingURL` comment when optimization is enabled, because we only process these bundles when we enabling optimization. With this change we now process such bundles even when optimization is disabled and add `sourceMappingURL` when source maps are enabled and not hidden. Closes #16522
1 parent 1ab6091 commit 5bd2e6b

File tree

5 files changed

+140
-147
lines changed

5 files changed

+140
-147
lines changed

packages/angular_devkit/build_angular/src/browser/action-cache.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,38 +70,43 @@ export class BundleActionCache {
7070
}
7171

7272
generateCacheKeys(action: ProcessBundleOptions): string[] {
73-
const baseCacheKey = this.generateBaseCacheKey(action.code);
74-
75-
// Postfix added to sourcemap cache keys when vendor sourcemaps are present
73+
// Postfix added to sourcemap cache keys when vendor, hidden sourcemaps are present
7674
// Allows non-destructive caching of both variants
77-
const SourceMapVendorPostfix = !!action.sourceMaps && action.vendorSourceMaps ? '|vendor' : '';
75+
const sourceMapVendorPostfix = action.sourceMaps && action.vendorSourceMaps ? '|vendor' : '';
76+
77+
// sourceMappingURL is added at the very end which causes the code to be the same when sourcemaps are enabled/disabled
78+
// When using hiddenSourceMaps we can omit the postfix since sourceMappingURL will not be added.
79+
const sourceMapPostFix = action.sourceMaps && !action.hiddenSourceMaps ? '|sourcemap' : '';
80+
81+
const baseCacheKey = this.generateBaseCacheKey(action.code);
7882

7983
// Determine cache entries required based on build settings
80-
const cacheKeys = [];
84+
const cacheKeys: string[] = [];
8185

8286
// If optimizing and the original is not ignored, add original as required
83-
if ((action.optimize || action.optimizeOnly) && !action.ignoreOriginal) {
84-
cacheKeys[CacheKey.OriginalCode] = baseCacheKey + '|orig';
87+
if (!action.ignoreOriginal) {
88+
cacheKeys[CacheKey.OriginalCode] = baseCacheKey + sourceMapPostFix + '|orig';
8589

8690
// If sourcemaps are enabled, add original sourcemap as required
8791
if (action.sourceMaps) {
88-
cacheKeys[CacheKey.OriginalMap] = baseCacheKey + SourceMapVendorPostfix + '|orig-map';
92+
cacheKeys[CacheKey.OriginalMap] = baseCacheKey + sourceMapVendorPostfix + '|orig-map';
8993
}
9094
}
95+
9196
// If not only optimizing, add downlevel as required
9297
if (!action.optimizeOnly) {
93-
cacheKeys[CacheKey.DownlevelCode] = baseCacheKey + '|dl';
98+
cacheKeys[CacheKey.DownlevelCode] = baseCacheKey + sourceMapPostFix + '|dl';
9499

95100
// If sourcemaps are enabled, add downlevel sourcemap as required
96101
if (action.sourceMaps) {
97-
cacheKeys[CacheKey.DownlevelMap] = baseCacheKey + SourceMapVendorPostfix + '|dl-map';
102+
cacheKeys[CacheKey.DownlevelMap] = baseCacheKey + sourceMapVendorPostfix + '|dl-map';
98103
}
99104
}
100105

101106
return cacheKeys;
102107
}
103108

104-
async getCacheEntries(cacheKeys: (string | null)[]): Promise<(CacheEntry | null)[] | false> {
109+
async getCacheEntries(cacheKeys: (string | undefined)[]): Promise<(CacheEntry | null)[] | false> {
105110
// Attempt to get required cache entries
106111
const cacheEntries = [];
107112
for (const key of cacheKeys) {

packages/angular_devkit/build_angular/src/browser/index.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -345,12 +345,6 @@ export function buildWebpackBrowser(
345345
if (!es5Polyfills) {
346346
moduleFiles.push(file);
347347
}
348-
// If not optimizing then ES2015 polyfills do not need processing
349-
// Unlike other module scripts, it is never downleveled
350-
const es2015Polyfills = file.file.startsWith('polyfills-es20');
351-
if (!actionOptions.optimize && es2015Polyfills) {
352-
continue;
353-
}
354348

355349
// Retrieve the content/map for the file
356350
// NOTE: Additional future optimizations will read directly from memory
@@ -371,6 +365,8 @@ export function buildWebpackBrowser(
371365
filename = filename.replace(/\-es20\d{2}/, '');
372366
}
373367

368+
const es2015Polyfills = file.file.startsWith('polyfills-es20');
369+
374370
// Record the bundle processing action
375371
// The runtime chunk gets special processing for lazy loaded files
376372
actions.push({

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

Lines changed: 76 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface ProcessBundleOptions {
2727
optimize?: boolean;
2828
optimizeOnly?: boolean;
2929
ignoreOriginal?: boolean;
30-
cacheKeys?: (string | null)[];
30+
cacheKeys?: (string | undefined)[];
3131
integrityAlgorithm?: 'sha256' | 'sha384' | 'sha512';
3232
runtimeData?: ProcessBundleResult[];
3333
}
@@ -62,9 +62,9 @@ export function setup(options: { cachePath: string }): void {
6262
cachePath = options.cachePath;
6363
}
6464

65-
async function cachePut(content: string, key: string | null, integrity?: string): Promise<void> {
65+
async function cachePut(content: string, key: string | undefined, integrity?: string): Promise<void> {
6666
if (cachePath && key) {
67-
await cacache.put(cachePath, key, content, {
67+
await cacache.put(cachePath, key || null, content, {
6868
metadata: { integrity },
6969
});
7070
}
@@ -94,8 +94,7 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
9494
// if code size is larger than 1 MB, manually handle sourcemaps with newer source-map package.
9595
const codeSize = Buffer.byteLength(options.code);
9696
const mapSize = options.map ? Buffer.byteLength(options.map) : 0;
97-
const manualSourceMaps = codeSize >= 1024 * 1024 || mapSize >= 1024 * 1024;
98-
97+
const manualSourceMaps = codeSize >= 500 * 1024 || mapSize >= 500 * 1024;
9998
const sourceCode = options.code;
10099
const sourceMap = options.map ? JSON.parse(options.map) : false;
101100

@@ -138,59 +137,21 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
138137
}
139138
}
140139

141-
if (options.optimize) {
142-
if (downlevelCode) {
143-
const minifyResult = terserMangle(downlevelCode, {
144-
filename: downlevelFilename,
145-
map: downlevelMap,
146-
compress: true,
147-
});
148-
downlevelCode = minifyResult.code;
149-
downlevelMap = minifyResult.map;
150-
}
151-
152-
if (!options.ignoreOriginal) {
153-
result.original = await mangleOriginal(options);
154-
}
155-
}
156-
157140
if (downlevelCode) {
158-
const downlevelPath = path.join(basePath, downlevelFilename);
159-
160-
let mapContent;
161-
if (downlevelMap) {
162-
if (!options.hiddenSourceMaps) {
163-
downlevelCode += `\n//# sourceMappingURL=${downlevelFilename}.map`;
164-
}
165-
166-
mapContent = JSON.stringify(downlevelMap);
167-
await cachePut(mapContent, options.cacheKeys[CacheKey.DownlevelMap]);
168-
fs.writeFileSync(downlevelPath + '.map', mapContent);
169-
}
170-
171-
result.downlevel = createFileEntry(
172-
downlevelFilename,
173-
downlevelCode,
174-
mapContent,
175-
options.integrityAlgorithm,
176-
);
177-
178-
await cachePut(
179-
downlevelCode,
180-
options.cacheKeys[CacheKey.DownlevelCode],
181-
result.downlevel.integrity,
182-
);
183-
fs.writeFileSync(downlevelPath, downlevelCode);
141+
result.downlevel = await processBundle({
142+
...options,
143+
code: downlevelCode,
144+
map: downlevelMap,
145+
filename: path.join(basePath, downlevelFilename),
146+
isOriginal: false,
147+
});
184148
}
185149

186-
// If original was not processed, add info
187150
if (!result.original && !options.ignoreOriginal) {
188-
result.original = createFileEntry(
189-
options.filename,
190-
options.code,
191-
options.map,
192-
options.integrityAlgorithm,
193-
);
151+
result.original = await processBundle({
152+
...options,
153+
isOriginal: true,
154+
});
194155
}
195156

196157
return result;
@@ -248,41 +209,74 @@ async function mergeSourcemaps(first: RawSourceMap, second: RawSourceMap) {
248209
return map;
249210
}
250211

251-
async function mangleOriginal(options: ProcessBundleOptions): Promise<ProcessBundleFile> {
252-
const result = terserMangle(options.code, {
253-
filename: path.basename(options.filename),
254-
map: options.map ? JSON.parse(options.map) : undefined,
255-
ecma: 6,
256-
});
212+
async function processBundle(
213+
options: Omit<ProcessBundleOptions, 'map'> & { isOriginal: boolean; map?: string | RawSourceMap },
214+
): Promise<ProcessBundleFile> {
215+
const {
216+
optimize,
217+
isOriginal,
218+
code,
219+
map,
220+
filename: filepath,
221+
hiddenSourceMaps,
222+
cacheKeys = [],
223+
integrityAlgorithm,
224+
} = options;
225+
226+
const rawMap = typeof map === 'string' ? JSON.parse(map) as RawSourceMap : map;
227+
const filename = path.basename(filepath);
228+
229+
let result: {
230+
code: string,
231+
map: RawSourceMap | undefined,
232+
};
233+
234+
if (optimize) {
235+
result = terserMangle(code, {
236+
filename,
237+
map: rawMap,
238+
compress: !isOriginal, // We only compress bundles which are downlevelled.
239+
ecma: isOriginal ? 6 : 5,
240+
});
241+
} else {
242+
if (rawMap) {
243+
rawMap.file = filename;
244+
}
257245

258-
let mapContent;
246+
result = {
247+
map: rawMap,
248+
code,
249+
};
250+
}
251+
252+
let mapContent: string | undefined;
259253
if (result.map) {
260-
if (!options.hiddenSourceMaps) {
261-
result.code += `\n//# sourceMappingURL=${path.basename(options.filename)}.map`;
254+
if (!hiddenSourceMaps) {
255+
result.code += `\n//# sourceMappingURL=${filename}.map`;
262256
}
263257

264258
mapContent = JSON.stringify(result.map);
265259

266260
await cachePut(
267261
mapContent,
268-
(options.cacheKeys && options.cacheKeys[CacheKey.OriginalMap]) || null,
262+
cacheKeys[isOriginal ? CacheKey.OriginalMap : CacheKey.DownlevelMap],
269263
);
270-
fs.writeFileSync(options.filename + '.map', mapContent);
264+
fs.writeFileSync(filepath + '.map', mapContent);
271265
}
272266

273267
const fileResult = createFileEntry(
274-
options.filename,
268+
filepath,
275269
result.code,
276270
mapContent,
277-
options.integrityAlgorithm,
271+
integrityAlgorithm,
278272
);
279273

280274
await cachePut(
281275
result.code,
282-
(options.cacheKeys && options.cacheKeys[CacheKey.OriginalCode]) || null,
276+
cacheKeys[isOriginal ? CacheKey.OriginalCode : CacheKey.DownlevelCode],
283277
fileResult.integrity,
284278
);
285-
fs.writeFileSync(options.filename, result.code);
279+
fs.writeFileSync(filepath, result.code);
286280

287281
return fileResult;
288282
}
@@ -383,62 +377,17 @@ async function processRuntime(
383377
// Extra spacing is intentional to align source line positions
384378
downlevelCode = downlevelCode.replace(/"\-es20\d{2}\./, ' "-es5.');
385379

386-
const downlevelFilePath = options.filename.replace(/\-es20\d{2}/, '-es5');
387-
let downlevelMap;
388-
let result;
389-
if (options.optimize) {
390-
const minifiyResults = terserMangle(downlevelCode, {
391-
filename: path.basename(downlevelFilePath),
392-
map: options.map === undefined ? undefined : JSON.parse(options.map),
393-
});
394-
downlevelCode = minifiyResults.code;
395-
downlevelMap = JSON.stringify(minifiyResults.map);
396-
397-
result = {
398-
original: await mangleOriginal({ ...options, code: originalCode }),
399-
downlevel: createFileEntry(
400-
downlevelFilePath,
401-
downlevelCode,
402-
downlevelMap,
403-
options.integrityAlgorithm,
404-
),
405-
};
406-
} else {
407-
if (options.map) {
408-
const rawMap = JSON.parse(options.map) as RawSourceMap;
409-
rawMap.file = path.basename(downlevelFilePath);
410-
downlevelMap = JSON.stringify(rawMap);
411-
}
412-
413-
result = {
414-
original: createFileEntry(
415-
options.filename,
416-
originalCode,
417-
options.map,
418-
options.integrityAlgorithm,
419-
),
420-
downlevel: createFileEntry(
421-
downlevelFilePath,
422-
downlevelCode,
423-
downlevelMap,
424-
options.integrityAlgorithm,
425-
),
426-
};
427-
}
428-
429-
if (downlevelMap) {
430-
await cachePut(
431-
downlevelMap,
432-
(options.cacheKeys && options.cacheKeys[CacheKey.DownlevelMap]) || null,
433-
);
434-
fs.writeFileSync(downlevelFilePath + '.map', downlevelMap);
435-
downlevelCode += `\n//# sourceMappingURL=${path.basename(downlevelFilePath)}.map`;
436-
}
437-
await cachePut(
438-
downlevelCode,
439-
(options.cacheKeys && options.cacheKeys[CacheKey.DownlevelCode]) || null,
440-
);
441-
fs.writeFileSync(downlevelFilePath, downlevelCode);
442-
443-
return result;
380+
return {
381+
original: await processBundle({
382+
...options,
383+
code: originalCode,
384+
isOriginal: true,
385+
}),
386+
downlevel: await processBundle({
387+
...options,
388+
code: downlevelCode,
389+
filename: options.filename.replace(/\-es20\d{2}/, '-es5'),
390+
isOriginal: false,
391+
}),
392+
};
444393
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as fs from 'fs';
2+
import { ng } from '../../utils/process';
3+
4+
export default async function () {
5+
await ng('build', '--prod', '--output-hashing=none', '--source-map', 'false');
6+
await testForSourceMaps(6);
7+
8+
await ng('build', '--output-hashing=none', '--source-map', 'false');
9+
await testForSourceMaps(8);
10+
}
11+
12+
async function testForSourceMaps(expectedNumberOfFiles: number): Promise <void> {
13+
const files = fs.readdirSync('./dist/test-project');
14+
15+
let count = 0;
16+
for (const file of files) {
17+
if (!file.endsWith('.js')) {
18+
continue;
19+
}
20+
21+
++count;
22+
23+
if (files.includes(file + '.map')) {
24+
throw new Error('Sourcemap generated for ' + file);
25+
}
26+
27+
const content = fs.readFileSync('./dist/test-project/' + file, 'utf8');
28+
if (content.includes(`//# sourceMappingURL=${file}.map`)) {
29+
throw new Error('Sourcemap comment found generated for ' + file);
30+
}
31+
}
32+
33+
if (count < expectedNumberOfFiles) {
34+
throw new Error(`Javascript file count is low. Expected ${expectedNumberOfFiles} but found ${count}`);
35+
}
36+
}

0 commit comments

Comments
 (0)