From 00f2aa724021d0ced312dd057575de481d6f80a0 Mon Sep 17 00:00:00 2001 From: Charles Lyding Date: Tue, 14 Feb 2017 13:39:00 -0500 Subject: [PATCH] fix(@angular/cli): stabilize webpack module identifiers --- .../cli/models/webpack-configs/production.ts | 1 + packages/@angular/cli/tasks/eject.ts | 7 +- tests/e2e/tests/build/chunk-hash.ts | 104 ++++++++++++------ 3 files changed, 78 insertions(+), 34 deletions(-) diff --git a/packages/@angular/cli/models/webpack-configs/production.ts b/packages/@angular/cli/models/webpack-configs/production.ts index 383ed29a4c7d..8a20468a6b75 100644 --- a/packages/@angular/cli/models/webpack-configs/production.ts +++ b/packages/@angular/cli/models/webpack-configs/production.ts @@ -70,6 +70,7 @@ export const getProdConfig = function (wco: WebpackConfigOptions) { new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }), + new (webpack).HashedModuleIdsPlugin(), new webpack.optimize.UglifyJsPlugin({ mangle: { screw_ie8: true }, compress: { screw_ie8: true, warnings: buildOptions.verbose }, diff --git a/packages/@angular/cli/tasks/eject.ts b/packages/@angular/cli/tasks/eject.ts index caa891004532..3445f212fe93 100644 --- a/packages/@angular/cli/tasks/eject.ts +++ b/packages/@angular/cli/tasks/eject.ts @@ -165,6 +165,9 @@ class JsonWebpackSerializer { case webpack.NoEmitOnErrorsPlugin: this._addImport('webpack', 'NoEmitOnErrorsPlugin'); break; + case (webpack).HashedModuleIdsPlugin: + this._addImport('webpack', 'HashedModuleIdsPlugin'); + break; case webpack.optimize.UglifyJsPlugin: this._addImport('webpack.optimize', 'UglifyJsPlugin'); break; @@ -487,13 +490,13 @@ export default Task.extend({ console.log(yellow(stripIndent` ========================================================================================== Ejection was successful. - + To run your builds, you now need to do the following commands: - "npm run build" to build. - "npm run test" to run unit tests. - "npm start" to serve the app using webpack-dev-server. - "npm run e2e" to run protractor. - + Running the equivalent CLI commands will result in an error. ========================================================================================== diff --git a/tests/e2e/tests/build/chunk-hash.ts b/tests/e2e/tests/build/chunk-hash.ts index 9e79a7e52b50..fab96641315d 100644 --- a/tests/e2e/tests/build/chunk-hash.ts +++ b/tests/e2e/tests/build/chunk-hash.ts @@ -5,52 +5,92 @@ import {ng} from '../../utils/process'; import {writeFile} from '../../utils/fs'; import {addImportToModule} from '../../utils/ast'; +const OUTPUT_RE = /(main|polyfills|vendor|inline|styles|\d+)\.[a-z0-9]+\.(chunk|bundle)\.(js|css)$/; + +function generateFileHashMap(): Map { + const hashes = new Map(); + + fs.readdirSync('./dist') + .forEach(name => { + if (!name.match(OUTPUT_RE)) { + return; + } + + const [module, hash] = name.split('.'); + hashes.set(module, hash); + }); + + return hashes; +} + +function validateHashes( + oldHashes: Map, + newHashes: Map, + shouldChange: Array): void { + + console.log(' Validating hashes...'); + console.log(` Old hashes: ${JSON.stringify([...oldHashes])}`); + console.log(` New hashes: ${JSON.stringify([...newHashes])}`); + + oldHashes.forEach((hash, module) => { + if (hash == newHashes.get(module)) { + if (shouldChange.includes(module)) { + throw new Error(`Module "${module}" did not change hash (${hash})...`); + } + } else if (!shouldChange.includes(module)) { + throw new Error(`Module "${module}" changed hash (${hash})...`); + } + }); +} export default function() { - const oldHashes: {[module: string]: string} = {}; - const newHashes: {[module: string]: string} = {}; + let oldHashes: Map; + let newHashes: Map; // First, collect the hashes. return Promise.resolve() .then(() => ng('generate', 'module', 'lazy', '--routing')) .then(() => addImportToModule('src/app/app.module.ts', oneLine` RouterModule.forRoot([{ path: "lazy", loadChildren: "./lazy/lazy.module#LazyModule" }]) `, '@angular/router')) + .then(() => addImportToModule( + 'src/app/app.module.ts', 'ReactiveFormsModule', '@angular/forms')) .then(() => ng('build', '--prod')) .then(() => { - fs.readdirSync('./dist') - .forEach(name => { - if (!name.match(/(main|inline|styles|\d+)\.[a-z0-9]+\.bundle\.(js|css)/)) { - return; - } - - const [module, hash] = name.split('.'); - oldHashes[module] = hash; - }); + oldHashes = generateFileHashMap(); }) - .then(() => writeFile('src/app/app.component.css', 'h1 { margin: 5px; }')) - .then(() => writeFile('src/styles.css', 'body { background: red; }')) .then(() => ng('build', '--prod')) .then(() => { - fs.readdirSync('./dist') - .forEach(name => { - if (!name.match(/(main|inline|styles|\d+)\.[a-z0-9]+\.bundle\.(js|css)/)) { - return; - } - - const [module, hash] = name.split('.'); - newHashes[module] = hash; - }); + newHashes = generateFileHashMap(); }) .then(() => { - console.log(' Validating hashes...'); - console.log(` Old hashes: ${JSON.stringify(oldHashes)}`); - console.log(` New hashes: ${JSON.stringify(newHashes)}`); - - Object.keys(oldHashes) - .forEach(module => { - if (oldHashes[module] == newHashes[module]) { - throw new Error(`Module "${module}" did not change hash (${oldHashes[module]})...`); - } - }); + validateHashes(oldHashes, newHashes, []); + oldHashes = newHashes; + }) + .then(() => writeFile('src/styles.css', 'body { background: blue; }')) + .then(() => ng('build', '--prod')) + .then(() => { + newHashes = generateFileHashMap(); + }) + .then(() => { + validateHashes(oldHashes, newHashes, ['styles']); + oldHashes = newHashes; + }) + .then(() => writeFile('src/app/app.component.css', 'h1 { margin: 10px; }')) + .then(() => ng('build', '--prod')) + .then(() => { + newHashes = generateFileHashMap(); + }) + .then(() => { + validateHashes(oldHashes, newHashes, ['inline', 'main']); + oldHashes = newHashes; + }) + .then(() => addImportToModule( + 'src/app/lazy/lazy.module.ts', 'ReactiveFormsModule', '@angular/forms')) + .then(() => ng('build', '--prod')) + .then(() => { + newHashes = generateFileHashMap(); + }) + .then(() => { + validateHashes(oldHashes, newHashes, ['inline', '0']); }); }