Skip to content

Commit fda8800

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

File tree

2 files changed

+238
-41
lines changed

2 files changed

+238
-41
lines changed

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

+105-26
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ import {
6464
normalizeSourceMaps,
6565
} from '../utils';
6666
import { manglingDisabled } from '../utils/mangle-options';
67-
import { CacheKey, ProcessBundleOptions } from '../utils/process-bundle';
67+
import { CacheKey, ProcessBundleOptions, ProcessBundleResult } from '../utils/process-bundle';
6868
import { assertCompatibleAngularVersion } from '../utils/version';
6969
import {
7070
generateBrowserWebpackConfigFromContext,
@@ -269,11 +269,12 @@ export function buildWebpackBrowser(
269269

270270
// Common options for all bundle process actions
271271
const sourceMapOptions = normalizeSourceMaps(options.sourceMap || false);
272-
const actionOptions = {
272+
const actionOptions: Partial<ProcessBundleOptions> = {
273273
optimize: normalizeOptimization(options.optimization).scripts,
274274
sourceMaps: sourceMapOptions.scripts,
275275
hiddenSourceMaps: sourceMapOptions.hidden,
276276
vendorSourceMaps: sourceMapOptions.vendor,
277+
integrityAlgorithm: options.subresourceIntegrity ? 'sha384' : undefined,
277278
};
278279

279280
const actions: ProcessBundleOptions[] = [];
@@ -303,8 +304,10 @@ export function buildWebpackBrowser(
303304
seen.add(file.file);
304305

305306
// All files at this point except ES5 polyfills are module scripts
306-
const es5Polyfills = file.file.startsWith('polyfills-es5');
307-
if (!es5Polyfills && !file.file.startsWith('polyfills-nomodule-es5')) {
307+
const es5Polyfills =
308+
file.file.startsWith('polyfills-es5') ||
309+
file.file.startsWith('polyfills-nomodule-es5');
310+
if (!es5Polyfills) {
308311
moduleFiles.push(file);
309312
}
310313
// If not optimizing then ES2015 polyfills do not need processing
@@ -339,6 +342,7 @@ export function buildWebpackBrowser(
339342
filename,
340343
code,
341344
map,
345+
name: file.name,
342346
optimizeOnly: true,
343347
});
344348

@@ -352,6 +356,7 @@ export function buildWebpackBrowser(
352356
filename,
353357
code,
354358
map,
359+
name: file.name,
355360
runtime: file.file.startsWith('runtime'),
356361
ignoreOriginal: es5Polyfills,
357362
});
@@ -367,15 +372,18 @@ export function buildWebpackBrowser(
367372
context.logger.info('Generating ES5 bundles for differential loading...');
368373

369374
const processActions: typeof actions = [];
375+
let processRuntimeAction: ProcessBundleOptions | undefined;
370376
const cacheActions: { src: string; dest: string }[] = [];
377+
const processResults: ProcessBundleResult[] = [];
371378
for (const action of actions) {
372379
// Create base cache key with elements:
373380
// * package version - different build-angular versions cause different final outputs
374381
// * code length/hash - ensure cached version matches the same input code
375-
const codeHash = createHash('sha1')
382+
const algorithm = action.integrityAlgorithm || 'sha1';
383+
const codeHash = createHash(algorithm)
376384
.update(action.code)
377-
.digest('hex');
378-
let baseCacheKey = `${packageVersion}|${action.code.length}|${codeHash}`;
385+
.digest('base64');
386+
let baseCacheKey = `${packageVersion}|${action.code.length}|${algorithm}-${codeHash}`;
379387
if (manglingDisabled) {
380388
baseCacheKey += '|MD';
381389
}
@@ -430,31 +438,86 @@ export function buildWebpackBrowser(
430438

431439
// If all required cached entries are present, use the cached entries
432440
// Otherwise process the files
433-
if (cached) {
434-
if (cacheEntries[CacheKey.OriginalCode]) {
435-
cacheActions.push({
436-
src: cacheEntries[CacheKey.OriginalCode].path,
437-
dest: action.filename,
438-
});
441+
// If SRI is enabled always process the runtime bundle
442+
// Lazy route integrity values are stored in the runtime bundle
443+
if (action.integrityAlgorithm && action.runtime) {
444+
processRuntimeAction = action;
445+
} else if (cached) {
446+
const result: ProcessBundleResult = { name: action.name };
447+
if (action.integrityAlgorithm) {
448+
result.integrity = `${action.integrityAlgorithm}-${codeHash}`;
439449
}
440-
if (cacheEntries[CacheKey.OriginalMap]) {
450+
451+
let cacheEntry = cacheEntries[CacheKey.OriginalCode];
452+
if (cacheEntry) {
441453
cacheActions.push({
442-
src: cacheEntries[CacheKey.OriginalMap].path,
443-
dest: action.filename + '.map',
454+
src: cacheEntry.path,
455+
dest: action.filename,
444456
});
457+
result.original = {
458+
filename: action.filename,
459+
size: cacheEntry.size,
460+
integrity: cacheEntry.metadata && cacheEntry.metadata.integrity,
461+
};
462+
463+
cacheEntry = cacheEntries[CacheKey.OriginalMap];
464+
if (cacheEntry) {
465+
cacheActions.push({
466+
src: cacheEntry.path,
467+
dest: action.filename + '.map',
468+
});
469+
result.original.map = {
470+
filename: action.filename + '.map',
471+
size: cacheEntry.size,
472+
};
473+
}
474+
} else if (!action.ignoreOriginal) {
475+
// If the original wasn't processed (and therefore not cached), add info
476+
result.original = {
477+
filename: action.filename,
478+
size: Buffer.byteLength(action.code, 'utf8'),
479+
map:
480+
action.map === undefined
481+
? undefined
482+
: {
483+
filename: action.filename + '.map',
484+
size: Buffer.byteLength(action.map, 'utf8'),
485+
},
486+
};
445487
}
446-
if (cacheEntries[CacheKey.DownlevelCode]) {
488+
489+
cacheEntry = cacheEntries[CacheKey.DownlevelCode];
490+
if (cacheEntry) {
447491
cacheActions.push({
448-
src: cacheEntries[CacheKey.DownlevelCode].path,
492+
src: cacheEntry.path,
449493
dest: action.filename.replace('es2015', 'es5'),
450494
});
495+
result.downlevel = {
496+
filename: action.filename.replace('es2015', 'es5'),
497+
size: cacheEntry.size,
498+
integrity: cacheEntry.metadata && cacheEntry.metadata.integrity,
499+
};
500+
501+
cacheEntry = cacheEntries[CacheKey.DownlevelMap];
502+
if (cacheEntry) {
503+
cacheActions.push({
504+
src: cacheEntry.path,
505+
dest: action.filename.replace('es2015', 'es5') + '.map',
506+
});
507+
result.downlevel.map = {
508+
filename: action.filename.replace('es2015', 'es5') + '.map',
509+
size: cacheEntry.size,
510+
};
511+
}
451512
}
452-
if (cacheEntries[CacheKey.DownlevelMap]) {
453-
cacheActions.push({
454-
src: cacheEntries[CacheKey.DownlevelMap].path,
455-
dest: action.filename.replace('es2015', 'es5') + '.map',
456-
});
457-
}
513+
514+
processResults.push(result);
515+
} else if (action.runtime) {
516+
processRuntimeAction = {
517+
...action,
518+
cacheKeys,
519+
cachePath: cacheDownlevelPath || undefined,
520+
};
458521
} else {
459522
processActions.push({
460523
...action,
@@ -506,11 +569,16 @@ export function buildWebpackBrowser(
506569
['process'],
507570
);
508571
let completed = 0;
509-
const workCallback = (error: Error | null) => {
572+
const workCallback = (error: Error | null, result: ProcessBundleResult) => {
510573
if (error) {
511574
workerFarm.end(workers);
512575
reject(error);
513-
} else if (++completed === processActions.length) {
576+
577+
return;
578+
}
579+
580+
processResults.push(result);
581+
if (++completed === processActions.length) {
514582
workerFarm.end(workers);
515583
resolve();
516584
}
@@ -520,6 +588,17 @@ export function buildWebpackBrowser(
520588
});
521589
}
522590

591+
// Runtime must be processed after all other files
592+
if (processRuntimeAction) {
593+
const runtimeOptions = {
594+
...processRuntimeAction,
595+
runtimeData: processResults,
596+
};
597+
processResults.push(
598+
await import('../utils/process-bundle').then(m => m.processAsync(runtimeOptions)),
599+
);
600+
}
601+
523602
context.logger.info('ES5 bundle generation complete.');
524603
} else {
525604
const { emittedFiles = [] } = firstBuild;

0 commit comments

Comments
 (0)