Skip to content

Commit 9ec5b4e

Browse files
filipesilvaBrocco
authored andcommitted
feat(@angular/cli): add build-optimizer support
Adds the new flag `--build-optimizer` (`--bo`), usable only with `--aot` (or `--prod` since it auto enables `--aot`). This feature is experimental, and may not work correctly on your project. Should it work, total bundle size should go down. Savings are heavily dependent on the project. See https://github.com/angular/devkit/tree/master/packages/angular_devkit/build_optimizer for details about all the optimizations applied. Usage: `ng build --prod --build-optimizer`. Disabling the vendor chunk has been shown to improve total savings, and is done automatically when `--bo` is specified unless `--vendor-chunk` has a value. Please let us know if using `--build-optimizer` breaks your project so we can improve it further. Repos are very welcome.
1 parent 5c3146c commit 9ec5b4e

File tree

13 files changed

+98
-10
lines changed

13 files changed

+98
-10
lines changed

docs/documentation/build.md

+10
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,13 @@ Note: service worker support is experimental and subject to change.
332332
Show circular dependency warnings on builds.
333333
</p>
334334
</details>
335+
336+
<details>
337+
<summary>build-optimizer</summary>
338+
<p>
339+
<code>--build-optimizer</code> (aliases: <code>-bo</code>)
340+
</p>
341+
<p>
342+
(Experimental) Enables @angular-devkit/build-optimizer optimizations when using `--aot`.
343+
</p>
344+
</details>

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
},
4141
"homepage": "https://github.com/angular/angular-cli",
4242
"dependencies": {
43+
"@angular-devkit/build-optimizer": "0.0.3",
4344
"autoprefixer": "^6.5.3",
4445
"chalk": "^1.1.3",
4546
"circular-dependency-plugin": "^3.0.0",

packages/@angular/cli/commands/build.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ export const baseBuildCommandOptions: any = [
4848
{
4949
name: 'vendor-chunk',
5050
type: Boolean,
51-
default: true,
5251
aliases: ['vc'],
5352
description: 'Use a separate bundle containing only vendor libraries.'
5453
},
@@ -159,6 +158,14 @@ export const baseBuildCommandOptions: any = [
159158
aliases: ['scd'],
160159
description: 'Show circular dependency warnings on builds.',
161160
default: buildConfigDefaults['showCircularDependencies']
161+
},
162+
{
163+
name: 'build-optimizer',
164+
type: Boolean,
165+
default: false,
166+
aliases: ['bo'],
167+
description: '(Experimental) Enables @angular-devkit/build-optimizer '
168+
+ 'optimizations when using `--aot`.'
162169
}
163170
];
164171

@@ -185,6 +192,11 @@ const BuildCommand = Command.extend({
185192
// Check angular version.
186193
Version.assertAngularVersionIs2_3_1OrHigher(this.project.root);
187194

195+
// Default vendor chunk to false when build optimizer is on.
196+
if (commandOptions.vendorChunk === undefined) {
197+
commandOptions.vendorChunk = !commandOptions.buildOptimizer;
198+
}
199+
188200
const BuildTask = require('../tasks/build').default;
189201

190202
const buildTask = new BuildTask({

packages/@angular/cli/commands/eject.ts

+6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ const EjectCommand = Command.extend({
3232
availableOptions: baseEjectCommandOptions,
3333

3434
run: function (commandOptions: EjectTaskOptions) {
35+
36+
// Default vendor chunk to false when build optimizer is on.
37+
if (commandOptions.vendorChunk === undefined) {
38+
commandOptions.vendorChunk = !commandOptions.buildOptimizer;
39+
}
40+
3541
const EjectTask = require('../tasks/eject').default;
3642
const ejectTask = new EjectTask({
3743
project: this.project,

packages/@angular/cli/commands/serve.ts

+6
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ const ServeCommand = Command.extend({
121121
const ServeTask = require('../tasks/serve').default;
122122

123123
Version.assertAngularVersionIs2_3_1OrHigher(this.project.root);
124+
125+
// Default vendor chunk to false when build optimizer is on.
126+
if (commandOptions.vendorChunk === undefined) {
127+
commandOptions.vendorChunk = !commandOptions.buildOptimizer;
128+
}
129+
124130
return checkPort(commandOptions.port, commandOptions.host, defaultPort)
125131
.then(port => {
126132
commandOptions.port = port;

packages/@angular/cli/models/build-options.ts

+1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ export interface BuildOptions {
2222
preserveSymlinks?: boolean;
2323
extractLicenses?: boolean;
2424
showCircularDependencies?: boolean;
25+
buildOptimizer?: boolean;
2526
}

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

+5
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ export class NgCliWebpackConfig {
7070
if (buildOptions.target !== 'development' && buildOptions.target !== 'production') {
7171
throw new Error("Invalid build target. Only 'development' and 'production' are available.");
7272
}
73+
74+
if (buildOptions.buildOptimizer
75+
&& !(buildOptions.aot || buildOptions.target === 'production')) {
76+
throw new Error('The `--build-optimizer` option cannot be used without `--aot`.');
77+
}
7378
}
7479

7580
// Fill in defaults for build targets

packages/@angular/cli/models/webpack-configs/common.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const CircularDependencyPlugin = require('circular-dependency-plugin');
1919
* require('json-loader')
2020
* require('url-loader')
2121
* require('file-loader')
22+
* require('@angular-devkit/build-optimizer')
2223
*/
2324

2425
export function getCommonConfig(wco: WebpackConfigOptions) {
@@ -71,6 +72,16 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
7172
}));
7273
}
7374

75+
if (buildOptions.buildOptimizer) {
76+
extraRules.push({
77+
test: /\.js$/,
78+
use: [{
79+
loader: '@angular-devkit/build-optimizer/webpack-loader',
80+
options: { sourceMap: buildOptions.sourcemaps }
81+
}]
82+
});
83+
}
84+
7485
return {
7586
resolve: {
7687
extensions: ['.ts', '.js'],
@@ -107,7 +118,7 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
107118
node: {
108119
fs: 'empty',
109120
// `global` should be kept true, removing it resulted in a
110-
// massive size increase with NGO on AIO.
121+
// massive size increase with Build Optimizer on AIO.
111122
global: true,
112123
crypto: 'empty',
113124
tls: 'empty',

packages/@angular/cli/models/webpack-configs/production.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as webpack from 'webpack';
33
import * as fs from 'fs';
44
import * as semver from 'semver';
55
import { stripIndent } from 'common-tags';
6+
import { PurifyPlugin } from '@angular-devkit/build-optimizer';
67
import { StaticAssetPlugin } from '../../plugins/static-asset';
78
import { GlobCopyWebpackPlugin } from '../../plugins/glob-copy-webpack-plugin';
89
import { WebpackConfigOptions } from '../webpack-config';
@@ -91,20 +92,28 @@ export const getProdConfig = function (wco: WebpackConfigOptions) {
9192
}));
9293
}
9394

95+
const uglifyCompressOptions: any = { screw_ie8: true, warnings: buildOptions.verbose };
96+
97+
if (buildOptions.buildOptimizer) {
98+
// This plugin must be before webpack.optimize.UglifyJsPlugin.
99+
extraPlugins.push(new PurifyPlugin());
100+
uglifyCompressOptions.pure_getters = true;
101+
}
102+
94103
return {
95104
entry: entryPoints,
96-
plugins: [
105+
plugins: extraPlugins.concat([
97106
new webpack.EnvironmentPlugin({
98107
'NODE_ENV': 'production'
99108
}),
100109
new webpack.HashedModuleIdsPlugin(),
101110
new webpack.optimize.ModuleConcatenationPlugin(),
102111
new webpack.optimize.UglifyJsPlugin(<any>{
103112
mangle: { screw_ie8: true },
104-
compress: { screw_ie8: true, warnings: buildOptions.verbose },
113+
compress: uglifyCompressOptions,
105114
sourceMap: buildOptions.sourcemaps,
106115
comments: false
107116
})
108-
].concat(extraPlugins)
117+
])
109118
};
110119
};

packages/@angular/cli/models/webpack-configs/typescript.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ function _createAotPlugin(wco: WebpackConfigOptions, options: any) {
7474
}, options));
7575
}
7676

77-
7877
export const getNonAotConfig = function(wco: WebpackConfigOptions) {
7978
const { appConfig, projectRoot } = wco;
8079
const tsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.tsconfig);
@@ -86,7 +85,7 @@ export const getNonAotConfig = function(wco: WebpackConfigOptions) {
8685
};
8786

8887
export const getAotConfig = function(wco: WebpackConfigOptions) {
89-
const { projectRoot, appConfig } = wco;
88+
const { projectRoot, buildOptions, appConfig } = wco;
9089
const tsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.tsconfig);
9190
const testTsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.testTsconfig);
9291

@@ -99,8 +98,16 @@ export const getAotConfig = function(wco: WebpackConfigOptions) {
9998
pluginOptions.exclude = exclude;
10099
}
101100

101+
let boLoader: any = [];
102+
if (buildOptions.buildOptimizer) {
103+
boLoader = [{
104+
loader: '@angular-devkit/build-optimizer/webpack-loader',
105+
options: { sourceMap: buildOptions.sourcemaps }
106+
}];
107+
}
108+
102109
return {
103-
module: { rules: [{ test: /\.ts$/, loader: webpackLoader }] },
110+
module: { rules: [{ test: /\.ts$/, use: [...boLoader, webpackLoader] }] },
104111
plugins: [ _createAotPlugin(wco, pluginOptions) ]
105112
};
106113
};

packages/@angular/cli/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
},
2828
"homepage": "https://github.com/angular/angular-cli",
2929
"dependencies": {
30+
"@angular-devkit/build-optimizer": "0.0.3",
3031
"@ngtools/json-schema": "1.1.0",
3132
"@ngtools/webpack": "1.6.0-beta.1",
3233
"autoprefixer": "^6.5.3",
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { ng } from '../../utils/process';
2+
import { expectFileToMatch, expectFileToExist } from '../../utils/fs';
3+
import { expectToFail } from '../../utils/utils';
4+
5+
6+
export default function () {
7+
return ng('build', '--aot', '--bo')
8+
.then(() => expectToFail(() => expectFileToExist('dist/vendor.js')))
9+
.then(() => expectToFail(() => expectFileToMatch('dist/main.js', /\.decorators =/)));
10+
}

yarn.lock

+11-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
# yarn lockfile v1
33

44

5+
"@angular-devkit/[email protected]":
6+
version "0.0.3"
7+
resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.0.3.tgz#092bdf732b79a779ce540f9bb99d6590dd971204"
8+
dependencies:
9+
loader-utils "^1.1.0"
10+
magic-string "^0.19.1"
11+
source-map "^0.5.6"
12+
typescript "^2.3.3"
13+
514
"@angular/compiler-cli@^4.0.0":
615
version "4.2.4"
716
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.2.4.tgz#cce941a28362fc1c042ab85890fcaab1e233dd57"
@@ -3143,7 +3152,7 @@ macaddress@^0.2.8:
31433152
version "0.2.8"
31443153
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
31453154

3146-
magic-string@^0.19.0:
3155+
magic-string@^0.19.0, magic-string@^0.19.1:
31473156
version "0.19.1"
31483157
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.19.1.tgz#14d768013caf2ec8fdea16a49af82fc377e75201"
31493158
dependencies:
@@ -5242,7 +5251,7 @@ typedarray@^0.0.6:
52425251
version "0.0.6"
52435252
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
52445253

5245-
typescript@~2.3.1:
5254+
typescript@^2.3.3, typescript@~2.3.1:
52465255
version "2.3.4"
52475256
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.4.tgz#3d38321828231e434f287514959c37a82b629f42"
52485257

0 commit comments

Comments
 (0)