Skip to content

Commit f08d79b

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 0248f1d commit f08d79b

File tree

5 files changed

+139
-148
lines changed

5 files changed

+139
-148
lines changed

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -390,12 +390,6 @@ export function buildWebpackBrowser(
390390
if (!es5Polyfills) {
391391
moduleFiles.push(file);
392392
}
393-
// If not optimizing then ES2015 polyfills do not need processing
394-
// Unlike other module scripts, it is never downleveled
395-
const es2015Polyfills = file.file.startsWith('polyfills-es20');
396-
if (!actionOptions.optimize && es2015Polyfills) {
397-
continue;
398-
}
399393

400394
// Retrieve the content/map for the file
401395
// NOTE: Additional future optimizations will read directly from memory
@@ -417,6 +411,8 @@ export function buildWebpackBrowser(
417411
filename = filename.replace(/\-es20\d{2}/, '');
418412
}
419413

414+
const es2015Polyfills = file.file.startsWith('polyfills-es20');
415+
420416
// Record the bundle processing action
421417
// The runtime chunk gets special processing for lazy loaded files
422418
actions.push({

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

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

5252
generateCacheKeys(action: ProcessBundleOptions): string[] {
53-
const baseCacheKey = this.generateBaseCacheKey(action.code);
54-
55-
// Postfix added to sourcemap cache keys when vendor sourcemaps are present
53+
// Postfix added to sourcemap cache keys when vendor, hidden sourcemaps are present
5654
// Allows non-destructive caching of both variants
57-
const SourceMapVendorPostfix = !!action.sourceMaps && action.vendorSourceMaps ? '|vendor' : '';
55+
const sourceMapVendorPostfix = action.sourceMaps && action.vendorSourceMaps ? '|vendor' : '';
56+
57+
// sourceMappingURL is added at the very end which causes the code to be the same when sourcemaps are enabled/disabled
58+
// When using hiddenSourceMaps we can omit the postfix since sourceMappingURL will not be added.
59+
const sourceMapPostFix = action.sourceMaps && !action.hiddenSourceMaps ? '|sourcemap' : '';
60+
61+
const baseCacheKey = this.generateBaseCacheKey(action.code);
5862

5963
// Determine cache entries required based on build settings
60-
const cacheKeys = [];
64+
const cacheKeys: string[] = [];
6165

6266
// If optimizing and the original is not ignored, add original as required
63-
if ((action.optimize || action.optimizeOnly) && !action.ignoreOriginal) {
64-
cacheKeys[CacheKey.OriginalCode] = baseCacheKey + '|orig';
67+
if (!action.ignoreOriginal) {
68+
cacheKeys[CacheKey.OriginalCode] = baseCacheKey + sourceMapPostFix + '|orig';
6569

6670
// If sourcemaps are enabled, add original sourcemap as required
6771
if (action.sourceMaps) {
68-
cacheKeys[CacheKey.OriginalMap] = baseCacheKey + SourceMapVendorPostfix + '|orig-map';
72+
cacheKeys[CacheKey.OriginalMap] = baseCacheKey + sourceMapVendorPostfix + '|orig-map';
6973
}
7074
}
75+
7176
// If not only optimizing, add downlevel as required
7277
if (!action.optimizeOnly) {
73-
cacheKeys[CacheKey.DownlevelCode] = baseCacheKey + '|dl';
78+
cacheKeys[CacheKey.DownlevelCode] = baseCacheKey + sourceMapPostFix + '|dl';
7479

7580
// If sourcemaps are enabled, add downlevel sourcemap as required
7681
if (action.sourceMaps) {
77-
cacheKeys[CacheKey.DownlevelMap] = baseCacheKey + SourceMapVendorPostfix + '|dl-map';
82+
cacheKeys[CacheKey.DownlevelMap] = baseCacheKey + sourceMapVendorPostfix + '|dl-map';
7883
}
7984
}
8085

8186
return cacheKeys;
8287
}
8388

84-
async getCacheEntries(cacheKeys: (string | null)[]): Promise<(CacheEntry | null)[] | false> {
89+
async getCacheEntries(cacheKeys: (string | undefined)[]): Promise<(CacheEntry | null)[] | false> {
8590
// Attempt to get required cache entries
8691
const cacheEntries = [];
8792
for (const key of cacheKeys) {

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

Lines changed: 75 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export interface ProcessBundleOptions {
3939
optimize?: boolean;
4040
optimizeOnly?: boolean;
4141
ignoreOriginal?: boolean;
42-
cacheKeys?: (string | null)[];
42+
cacheKeys?: (string | undefined)[];
4343
integrityAlgorithm?: 'sha256' | 'sha384' | 'sha512';
4444
runtimeData?: ProcessBundleResult[];
4545
replacements?: [string, string][];
@@ -80,9 +80,9 @@ export function setup(data: number[] | { cachePath: string; i18n: I18nOptions })
8080
i18n = options.i18n;
8181
}
8282

83-
async function cachePut(content: string, key: string | null, integrity?: string): Promise<void> {
83+
async function cachePut(content: string, key: string | undefined, integrity?: string): Promise<void> {
8484
if (cachePath && key) {
85-
await cacache.put(cachePath, key, content, {
85+
await cacache.put(cachePath, key || null, content, {
8686
metadata: { integrity },
8787
});
8888
}
@@ -114,7 +114,6 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
114114
const codeSize = Buffer.byteLength(options.code);
115115
const mapSize = options.map ? Buffer.byteLength(options.map) : 0;
116116
const manualSourceMaps = codeSize >= 500 * 1024 || mapSize >= 500 * 1024;
117-
118117
const sourceCode = options.code;
119118
const sourceMap = options.map ? JSON.parse(options.map) : undefined;
120119

@@ -155,59 +154,21 @@ export async function process(options: ProcessBundleOptions): Promise<ProcessBun
155154
}
156155
}
157156

158-
if (options.optimize) {
159-
if (downlevelCode) {
160-
const minifyResult = terserMangle(downlevelCode, {
161-
filename: downlevelFilename,
162-
map: downlevelMap,
163-
compress: true,
164-
});
165-
downlevelCode = minifyResult.code;
166-
downlevelMap = minifyResult.map;
167-
}
168-
169-
if (!options.ignoreOriginal) {
170-
result.original = await mangleOriginal(options);
171-
}
172-
}
173-
174157
if (downlevelCode) {
175-
const downlevelPath = path.join(basePath, downlevelFilename);
176-
177-
let mapContent;
178-
if (downlevelMap) {
179-
if (!options.hiddenSourceMaps) {
180-
downlevelCode += `\n//# sourceMappingURL=${downlevelFilename}.map`;
181-
}
182-
183-
mapContent = JSON.stringify(downlevelMap);
184-
await cachePut(mapContent, options.cacheKeys[CacheKey.DownlevelMap]);
185-
fs.writeFileSync(downlevelPath + '.map', mapContent);
186-
}
187-
188-
result.downlevel = createFileEntry(
189-
path.join(basePath, downlevelFilename),
190-
downlevelCode,
191-
mapContent,
192-
options.integrityAlgorithm,
193-
);
194-
195-
await cachePut(
196-
downlevelCode,
197-
options.cacheKeys[CacheKey.DownlevelCode],
198-
result.downlevel.integrity,
199-
);
200-
fs.writeFileSync(downlevelPath, downlevelCode);
158+
result.downlevel = await processBundle({
159+
...options,
160+
code: downlevelCode,
161+
map: downlevelMap,
162+
filename: path.join(basePath, downlevelFilename),
163+
isOriginal: false,
164+
});
201165
}
202166

203-
// If original was not processed, add info
204167
if (!result.original && !options.ignoreOriginal) {
205-
result.original = createFileEntry(
206-
options.filename,
207-
options.code,
208-
options.map,
209-
options.integrityAlgorithm,
210-
);
168+
result.original = await processBundle({
169+
...options,
170+
isOriginal: true,
171+
});
211172
}
212173

213174
return result;
@@ -286,41 +247,74 @@ async function mergeSourceMapsFast(first: RawSourceMap, second: RawSourceMap) {
286247
return map;
287248
}
288249

289-
async function mangleOriginal(options: ProcessBundleOptions): Promise<ProcessBundleFile> {
290-
const result = terserMangle(options.code, {
291-
filename: path.basename(options.filename),
292-
map: options.map ? JSON.parse(options.map) : undefined,
293-
ecma: 6,
294-
});
250+
async function processBundle(
251+
options: Omit<ProcessBundleOptions, 'map'> & { isOriginal: boolean; map?: string | RawSourceMap },
252+
): Promise<ProcessBundleFile> {
253+
const {
254+
optimize,
255+
isOriginal,
256+
code,
257+
map,
258+
filename: filepath,
259+
hiddenSourceMaps,
260+
cacheKeys = [],
261+
integrityAlgorithm,
262+
} = options;
263+
264+
const rawMap = typeof map === 'string' ? JSON.parse(map) as RawSourceMap : map;
265+
const filename = path.basename(filepath);
266+
267+
let result: {
268+
code: string,
269+
map: RawSourceMap | undefined,
270+
};
271+
272+
if (optimize) {
273+
result = terserMangle(code, {
274+
filename,
275+
map: rawMap,
276+
compress: !isOriginal, // We only compress bundles which are downlevelled.
277+
ecma: isOriginal ? 6 : 5,
278+
});
279+
} else {
280+
if (rawMap) {
281+
rawMap.file = filename;
282+
}
295283

296-
let mapContent;
284+
result = {
285+
map: rawMap,
286+
code,
287+
};
288+
}
289+
290+
let mapContent: string | undefined;
297291
if (result.map) {
298-
if (!options.hiddenSourceMaps) {
299-
result.code += `\n//# sourceMappingURL=${path.basename(options.filename)}.map`;
292+
if (!hiddenSourceMaps) {
293+
result.code += `\n//# sourceMappingURL=${filename}.map`;
300294
}
301295

302296
mapContent = JSON.stringify(result.map);
303297

304298
await cachePut(
305299
mapContent,
306-
(options.cacheKeys && options.cacheKeys[CacheKey.OriginalMap]) || null,
300+
cacheKeys[isOriginal ? CacheKey.OriginalMap : CacheKey.DownlevelMap],
307301
);
308-
fs.writeFileSync(options.filename + '.map', mapContent);
302+
fs.writeFileSync(filepath + '.map', mapContent);
309303
}
310304

311305
const fileResult = createFileEntry(
312-
options.filename,
306+
filepath,
313307
result.code,
314308
mapContent,
315-
options.integrityAlgorithm,
309+
integrityAlgorithm,
316310
);
317311

318312
await cachePut(
319313
result.code,
320-
(options.cacheKeys && options.cacheKeys[CacheKey.OriginalCode]) || null,
314+
cacheKeys[isOriginal ? CacheKey.OriginalCode : CacheKey.DownlevelCode],
321315
fileResult.integrity,
322316
);
323-
fs.writeFileSync(options.filename, result.code);
317+
fs.writeFileSync(filepath, result.code);
324318

325319
return fileResult;
326320
}
@@ -421,66 +415,19 @@ async function processRuntime(
421415
// Extra spacing is intentional to align source line positions
422416
downlevelCode = downlevelCode.replace(/"\-es20\d{2}\./, ' "-es5.');
423417

424-
const downlevelFilePath = options.filename.replace(/\-es20\d{2}/, '-es5');
425-
let downlevelMap;
426-
let result;
427-
if (options.optimize) {
428-
const minifiyResults = terserMangle(downlevelCode, {
429-
filename: path.basename(downlevelFilePath),
430-
map: options.map === undefined ? undefined : JSON.parse(options.map),
431-
});
432-
downlevelCode = minifiyResults.code;
433-
downlevelMap = JSON.stringify(minifiyResults.map);
434-
435-
result = {
436-
original: await mangleOriginal({ ...options, code: originalCode }),
437-
downlevel: createFileEntry(
438-
downlevelFilePath,
439-
downlevelCode,
440-
downlevelMap,
441-
options.integrityAlgorithm,
442-
),
443-
};
444-
} else {
445-
if (options.map) {
446-
const rawMap = JSON.parse(options.map) as RawSourceMap;
447-
rawMap.file = path.basename(downlevelFilePath);
448-
downlevelMap = JSON.stringify(rawMap);
449-
}
450-
451-
result = {
452-
original: createFileEntry(
453-
options.filename,
454-
originalCode,
455-
options.map,
456-
options.integrityAlgorithm,
457-
),
458-
downlevel: createFileEntry(
459-
downlevelFilePath,
460-
downlevelCode,
461-
downlevelMap,
462-
options.integrityAlgorithm,
463-
),
464-
};
465-
}
466-
467-
if (downlevelMap) {
468-
await cachePut(
469-
downlevelMap,
470-
(options.cacheKeys && options.cacheKeys[CacheKey.DownlevelMap]) || null,
471-
);
472-
fs.writeFileSync(downlevelFilePath + '.map', downlevelMap);
473-
if (!options.hiddenSourceMaps) {
474-
downlevelCode += `\n//# sourceMappingURL=${path.basename(downlevelFilePath)}.map`;
475-
}
476-
}
477-
await cachePut(
478-
downlevelCode,
479-
(options.cacheKeys && options.cacheKeys[CacheKey.DownlevelCode]) || null,
480-
);
481-
fs.writeFileSync(downlevelFilePath, downlevelCode);
482-
483-
return result;
418+
return {
419+
original: await processBundle({
420+
...options,
421+
code: originalCode,
422+
isOriginal: true,
423+
}),
424+
downlevel: await processBundle({
425+
...options,
426+
code: downlevelCode,
427+
filename: options.filename.replace(/\-es20\d{2}/, '-es5'),
428+
isOriginal: false,
429+
}),
430+
};
484431
}
485432

486433
function createReplacePlugin(replacements: [string, string][]): PluginObj {
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)