From f888245f4cc1d234b70ea967ac2913bbf2481fec Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Tue, 4 Jul 2017 19:07:00 +0100 Subject: [PATCH] feat(@angular/cli): name lazy chunks Before: ``` $ ng build --no-progress Hash: ff03df269349b817eef4 Time: 11202ms chunk {0} 0.chunk.js, 0.chunk.js.map 1.61 kB {1} {3} [rendered] chunk {1} 1.chunk.js, 1.chunk.js.map 1.46 kB {0} {3} [rendered] chunk {2} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 160 kB {6} [initial] [rendered] chunk {3} main.bundle.js, main.bundle.js.map (main) 6.38 kB {5} [initial] [rendered] chunk {4} styles.bundle.js, styles.bundle.js.map (styles) 10.5 kB {6} [initial] [rendered] chunk {5} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.16 MB [initial] [rendered] chunk {6} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered] ``` After: ``` $ ng build --no-progress Hash: 2bc12a89f40f3b4818b5 Time: 9613ms chunk {feature.module} feature.module.chunk.js, feature.module.chunk.js.map 1.46 kB {lazy.module} {main} [rendered] chunk {inline} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered] chunk {lazy.module} lazy.module.chunk.js, lazy.module.chunk.js.map 1.61 kB {feature.module} {main} [rendered] chunk {main} main.bundle.js, main.bundle.js.map (main) 6.38 kB {vendor} [initial] [rendered] chunk {polyfills} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 160 kB {inline} [initial] [rendered] chunk {styles} styles.bundle.js, styles.bundle.js.map (styles) 10.5 kB {inline} [initial] [rendered] chunk {vendor} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.16 MB [initial] [rendered] ``` Fix #6700 --- .../cli/models/webpack-configs/common.ts | 4 +- .../cli/models/webpack-configs/production.ts | 2 +- .../named-lazy-chunks-webpack-plugin.ts | 41 +++++++++++++++++++ packages/@angular/cli/plugins/webpack.js | 4 +- packages/@angular/cli/tasks/eject.ts | 1 + .../@angular/cli/webpack-custom-typings.d.ts | 10 +++++ packages/@ngtools/webpack/src/plugin.ts | 6 ++- tests/e2e/tests/misc/lazy-module.ts | 16 ++++++-- yarn.lock | 10 ++--- 9 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 packages/@angular/cli/plugins/named-lazy-chunks-webpack-plugin.ts create mode 100644 packages/@angular/cli/webpack-custom-typings.d.ts diff --git a/packages/@angular/cli/models/webpack-configs/common.ts b/packages/@angular/cli/models/webpack-configs/common.ts index 99052f62a930..85ee42731983 100644 --- a/packages/@angular/cli/models/webpack-configs/common.ts +++ b/packages/@angular/cli/models/webpack-configs/common.ts @@ -1,6 +1,7 @@ import * as webpack from 'webpack'; import * as path from 'path'; import { GlobCopyWebpackPlugin } from '../../plugins/glob-copy-webpack-plugin'; +import { NamedLazyChunksWebpackPlugin } from '../../plugins/named-lazy-chunks-webpack-plugin'; import { extraEntryParser, getOutputHashFormat } from './utils'; import { WebpackConfigOptions } from '../webpack-config'; @@ -109,7 +110,8 @@ export function getCommonConfig(wco: WebpackConfigOptions) { ].concat(extraRules) }, plugins: [ - new webpack.NoEmitOnErrorsPlugin() + new webpack.NoEmitOnErrorsPlugin(), + new NamedLazyChunksWebpackPlugin(), ].concat(extraPlugins), node: { fs: 'empty', diff --git a/packages/@angular/cli/models/webpack-configs/production.ts b/packages/@angular/cli/models/webpack-configs/production.ts index 1a9003e5ad96..d7bdc146d82e 100644 --- a/packages/@angular/cli/models/webpack-configs/production.ts +++ b/packages/@angular/cli/models/webpack-configs/production.ts @@ -97,7 +97,7 @@ export const getProdConfig = function (wco: WebpackConfigOptions) { new webpack.EnvironmentPlugin({ 'NODE_ENV': 'production' }), - new (webpack).HashedModuleIdsPlugin(), + new webpack.HashedModuleIdsPlugin(), new webpack.optimize.UglifyJsPlugin({ mangle: { screw_ie8: true }, compress: { screw_ie8: true, warnings: buildOptions.verbose }, diff --git a/packages/@angular/cli/plugins/named-lazy-chunks-webpack-plugin.ts b/packages/@angular/cli/plugins/named-lazy-chunks-webpack-plugin.ts new file mode 100644 index 000000000000..970dfa43cdae --- /dev/null +++ b/packages/@angular/cli/plugins/named-lazy-chunks-webpack-plugin.ts @@ -0,0 +1,41 @@ +import * as webpack from 'webpack'; + + +// This just extends webpack.NamedChunksPlugin to prevent name collisions. +export class NamedLazyChunksWebpackPlugin extends webpack.NamedChunksPlugin { + constructor() { + // Append a dot and number if the name already exists. + const nameMap = new Map(); + function getUniqueName(baseName: string) { + let name = baseName; + let num = 0; + while (nameMap.has(name)) { + name = `${baseName}.${num++}`; + } + nameMap.set(name, true); + return name; + } + + const nameResolver = (chunk: any) => { + // Entry chunks have a name already, use it. + if (chunk.name) { + return chunk.name; + } + + // Try to figure out if it's a lazy loaded route. + if (chunk.blocks + && chunk.blocks.length > 0 + && chunk.blocks[0].dependencies + && chunk.blocks[0].dependencies.length > 0 + && chunk.blocks[0].dependencies[0].lazyRouteChunkName + ) { + // lazyRouteChunkName was added by @ngtools/webpack. + return getUniqueName(chunk.blocks[0].dependencies[0].lazyRouteChunkName); + } + + return null; + }; + + super(nameResolver); + } +} diff --git a/packages/@angular/cli/plugins/webpack.js b/packages/@angular/cli/plugins/webpack.js index 0fd22391ab39..c2636e70496d 100644 --- a/packages/@angular/cli/plugins/webpack.js +++ b/packages/@angular/cli/plugins/webpack.js @@ -6,5 +6,7 @@ module.exports = { GlobCopyWebpackPlugin: require('../plugins/glob-copy-webpack-plugin').GlobCopyWebpackPlugin, SuppressExtractedTextChunksWebpackPlugin: require('../plugins/suppress-entry-chunks-webpack-plugin') - .SuppressExtractedTextChunksWebpackPlugin + .SuppressExtractedTextChunksWebpackPlugin, + NamedLazyChunksWebpackPlugin: + require('../plugins/named-lazy-chunks-webpack-plugin').NamedLazyChunksWebpackPlugin }; diff --git a/packages/@angular/cli/tasks/eject.ts b/packages/@angular/cli/tasks/eject.ts index 5427fb204b37..1b2ceac86f95 100644 --- a/packages/@angular/cli/tasks/eject.ts +++ b/packages/@angular/cli/tasks/eject.ts @@ -175,6 +175,7 @@ class JsonWebpackSerializer { this._addImport('webpack.optimize', 'UglifyJsPlugin'); break; case angularCliPlugins.BaseHrefWebpackPlugin: + case angularCliPlugins.NamedLazyChunksWebpackPlugin: case angularCliPlugins.SuppressExtractedTextChunksWebpackPlugin: this._addImport('@angular/cli/plugins/webpack', plugin.constructor.name); break; diff --git a/packages/@angular/cli/webpack-custom-typings.d.ts b/packages/@angular/cli/webpack-custom-typings.d.ts new file mode 100644 index 000000000000..b57841eb3c17 --- /dev/null +++ b/packages/@angular/cli/webpack-custom-typings.d.ts @@ -0,0 +1,10 @@ +import * as webpack from 'webpack'; + +declare module 'webpack' { + export class NamedChunksPlugin { + constructor(nameResolver: (chunk: any) => string | null); + } + export class HashedModuleIdsPlugin { + constructor(); + } +} diff --git a/packages/@ngtools/webpack/src/plugin.ts b/packages/@ngtools/webpack/src/plugin.ts index 2d2eecf61f76..338cb3b383c7 100644 --- a/packages/@ngtools/webpack/src/plugin.ts +++ b/packages/@ngtools/webpack/src/plugin.ts @@ -310,7 +310,11 @@ export class AotPlugin implements Tapable { .map((key) => { const value = this._lazyRoutes[key]; if (value !== null) { - return new ContextElementDependency(value, key); + const dep = new ContextElementDependency(value, key); + // lazyRouteChunkName is used by webpack.NamedChunksPlugin to give the + // lazy loaded chunk a name. + dep.lazyRouteChunkName = path.basename(key, '.ts'); + return dep; } else { return null; } diff --git a/tests/e2e/tests/misc/lazy-module.ts b/tests/e2e/tests/misc/lazy-module.ts index 5e95a8dd8319..cb88cb49358a 100644 --- a/tests/e2e/tests/misc/lazy-module.ts +++ b/tests/e2e/tests/misc/lazy-module.ts @@ -12,17 +12,27 @@ export default function() { .then(() => ng('build')) .then(() => oldNumberOfFiles = readdirSync('dist').length) .then(() => ng('generate', 'module', 'lazy', '--routing')) + .then(() => ng('generate', 'module', 'too/lazy', '--routing')) .then(() => addImportToModule('src/app/app.module.ts', oneLine` RouterModule.forRoot([{ path: "lazy", loadChildren: "app/lazy/lazy.module#LazyModule" }]), - RouterModule.forRoot([{ path: "lazy1", loadChildren: "./lazy/lazy.module#LazyModule" }]) + RouterModule.forRoot([{ path: "lazy1", loadChildren: "./lazy/lazy.module#LazyModule" }]), + RouterModule.forRoot([{ path: "lazy2", loadChildren: "./too/lazy/lazy.module#LazyModule" }]) `, '@angular/router')) .then(() => ng('build')) - .then(() => readdirSync('dist').length) - .then(currentNumberOfDistFiles => { + .then(() => readdirSync('dist')) + .then((distFiles) => { + const currentNumberOfDistFiles = distFiles.length; if (oldNumberOfFiles >= currentNumberOfDistFiles) { throw new Error('A bundle for the lazy module was not created.'); } oldNumberOfFiles = currentNumberOfDistFiles; + + if (!distFiles.includes('lazy.module.chunk.js')){ + throw new Error('The bundle for the lazy module did not have a name.'); + } + if (!distFiles.includes('lazy.module.0.chunk.js')){ + throw new Error('The bundle for the lazy module did not use a unique name.'); + } }) // verify System.import still works .then(() => writeFile('src/app/lazy-file.ts', '')) diff --git a/yarn.lock b/yarn.lock index 6b3e58f8404c..36e43de698e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4355,16 +4355,16 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: +rimraf@2, rimraf@^2.2.8, rimraf@~2.2.6: + version "2.2.8" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" + +rimraf@^2.5.1, rimraf@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" dependencies: glob "^7.0.5" -rimraf@~2.2.6: - version "2.2.8" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" - ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7"