Skip to content

Commit 0a3eb2d

Browse files
filipesilvaMRHarrison
authored andcommitted
feat(build): auto generate vendor chunk (angular#3117)
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 angular#1980 BREAKING CHANGE: `ng build/serve` now generates `vendor.bundle.js` by default.
1 parent 11c05ba commit 0a3eb2d

File tree

10 files changed

+61
-17
lines changed

10 files changed

+61
-17
lines changed

packages/angular-cli/commands/build.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface BuildOptions {
1212
baseHref?: string;
1313
aot?: boolean;
1414
sourcemap?: boolean;
15+
vendorChunk?: boolean;
1516
}
1617

1718
const BuildCommand = Command.extend({
@@ -33,7 +34,8 @@ const BuildCommand = Command.extend({
3334
{ name: 'suppress-sizes', type: Boolean, default: false },
3435
{ name: 'base-href', type: String, default: null, aliases: ['bh'] },
3536
{ name: 'aot', type: Boolean, default: false },
36-
{ name: 'sourcemap', type: Boolean, default: true, aliases: ['sm'] }
37+
{ name: 'sourcemap', type: Boolean, default: true, aliases: ['sm'] },
38+
{ name: 'vendor-chunk', type: Boolean, default: true }
3739
],
3840

3941
run: function (commandOptions: BuildOptions) {

packages/angular-cli/commands/serve.ts

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface ServeTaskOptions {
2828
aot?: boolean;
2929
sourcemap?: boolean;
3030
open?: boolean;
31+
vendorChunk?: boolean;
3132
}
3233

3334
const ServeCommand = Command.extend({
@@ -83,6 +84,7 @@ const ServeCommand = Command.extend({
8384
{ name: 'ssl-cert', type: String, default: 'ssl/server.crt' },
8485
{ name: 'aot', type: Boolean, default: false },
8586
{ name: 'sourcemap', type: Boolean, default: true, aliases: ['sm'] },
87+
{ name: 'vendor-chunk', type: Boolean, default: true },
8688
{
8789
name: 'open',
8890
type: Boolean,

packages/angular-cli/models/webpack-build-common.ts

+18-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as webpack from 'webpack';
22
import * as path from 'path';
33
import {GlobCopyWebpackPlugin} from '../plugins/glob-copy-webpack-plugin';
4+
import {packageChunkSort} from '../utilities/package-chunk-sort';
45
import {BaseHrefWebpackPlugin} from '@angular-cli/base-href-webpack';
56

67
const HtmlWebpackPlugin = require('html-webpack-plugin');
@@ -12,17 +13,20 @@ export function getWebpackCommonConfig(
1213
environment: string,
1314
appConfig: any,
1415
baseHref: string,
15-
sourcemap: boolean
16+
sourcemap: boolean,
17+
vendorChunk: boolean
1618
) {
1719

1820
const appRoot = path.resolve(projectRoot, appConfig.root);
1921
const appMain = path.resolve(appRoot, appConfig.main);
22+
const nodeModules = path.resolve(projectRoot, 'node_modules');
2023
const styles = appConfig.styles
2124
? appConfig.styles.map((style: string) => path.resolve(appRoot, style))
2225
: [];
2326
const scripts = appConfig.scripts
2427
? appConfig.scripts.map((script: string) => path.resolve(appRoot, script))
2528
: [];
29+
const extraPlugins: any[] = [];
2630

2731
let entry: { [key: string]: string[] } = {
2832
main: [appMain]
@@ -32,11 +36,19 @@ export function getWebpackCommonConfig(
3236
if (appConfig.styles.length > 0) { entry['styles'] = styles; }
3337
if (appConfig.scripts.length > 0) { entry['scripts'] = scripts; }
3438

39+
if (vendorChunk) {
40+
extraPlugins.push(new webpack.optimize.CommonsChunkPlugin({
41+
name: 'vendor',
42+
chunks: ['main'],
43+
minChunks: (module: any) => module.userRequest && module.userRequest.startsWith(nodeModules)
44+
}));
45+
}
46+
3547
return {
3648
devtool: sourcemap ? 'source-map' : false,
3749
resolve: {
3850
extensions: ['.ts', '.js'],
39-
modules: [path.resolve(projectRoot, 'node_modules')]
51+
modules: [nodeModules]
4052
},
4153
context: path.resolve(__dirname, './'),
4254
entry: entry,
@@ -52,9 +64,7 @@ export function getWebpackCommonConfig(
5264
enforce: 'pre',
5365
test: /\.js$/,
5466
loader: 'source-map-loader',
55-
exclude: [
56-
/node_modules/
57-
]
67+
exclude: [ nodeModules ]
5868
},
5969
// in main, load css as raw text
6070
       {
@@ -91,7 +101,7 @@ export function getWebpackCommonConfig(
91101
new HtmlWebpackPlugin({
92102
template: path.resolve(appRoot, appConfig.index),
93103
filename: path.resolve(appConfig.outDir, appConfig.index),
94-
chunksSortMode: 'dependency'
104+
chunksSortMode: packageChunkSort(['inline', 'styles', 'scripts', 'vendor', 'main'])
95105
}),
96106
new BaseHrefWebpackPlugin({
97107
baseHref: baseHref
@@ -104,10 +114,6 @@ export function getWebpackCommonConfig(
104114
.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')),
105115
path.resolve(appRoot, appConfig.environments[environment])
106116
),
107-
new webpack.optimize.CommonsChunkPlugin({
108-
// Optimizing ensures loading order in index.html
109-
name: ['styles', 'scripts', 'main'].reverse()
110-
}),
111117
new webpack.optimize.CommonsChunkPlugin({
112118
minChunks: Infinity,
113119
name: 'inline'
@@ -121,8 +127,8 @@ export function getWebpackCommonConfig(
121127
options: {
122128
postcss: [ autoprefixer() ]
123129
},
124-
}),
125-
],
130+
})
131+
].concat(extraPlugins),
126132
node: {
127133
fs: 'empty',
128134
global: true,

packages/angular-cli/models/webpack-config.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class NgCliWebpackConfig {
2525
baseHref?: string,
2626
isAoT = false,
2727
sourcemap = true,
28+
vendorChunk = false,
2829
) {
2930
const config: CliConfig = CliConfig.fromProject();
3031
const appConfig = config.config.apps[0];
@@ -36,7 +37,8 @@ export class NgCliWebpackConfig {
3637
environment,
3738
appConfig,
3839
baseHref,
39-
sourcemap
40+
sourcemap,
41+
vendorChunk
4042
);
4143
let targetConfigPartial = this.getTargetConfig(this.ngCliProject.root, appConfig);
4244
const typescriptConfigPartial = isAoT

packages/angular-cli/tasks/build-webpack-watch.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export default Task.extend({
2525
outputDir,
2626
runTaskOptions.baseHref,
2727
runTaskOptions.aot,
28-
runTaskOptions.sourcemap
28+
runTaskOptions.sourcemap,
29+
runTaskOptions.vendorChunk
2930
).config;
3031
const webpackCompiler: any = webpack(config);
3132

packages/angular-cli/tasks/build-webpack.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export default <any>Task.extend({
2424
outputDir,
2525
runTaskOptions.baseHref,
2626
runTaskOptions.aot,
27-
runTaskOptions.sourcemap
27+
runTaskOptions.sourcemap,
28+
runTaskOptions.vendorChunk
2829
).config;
2930

3031
const webpackCompiler: any = webpack(config);

packages/angular-cli/tasks/serve-webpack.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export default Task.extend({
2727
undefined,
2828
undefined,
2929
commandOptions.aot,
30-
commandOptions.sourcemap
30+
commandOptions.sourcemap,
31+
commandOptions.vendorChunk
3132
).config;
3233

3334
// This allows for live reload of page when changes are made to repo.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export function packageChunkSort(packages: string[]) {
2+
return function sort(left: any, right: any) {
3+
let leftIndex = packages.indexOf(left.names[0]);
4+
let rightindex = packages.indexOf(right.names[0]);
5+
6+
if ( leftIndex < 0 || rightindex < 0) {
7+
// Unknown packages are loaded last
8+
return 1;
9+
}
10+
11+
if (leftIndex > rightindex) {
12+
return 1;
13+
}
14+
15+
return -1;
16+
};
17+
}

tests/e2e/tests/build/vendor-chunk.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {ng} from '../../utils/process';
2+
import {expectFileToExist} from '../../utils/fs';
3+
import {expectToFail} from '../../utils/utils';
4+
5+
6+
export default function() {
7+
return ng('build')
8+
.then(() => expectFileToExist('dist/vendor.bundle.js'))
9+
.then(() => ng('build', '--no-vendor-chunk'))
10+
.then(() => expectToFail(() => expectFileToExist('dist/vendor.bundle.js')));
11+
}

tests/e2e/tests/third-party/bootstrap.ts

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default function() {
2525
<script type="text/javascript" src="inline.bundle.js"></script>
2626
<script type="text/javascript" src="styles.bundle.js"></script>
2727
<script type="text/javascript" src="scripts.bundle.js"></script>
28+
<script type="text/javascript" src="vendor.bundle.js"></script>
2829
<script type="text/javascript" src="main.bundle.js"></script>
2930
`));
3031
}

0 commit comments

Comments
 (0)