Skip to content

Commit

Permalink
fix(@angular/cli): stabilize webpack module identifiers
Browse files Browse the repository at this point in the history
This provides increased viability for long-term caching by limiting the
changes to the content of output files due to webpack module identifier
changes.

Close #4733
  • Loading branch information
Charles Lyding authored and filipesilva committed Feb 22, 2017
1 parent c99a716 commit 26d1e41
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 32 deletions.
1 change: 1 addition & 0 deletions packages/@angular/cli/models/webpack-configs/production.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const getProdConfig = function (wco: WebpackConfigOptions) {
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new (<any>webpack).HashedModuleIdsPlugin(),
new webpack.optimize.UglifyJsPlugin(<any>{
mangle: { screw_ie8: true },
compress: { screw_ie8: true, warnings: buildOptions.verbose },
Expand Down
3 changes: 3 additions & 0 deletions packages/@angular/cli/tasks/eject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ class JsonWebpackSerializer {
case webpack.NoEmitOnErrorsPlugin:
this._addImport('webpack', 'NoEmitOnErrorsPlugin');
break;
case (<any>webpack).HashedModuleIdsPlugin:
this._addImport('webpack', 'HashedModuleIdsPlugin');
break;
case webpack.optimize.UglifyJsPlugin:
this._addImport('webpack.optimize', 'UglifyJsPlugin');
break;
Expand Down
104 changes: 72 additions & 32 deletions tests/e2e/tests/build/chunk-hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> {
const hashes = new Map<string, string>();

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<string, string>,
newHashes: Map<string, string>,
shouldChange: Array<string>): 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<string, string>;
let newHashes: Map<string, string>;
// 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']);
});
}

0 comments on commit 26d1e41

Please sign in to comment.