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); +}