From 1ffdaba94bf6546cd465210131b65b08c5fbe4f7 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 15 Oct 2020 18:00:04 -0400 Subject: [PATCH] fix(@angular-devkit/build-angular): ensure correct SRI values with differential loading Previously, the cached integrity values for a subsequent differential loading build would not be properly integrated. This resulted in builds with incorrect integrity values after an initial build. The cached differential loading builds will now use the correct integrity values on subsequent builds. Closes #18254 --- .../build_angular/src/utils/action-cache.ts | 21 ++++-- .../tests/build/differential-loading-sri.ts | 73 +++++++++++++++++++ 2 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 tests/legacy-cli/e2e/tests/build/differential-loading-sri.ts diff --git a/packages/angular_devkit/build_angular/src/utils/action-cache.ts b/packages/angular_devkit/build_angular/src/utils/action-cache.ts index 9a2fd8f0a407..6aa1b4c9afa6 100644 --- a/packages/angular_devkit/build_angular/src/utils/action-cache.ts +++ b/packages/angular_devkit/build_angular/src/utils/action-cache.ts @@ -33,15 +33,21 @@ export class BundleActionCache { } } - generateBaseCacheKey(content: string): string { - // Create base cache key with elements: - // * package version - different build-angular versions cause different final outputs - // * code length/hash - ensure cached version matches the same input code + generateIntegrityValue(content: string): string { const algorithm = this.integrityAlgorithm || 'sha1'; const codeHash = createHash(algorithm) .update(content) .digest('base64'); - let baseCacheKey = `${packageVersion}|${content.length}|${algorithm}-${codeHash}`; + + return `${algorithm}-${codeHash}`; + } + + generateBaseCacheKey(content: string): string { + // Create base cache key with elements: + // * package version - different build-angular versions cause different final outputs + // * code length/hash - ensure cached version matches the same input code + const integrity = this.generateIntegrityValue(content); + let baseCacheKey = `${packageVersion}|${content.length}|${integrity}`; if (!allowMangle) { baseCacheKey += '|MD'; } @@ -116,7 +122,10 @@ export class BundleActionCache { return null; } - const result: ProcessBundleResult = { name: action.name }; + const result: ProcessBundleResult = { + name: action.name, + integrity: this.generateIntegrityValue(action.code), + }; let cacheEntry = entries[CacheKey.OriginalCode]; if (cacheEntry) { diff --git a/tests/legacy-cli/e2e/tests/build/differential-loading-sri.ts b/tests/legacy-cli/e2e/tests/build/differential-loading-sri.ts new file mode 100644 index 000000000000..ab14256fbedd --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/differential-loading-sri.ts @@ -0,0 +1,73 @@ +import { createHash } from 'crypto'; +import { + appendToFile, + expectFileToMatch, + prependToFile, + readFile, + replaceInFile, + writeFile, +} from '../../utils/fs'; +import { ng } from '../../utils/process'; + +export default async function () { + // Enable Differential loading + await replaceInFile('.browserslistrc', 'not IE 11', 'IE 11'); + + const appRoutingModulePath = 'src/app/app-routing.module.ts'; + + // Add app routing. + // This is done automatically on a new app with --routing. + await writeFile( + appRoutingModulePath, + ` + import { NgModule } from '@angular/core'; + import { Routes, RouterModule } from '@angular/router'; + + const routes: Routes = []; + + @NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] + }) + export class AppRoutingModule { } + `, + ); + await prependToFile( + 'src/app/app.module.ts', + `import { AppRoutingModule } from './app-routing.module';`, + ); + await replaceInFile('src/app/app.module.ts', `imports: [`, `imports: [ AppRoutingModule,`); + await appendToFile('src/app/app.component.html', ''); + + await ng('generate', 'module', 'lazy', '--module=app.module', '--route', 'lazy'); + + await ng( + 'build', + '--prod', + '--subresource-integrity', + '--output-hashing=none', + '--output-path=dist/first', + ); + + // Second build used to ensure cached files use correct integrity values + await ng( + 'build', + '--prod', + '--subresource-integrity', + '--output-hashing=none', + '--output-path=dist/second', + ); + + const codeHashES5 = createHash('sha384') + .update(await readFile('dist/first/5-es5.js')) + .digest('base64'); + const codeHashES2015 = createHash('sha384') + .update(await readFile('dist/first/5-es2015.js')) + .digest('base64'); + + await expectFileToMatch('dist/first/runtime-es5.js', 'sha384-' + codeHashES5); + await expectFileToMatch('dist/first/runtime-es2015.js', 'sha384-' + codeHashES2015); + + await expectFileToMatch('dist/second/runtime-es5.js', 'sha384-' + codeHashES5); + await expectFileToMatch('dist/second/runtime-es2015.js', 'sha384-' + codeHashES2015); +}