From 6a5bd8a9b2274e0291a8609856788fb28aeaafee Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Fri, 11 Nov 2016 21:56:10 +0000 Subject: [PATCH] feat(build): auto generate vendor chunk Add `--vendor-chunk` option to `ng build/serve`. Enabling vendor chunk can help with rebuild speeds. My tests on a medium project show a 34% improvement on rebuild. This option is enabled by default on `ng serve` and `ng build`. To disable it, use `--no-vendor-chunk` flag. Partially address #1980 BREAKING CHANGE: `ng build/serve` now generated `vendor.bundle.js` by default. --- packages/angular-cli/commands/build.ts | 4 ++- packages/angular-cli/commands/serve.ts | 2 ++ .../models/webpack-build-common.ts | 30 +++++++++++-------- packages/angular-cli/models/webpack-config.ts | 4 ++- .../angular-cli/tasks/build-webpack-watch.ts | 3 +- packages/angular-cli/tasks/build-webpack.ts | 3 +- packages/angular-cli/tasks/serve-webpack.ts | 3 +- .../utilities/package-chunk-sort.ts | 17 +++++++++++ tests/e2e/tests/build/vendor-chunk.ts | 11 +++++++ tests/e2e/tests/third-party/bootstrap.ts | 1 + 10 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 packages/angular-cli/utilities/package-chunk-sort.ts create mode 100644 tests/e2e/tests/build/vendor-chunk.ts diff --git a/packages/angular-cli/commands/build.ts b/packages/angular-cli/commands/build.ts index 843fa466f79d..b1ab175f391b 100644 --- a/packages/angular-cli/commands/build.ts +++ b/packages/angular-cli/commands/build.ts @@ -12,6 +12,7 @@ export interface BuildOptions { baseHref?: string; aot?: boolean; sourcemap?: boolean; + vendorChunk?: boolean; } const BuildCommand = Command.extend({ @@ -33,7 +34,8 @@ const BuildCommand = Command.extend({ { name: 'suppress-sizes', type: Boolean, default: false }, { name: 'base-href', type: String, default: null, aliases: ['bh'] }, { name: 'aot', type: Boolean, default: false }, - { name: 'sourcemap', type: Boolean, default: true, aliases: ['sm'] } + { name: 'sourcemap', type: Boolean, default: true, aliases: ['sm'] }, + { name: 'vendor-chunk', type: Boolean, default: true } ], run: function (commandOptions: BuildOptions) { diff --git a/packages/angular-cli/commands/serve.ts b/packages/angular-cli/commands/serve.ts index 4b19b651edb1..9beb8822920b 100644 --- a/packages/angular-cli/commands/serve.ts +++ b/packages/angular-cli/commands/serve.ts @@ -28,6 +28,7 @@ export interface ServeTaskOptions { aot?: boolean; sourcemap?: boolean; open?: boolean; + vendorChunk?: boolean; } const ServeCommand = Command.extend({ @@ -83,6 +84,7 @@ const ServeCommand = Command.extend({ { name: 'ssl-cert', type: String, default: 'ssl/server.crt' }, { name: 'aot', type: Boolean, default: false }, { name: 'sourcemap', type: Boolean, default: true, aliases: ['sm'] }, + { name: 'vendor-chunk', type: Boolean, default: true }, { name: 'open', type: Boolean, diff --git a/packages/angular-cli/models/webpack-build-common.ts b/packages/angular-cli/models/webpack-build-common.ts index 5642e7ced8b7..fc974c4e5920 100644 --- a/packages/angular-cli/models/webpack-build-common.ts +++ b/packages/angular-cli/models/webpack-build-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 {packageChunkSort} from '../utilities/package-chunk-sort'; import {BaseHrefWebpackPlugin} from '@angular-cli/base-href-webpack'; const HtmlWebpackPlugin = require('html-webpack-plugin'); @@ -12,17 +13,20 @@ export function getWebpackCommonConfig( environment: string, appConfig: any, baseHref: string, - sourcemap: boolean + sourcemap: boolean, + vendorChunk: boolean ) { const appRoot = path.resolve(projectRoot, appConfig.root); const appMain = path.resolve(appRoot, appConfig.main); + const nodeModules = path.resolve(projectRoot, 'node_modules'); const styles = appConfig.styles ? appConfig.styles.map((style: string) => path.resolve(appRoot, style)) : []; const scripts = appConfig.scripts ? appConfig.scripts.map((script: string) => path.resolve(appRoot, script)) : []; + const extraPlugins: any[] = []; let entry: { [key: string]: string[] } = { main: [appMain] @@ -32,11 +36,19 @@ export function getWebpackCommonConfig( if (appConfig.styles.length > 0) { entry['styles'] = styles; } if (appConfig.scripts.length > 0) { entry['scripts'] = scripts; } + if (vendorChunk) { + extraPlugins.push(new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + chunks: ['main'], + minChunks: (module: any) => module.userRequest && module.userRequest.startsWith(nodeModules) + })); + } + return { devtool: sourcemap ? 'source-map' : 'eval', resolve: { extensions: ['.ts', '.js'], - modules: [path.resolve(projectRoot, 'node_modules')] + modules: [nodeModules] }, context: path.resolve(__dirname, './'), entry: entry, @@ -52,9 +64,7 @@ export function getWebpackCommonConfig( enforce: 'pre', test: /\.js$/, loader: 'source-map-loader', - exclude: [ - /node_modules/ - ] + exclude: [ nodeModules ] }, // in main, load css as raw text        { @@ -91,7 +101,7 @@ export function getWebpackCommonConfig( new HtmlWebpackPlugin({ template: path.resolve(appRoot, appConfig.index), filename: path.resolve(appConfig.outDir, appConfig.index), - chunksSortMode: 'dependency' + chunksSortMode: packageChunkSort(['inline', 'styles', 'scripts', 'vendor', 'main']) }), new BaseHrefWebpackPlugin({ baseHref: baseHref @@ -104,10 +114,6 @@ export function getWebpackCommonConfig( .replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')), path.resolve(appRoot, appConfig.environments[environment]) ), - new webpack.optimize.CommonsChunkPlugin({ - // Optimizing ensures loading order in index.html - name: ['styles', 'scripts', 'main'].reverse() - }), new webpack.optimize.CommonsChunkPlugin({ minChunks: Infinity, name: 'inline' @@ -121,8 +127,8 @@ export function getWebpackCommonConfig( options: { postcss: [ autoprefixer() ] }, - }), - ], + }) + ].concat(extraPlugins), node: { fs: 'empty', global: true, diff --git a/packages/angular-cli/models/webpack-config.ts b/packages/angular-cli/models/webpack-config.ts index a6c42a8ba407..5f2f8a359a47 100644 --- a/packages/angular-cli/models/webpack-config.ts +++ b/packages/angular-cli/models/webpack-config.ts @@ -25,6 +25,7 @@ export class NgCliWebpackConfig { baseHref?: string, isAoT = false, sourcemap = true, + vendorChunk = false, ) { const config: CliConfig = CliConfig.fromProject(); const appConfig = config.config.apps[0]; @@ -36,7 +37,8 @@ export class NgCliWebpackConfig { environment, appConfig, baseHref, - sourcemap + sourcemap, + vendorChunk ); let targetConfigPartial = this.getTargetConfig(this.ngCliProject.root, appConfig); const typescriptConfigPartial = isAoT diff --git a/packages/angular-cli/tasks/build-webpack-watch.ts b/packages/angular-cli/tasks/build-webpack-watch.ts index ab7498b3dc00..672da6965c1d 100644 --- a/packages/angular-cli/tasks/build-webpack-watch.ts +++ b/packages/angular-cli/tasks/build-webpack-watch.ts @@ -25,7 +25,8 @@ export default Task.extend({ outputDir, runTaskOptions.baseHref, runTaskOptions.aot, - runTaskOptions.sourcemap + runTaskOptions.sourcemap, + runTaskOptions.vendorChunk ).config; const webpackCompiler: any = webpack(config); diff --git a/packages/angular-cli/tasks/build-webpack.ts b/packages/angular-cli/tasks/build-webpack.ts index a2bd60c5c608..9f519d4b57f5 100644 --- a/packages/angular-cli/tasks/build-webpack.ts +++ b/packages/angular-cli/tasks/build-webpack.ts @@ -24,7 +24,8 @@ export default Task.extend({ outputDir, runTaskOptions.baseHref, runTaskOptions.aot, - runTaskOptions.sourcemap + runTaskOptions.sourcemap, + runTaskOptions.vendorChunk ).config; const webpackCompiler: any = webpack(config); diff --git a/packages/angular-cli/tasks/serve-webpack.ts b/packages/angular-cli/tasks/serve-webpack.ts index 5f8a98980966..936766633106 100644 --- a/packages/angular-cli/tasks/serve-webpack.ts +++ b/packages/angular-cli/tasks/serve-webpack.ts @@ -27,7 +27,8 @@ export default Task.extend({ undefined, undefined, commandOptions.aot, - commandOptions.sourcemap + commandOptions.sourcemap, + commandOptions.vendorChunk ).config; // This allows for live reload of page when changes are made to repo. diff --git a/packages/angular-cli/utilities/package-chunk-sort.ts b/packages/angular-cli/utilities/package-chunk-sort.ts new file mode 100644 index 000000000000..7c4daeb6dbfa --- /dev/null +++ b/packages/angular-cli/utilities/package-chunk-sort.ts @@ -0,0 +1,17 @@ +export function packageChunkSort(packages: string[]) { + return function sort(left: any, right: any) { + let leftIndex = packages.indexOf(left.names[0]); + let rightindex = packages.indexOf(right.names[0]); + + if ( leftIndex < 0 || rightindex < 0) { + // Unknown packages are loaded last + return 1; + } + + if (leftIndex > rightindex) { + return 1; + } + + return -1; + }; +} diff --git a/tests/e2e/tests/build/vendor-chunk.ts b/tests/e2e/tests/build/vendor-chunk.ts new file mode 100644 index 000000000000..cafc19e3371b --- /dev/null +++ b/tests/e2e/tests/build/vendor-chunk.ts @@ -0,0 +1,11 @@ +import {ng} from '../../utils/process'; +import {expectFileToExist} from '../../utils/fs'; +import {expectToFail} from '../../utils/utils'; + + +export default function() { + return ng('build') + .then(() => expectFileToExist('dist/vendor.bundle.js')) + .then(() => ng('build', '--no-vendor-chunk')) + .then(() => expectToFail(() => expectFileToExist('dist/vendor.bundle.js'))); +} diff --git a/tests/e2e/tests/third-party/bootstrap.ts b/tests/e2e/tests/third-party/bootstrap.ts index 68e015c6f474..ca68ae0991f1 100644 --- a/tests/e2e/tests/third-party/bootstrap.ts +++ b/tests/e2e/tests/third-party/bootstrap.ts @@ -25,6 +25,7 @@ export default function() { + `)); }