Skip to content

Commit 83c9189

Browse files
committed
fix(@angular-devkit/build-angular): inject correct SRI values in downlevel bundles
Fixes angular#15468
1 parent 4c54ad0 commit 83c9189

File tree

2 files changed

+230
-40
lines changed

2 files changed

+230
-40
lines changed

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

+105-26
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ import {
6666
normalizeSourceMaps,
6767
} from '../utils';
6868
import { manglingDisabled } from '../utils/mangle-options';
69-
import { CacheKey, ProcessBundleOptions } from '../utils/process-bundle';
69+
import { CacheKey, ProcessBundleOptions, ProcessBundleResult } from '../utils/process-bundle';
7070
import { assertCompatibleAngularVersion } from '../utils/version';
7171
import {
7272
generateBrowserWebpackConfigFromContext,
@@ -280,11 +280,12 @@ export function buildWebpackBrowser(
280280

281281
// Common options for all bundle process actions
282282
const sourceMapOptions = normalizeSourceMaps(options.sourceMap || false);
283-
const actionOptions = {
283+
const actionOptions: Partial<ProcessBundleOptions> = {
284284
optimize: normalizeOptimization(options.optimization).scripts,
285285
sourceMaps: sourceMapOptions.scripts,
286286
hiddenSourceMaps: sourceMapOptions.hidden,
287287
vendorSourceMaps: sourceMapOptions.vendor,
288+
integrityAlgorithm: options.subresourceIntegrity ? 'sha384' : undefined,
288289
};
289290

290291
const actions: ProcessBundleOptions[] = [];
@@ -314,8 +315,10 @@ export function buildWebpackBrowser(
314315
seen.add(file.file);
315316

316317
// All files at this point except ES5 polyfills are module scripts
317-
const es5Polyfills = file.file.startsWith('polyfills-es5');
318-
if (!es5Polyfills && !file.file.startsWith('polyfills-nomodule-es5')) {
318+
const es5Polyfills =
319+
file.file.startsWith('polyfills-es5') ||
320+
file.file.startsWith('polyfills-nomodule-es5');
321+
if (!es5Polyfills) {
319322
moduleFiles.push(file);
320323
}
321324
// If not optimizing then ES2015 polyfills do not need processing
@@ -350,6 +353,7 @@ export function buildWebpackBrowser(
350353
filename,
351354
code,
352355
map,
356+
name: file.name,
353357
optimizeOnly: true,
354358
});
355359

@@ -363,6 +367,7 @@ export function buildWebpackBrowser(
363367
filename,
364368
code,
365369
map,
370+
name: file.name,
366371
runtime: file.file.startsWith('runtime'),
367372
ignoreOriginal: es5Polyfills,
368373
});
@@ -378,15 +383,18 @@ export function buildWebpackBrowser(
378383
context.logger.info('Generating ES5 bundles for differential loading...');
379384

380385
const processActions: typeof actions = [];
386+
let processRuntimeAction: ProcessBundleOptions | undefined;
381387
const cacheActions: { src: string; dest: string }[] = [];
388+
const processResults: ProcessBundleResult[] = [];
382389
for (const action of actions) {
383390
// Create base cache key with elements:
384391
// * package version - different build-angular versions cause different final outputs
385392
// * code length/hash - ensure cached version matches the same input code
386-
const codeHash = createHash('sha1')
393+
const algorithm = action.integrityAlgorithm || 'sha1';
394+
const codeHash = createHash(algorithm)
387395
.update(action.code)
388-
.digest('hex');
389-
let baseCacheKey = `${packageVersion}|${action.code.length}|${codeHash}`;
396+
.digest('base64');
397+
let baseCacheKey = `${packageVersion}|${action.code.length}|${algorithm}-${codeHash}`;
390398
if (manglingDisabled) {
391399
baseCacheKey += '|MD';
392400
}
@@ -441,31 +449,86 @@ export function buildWebpackBrowser(
441449

442450
// If all required cached entries are present, use the cached entries
443451
// Otherwise process the files
444-
if (cached) {
445-
if (cacheEntries[CacheKey.OriginalCode]) {
446-
cacheActions.push({
447-
src: cacheEntries[CacheKey.OriginalCode].path,
448-
dest: action.filename,
449-
});
452+
// If SRI is enabled always process the runtime bundle
453+
// Lazy route integrity values are stored in the runtime bundle
454+
if (action.integrityAlgorithm && action.runtime) {
455+
processRuntimeAction = action;
456+
} else if (cached) {
457+
const result: ProcessBundleResult = { name: action.name };
458+
if (action.integrityAlgorithm) {
459+
result.integrity = `${action.integrityAlgorithm}-${codeHash}`;
450460
}
451-
if (cacheEntries[CacheKey.OriginalMap]) {
461+
462+
let cacheEntry = cacheEntries[CacheKey.OriginalCode];
463+
if (cacheEntry) {
452464
cacheActions.push({
453-
src: cacheEntries[CacheKey.OriginalMap].path,
454-
dest: action.filename + '.map',
465+
src: cacheEntry.path,
466+
dest: action.filename,
455467
});
468+
result.original = {
469+
filename: action.filename,
470+
size: cacheEntry.size,
471+
integrity: cacheEntry.metadata && cacheEntry.metadata.integrity,
472+
};
473+
474+
cacheEntry = cacheEntries[CacheKey.OriginalMap];
475+
if (cacheEntry) {
476+
cacheActions.push({
477+
src: cacheEntry.path,
478+
dest: action.filename + '.map',
479+
});
480+
result.original.map = {
481+
filename: action.filename + '.map',
482+
size: cacheEntry.size,
483+
};
484+
}
485+
} else if (!action.ignoreOriginal) {
486+
// If the original wasn't processed (and therefore not cached), add info
487+
result.original = {
488+
filename: action.filename,
489+
size: Buffer.byteLength(action.code, 'utf8'),
490+
map:
491+
action.map === undefined
492+
? undefined
493+
: {
494+
filename: action.filename + '.map',
495+
size: Buffer.byteLength(action.map, 'utf8'),
496+
},
497+
};
456498
}
457-
if (cacheEntries[CacheKey.DownlevelCode]) {
499+
500+
cacheEntry = cacheEntries[CacheKey.DownlevelCode];
501+
if (cacheEntry) {
458502
cacheActions.push({
459-
src: cacheEntries[CacheKey.DownlevelCode].path,
503+
src: cacheEntry.path,
460504
dest: action.filename.replace('es2015', 'es5'),
461505
});
506+
result.downlevel = {
507+
filename: action.filename.replace('es2015', 'es5'),
508+
size: cacheEntry.size,
509+
integrity: cacheEntry.metadata && cacheEntry.metadata.integrity,
510+
};
511+
512+
cacheEntry = cacheEntries[CacheKey.DownlevelMap];
513+
if (cacheEntry) {
514+
cacheActions.push({
515+
src: cacheEntry.path,
516+
dest: action.filename.replace('es2015', 'es5') + '.map',
517+
});
518+
result.downlevel.map = {
519+
filename: action.filename.replace('es2015', 'es5') + '.map',
520+
size: cacheEntry.size,
521+
};
522+
}
462523
}
463-
if (cacheEntries[CacheKey.DownlevelMap]) {
464-
cacheActions.push({
465-
src: cacheEntries[CacheKey.DownlevelMap].path,
466-
dest: action.filename.replace('es2015', 'es5') + '.map',
467-
});
468-
}
524+
525+
processResults.push(result);
526+
} else if (action.runtime) {
527+
processRuntimeAction = {
528+
...action,
529+
cacheKeys,
530+
cachePath: cacheDownlevelPath || undefined,
531+
};
469532
} else {
470533
processActions.push({
471534
...action,
@@ -517,11 +580,16 @@ export function buildWebpackBrowser(
517580
['process'],
518581
);
519582
let completed = 0;
520-
const workCallback = (error: Error | null) => {
583+
const workCallback = (error: Error | null, result: ProcessBundleResult) => {
521584
if (error) {
522585
workerFarm.end(workers);
523586
reject(error);
524-
} else if (++completed === processActions.length) {
587+
588+
return;
589+
}
590+
591+
processResults.push(result);
592+
if (++completed === processActions.length) {
525593
workerFarm.end(workers);
526594
resolve();
527595
}
@@ -531,6 +599,17 @@ export function buildWebpackBrowser(
531599
});
532600
}
533601

602+
// Runtime must be processed after all other files
603+
if (processRuntimeAction) {
604+
const runtimeOptions = {
605+
...processRuntimeAction,
606+
runtimeData: processResults,
607+
};
608+
processResults.push(
609+
await import('../utils/process-bundle').then(m => m.processAsync(runtimeOptions)),
610+
);
611+
}
612+
534613
context.logger.info('ES5 bundle generation complete.');
535614
} else {
536615
const { emittedFiles = [] } = firstBuild;

0 commit comments

Comments
 (0)