diff --git a/docs/documentation/build.md b/docs/documentation/build.md
index 4ae7f45898e9..2a3678bd904d 100644
--- a/docs/documentation/build.md
+++ b/docs/documentation/build.md
@@ -332,3 +332,13 @@ Note: service worker support is experimental and subject to change.
Show circular dependency warnings on builds.
+
+
+ build-optimizer
+
+ --build-optimizer
(aliases: -bo
)
+
+
+ (Experimental) Enables @angular-devkit/build-optimizer optimizations when using `--aot`.
+
+
diff --git a/package.json b/package.json
index f90128dd3698..5df01f21c664 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
},
"homepage": "https://github.com/angular/angular-cli",
"dependencies": {
+ "@angular-devkit/build-optimizer": "0.0.3",
"autoprefixer": "^6.5.3",
"chalk": "^1.1.3",
"circular-dependency-plugin": "^3.0.0",
diff --git a/packages/@angular/cli/commands/build.ts b/packages/@angular/cli/commands/build.ts
index fb609ae4a322..2138b1aa028d 100644
--- a/packages/@angular/cli/commands/build.ts
+++ b/packages/@angular/cli/commands/build.ts
@@ -48,7 +48,6 @@ export const baseBuildCommandOptions: any = [
{
name: 'vendor-chunk',
type: Boolean,
- default: true,
aliases: ['vc'],
description: 'Use a separate bundle containing only vendor libraries.'
},
@@ -159,6 +158,14 @@ export const baseBuildCommandOptions: any = [
aliases: ['scd'],
description: 'Show circular dependency warnings on builds.',
default: buildConfigDefaults['showCircularDependencies']
+ },
+ {
+ name: 'build-optimizer',
+ type: Boolean,
+ default: false,
+ aliases: ['bo'],
+ description: '(Experimental) Enables @angular-devkit/build-optimizer '
+ + 'optimizations when using `--aot`.'
}
];
@@ -185,6 +192,11 @@ const BuildCommand = Command.extend({
// Check angular version.
Version.assertAngularVersionIs2_3_1OrHigher(this.project.root);
+ // Default vendor chunk to false when build optimizer is on.
+ if (commandOptions.vendorChunk === undefined) {
+ commandOptions.vendorChunk = !commandOptions.buildOptimizer;
+ }
+
const BuildTask = require('../tasks/build').default;
const buildTask = new BuildTask({
diff --git a/packages/@angular/cli/commands/eject.ts b/packages/@angular/cli/commands/eject.ts
index f4e625f8d2ce..4983d691785f 100644
--- a/packages/@angular/cli/commands/eject.ts
+++ b/packages/@angular/cli/commands/eject.ts
@@ -32,6 +32,12 @@ const EjectCommand = Command.extend({
availableOptions: baseEjectCommandOptions,
run: function (commandOptions: EjectTaskOptions) {
+
+ // Default vendor chunk to false when build optimizer is on.
+ if (commandOptions.vendorChunk === undefined) {
+ commandOptions.vendorChunk = !commandOptions.buildOptimizer;
+ }
+
const EjectTask = require('../tasks/eject').default;
const ejectTask = new EjectTask({
project: this.project,
diff --git a/packages/@angular/cli/commands/serve.ts b/packages/@angular/cli/commands/serve.ts
index ee4194f8a571..85fa09caba03 100644
--- a/packages/@angular/cli/commands/serve.ts
+++ b/packages/@angular/cli/commands/serve.ts
@@ -121,6 +121,12 @@ const ServeCommand = Command.extend({
const ServeTask = require('../tasks/serve').default;
Version.assertAngularVersionIs2_3_1OrHigher(this.project.root);
+
+ // Default vendor chunk to false when build optimizer is on.
+ if (commandOptions.vendorChunk === undefined) {
+ commandOptions.vendorChunk = !commandOptions.buildOptimizer;
+ }
+
return checkPort(commandOptions.port, commandOptions.host, defaultPort)
.then(port => {
commandOptions.port = port;
diff --git a/packages/@angular/cli/models/build-options.ts b/packages/@angular/cli/models/build-options.ts
index 55be02530677..e8e2265eddce 100644
--- a/packages/@angular/cli/models/build-options.ts
+++ b/packages/@angular/cli/models/build-options.ts
@@ -22,4 +22,5 @@ export interface BuildOptions {
preserveSymlinks?: boolean;
extractLicenses?: boolean;
showCircularDependencies?: boolean;
+ buildOptimizer?: boolean;
}
diff --git a/packages/@angular/cli/models/webpack-config.ts b/packages/@angular/cli/models/webpack-config.ts
index e12daf8842b9..e3a1b2061d30 100644
--- a/packages/@angular/cli/models/webpack-config.ts
+++ b/packages/@angular/cli/models/webpack-config.ts
@@ -70,6 +70,11 @@ export class NgCliWebpackConfig {
if (buildOptions.target !== 'development' && buildOptions.target !== 'production') {
throw new Error("Invalid build target. Only 'development' and 'production' are available.");
}
+
+ if (buildOptions.buildOptimizer
+ && !(buildOptions.aot || buildOptions.target === 'production')) {
+ throw new Error('The `--build-optimizer` option cannot be used without `--aot`.');
+ }
}
// Fill in defaults for build targets
diff --git a/packages/@angular/cli/models/webpack-configs/common.ts b/packages/@angular/cli/models/webpack-configs/common.ts
index 1c5580c950ed..4a23ab39d311 100644
--- a/packages/@angular/cli/models/webpack-configs/common.ts
+++ b/packages/@angular/cli/models/webpack-configs/common.ts
@@ -19,6 +19,7 @@ const CircularDependencyPlugin = require('circular-dependency-plugin');
* require('json-loader')
* require('url-loader')
* require('file-loader')
+ * require('@angular-devkit/build-optimizer')
*/
export function getCommonConfig(wco: WebpackConfigOptions) {
@@ -71,6 +72,16 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
}));
}
+ if (buildOptions.buildOptimizer) {
+ extraRules.push({
+ test: /\.js$/,
+ use: [{
+ loader: '@angular-devkit/build-optimizer/webpack-loader',
+ options: { sourceMap: buildOptions.sourcemaps }
+ }]
+ });
+ }
+
return {
resolve: {
extensions: ['.ts', '.js'],
@@ -107,7 +118,7 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
node: {
fs: 'empty',
// `global` should be kept true, removing it resulted in a
- // massive size increase with NGO on AIO.
+ // massive size increase with Build Optimizer on AIO.
global: true,
crypto: 'empty',
tls: 'empty',
diff --git a/packages/@angular/cli/models/webpack-configs/production.ts b/packages/@angular/cli/models/webpack-configs/production.ts
index c86975ac2ab5..4e574f7428d2 100644
--- a/packages/@angular/cli/models/webpack-configs/production.ts
+++ b/packages/@angular/cli/models/webpack-configs/production.ts
@@ -3,6 +3,7 @@ import * as webpack from 'webpack';
import * as fs from 'fs';
import * as semver from 'semver';
import { stripIndent } from 'common-tags';
+import { PurifyPlugin } from '@angular-devkit/build-optimizer';
import { StaticAssetPlugin } from '../../plugins/static-asset';
import { GlobCopyWebpackPlugin } from '../../plugins/glob-copy-webpack-plugin';
import { WebpackConfigOptions } from '../webpack-config';
@@ -91,9 +92,17 @@ export const getProdConfig = function (wco: WebpackConfigOptions) {
}));
}
+ const uglifyCompressOptions: any = { screw_ie8: true, warnings: buildOptions.verbose };
+
+ if (buildOptions.buildOptimizer) {
+ // This plugin must be before webpack.optimize.UglifyJsPlugin.
+ extraPlugins.push(new PurifyPlugin());
+ uglifyCompressOptions.pure_getters = true;
+ }
+
return {
entry: entryPoints,
- plugins: [
+ plugins: extraPlugins.concat([
new webpack.EnvironmentPlugin({
'NODE_ENV': 'production'
}),
@@ -101,10 +110,10 @@ export const getProdConfig = function (wco: WebpackConfigOptions) {
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.optimize.UglifyJsPlugin({
mangle: { screw_ie8: true },
- compress: { screw_ie8: true, warnings: buildOptions.verbose },
+ compress: uglifyCompressOptions,
sourceMap: buildOptions.sourcemaps,
comments: false
})
- ].concat(extraPlugins)
+ ])
};
};
diff --git a/packages/@angular/cli/models/webpack-configs/typescript.ts b/packages/@angular/cli/models/webpack-configs/typescript.ts
index 51a6cb77fd18..ba58d224ba64 100644
--- a/packages/@angular/cli/models/webpack-configs/typescript.ts
+++ b/packages/@angular/cli/models/webpack-configs/typescript.ts
@@ -74,7 +74,6 @@ function _createAotPlugin(wco: WebpackConfigOptions, options: any) {
}, options));
}
-
export const getNonAotConfig = function(wco: WebpackConfigOptions) {
const { appConfig, projectRoot } = wco;
const tsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.tsconfig);
@@ -86,7 +85,7 @@ export const getNonAotConfig = function(wco: WebpackConfigOptions) {
};
export const getAotConfig = function(wco: WebpackConfigOptions) {
- const { projectRoot, appConfig } = wco;
+ const { projectRoot, buildOptions, appConfig } = wco;
const tsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.tsconfig);
const testTsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.testTsconfig);
@@ -99,8 +98,16 @@ export const getAotConfig = function(wco: WebpackConfigOptions) {
pluginOptions.exclude = exclude;
}
+ let boLoader: any = [];
+ if (buildOptions.buildOptimizer) {
+ boLoader = [{
+ loader: '@angular-devkit/build-optimizer/webpack-loader',
+ options: { sourceMap: buildOptions.sourcemaps }
+ }];
+ }
+
return {
- module: { rules: [{ test: /\.ts$/, loader: webpackLoader }] },
+ module: { rules: [{ test: /\.ts$/, use: [...boLoader, webpackLoader] }] },
plugins: [ _createAotPlugin(wco, pluginOptions) ]
};
};
diff --git a/packages/@angular/cli/package.json b/packages/@angular/cli/package.json
index 2c4483305e93..9e83474cea4e 100644
--- a/packages/@angular/cli/package.json
+++ b/packages/@angular/cli/package.json
@@ -27,6 +27,7 @@
},
"homepage": "https://github.com/angular/angular-cli",
"dependencies": {
+ "@angular-devkit/build-optimizer": "0.0.3",
"@ngtools/json-schema": "1.1.0",
"@ngtools/webpack": "1.6.0-beta.1",
"autoprefixer": "^6.5.3",
diff --git a/tests/e2e/tests/build/build-optimizer.ts b/tests/e2e/tests/build/build-optimizer.ts
new file mode 100644
index 000000000000..a1bc94329962
--- /dev/null
+++ b/tests/e2e/tests/build/build-optimizer.ts
@@ -0,0 +1,10 @@
+import { ng } from '../../utils/process';
+import { expectFileToMatch, expectFileToExist } from '../../utils/fs';
+import { expectToFail } from '../../utils/utils';
+
+
+export default function () {
+ return ng('build', '--aot', '--bo')
+ .then(() => expectToFail(() => expectFileToExist('dist/vendor.js')))
+ .then(() => expectToFail(() => expectFileToMatch('dist/main.js', /\.decorators =/)));
+}
diff --git a/yarn.lock b/yarn.lock
index 2842334bdda9..9527cafadd1f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,15 @@
# yarn lockfile v1
+"@angular-devkit/build-optimizer@0.0.3":
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.0.3.tgz#092bdf732b79a779ce540f9bb99d6590dd971204"
+ dependencies:
+ loader-utils "^1.1.0"
+ magic-string "^0.19.1"
+ source-map "^0.5.6"
+ typescript "^2.3.3"
+
"@angular/compiler-cli@^4.0.0":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.2.4.tgz#cce941a28362fc1c042ab85890fcaab1e233dd57"
@@ -3143,7 +3152,7 @@ macaddress@^0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
-magic-string@^0.19.0:
+magic-string@^0.19.0, magic-string@^0.19.1:
version "0.19.1"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.19.1.tgz#14d768013caf2ec8fdea16a49af82fc377e75201"
dependencies:
@@ -5242,7 +5251,7 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
-typescript@~2.3.1:
+typescript@^2.3.3, typescript@~2.3.1:
version "2.3.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.4.tgz#3d38321828231e434f287514959c37a82b629f42"