diff --git a/docs/documentation/build.md b/docs/documentation/build.md
index 3bcd49be66af..d42da45ca93a 100644
--- a/docs/documentation/build.md
+++ b/docs/documentation/build.md
@@ -322,3 +322,13 @@ Note: service worker support is experimental and subject to change.
Show circular dependency warnings on builds.
+
+
+ ngo
+
+ --ngo
+
+
+ Enables NGO optimizations when using `--aot`.
+
+
diff --git a/package.json b/package.json
index 76519aeb1913..db9251801243 100644
--- a/package.json
+++ b/package.json
@@ -74,6 +74,7 @@
"magic-string": "^0.19.0",
"memory-fs": "^0.4.1",
"minimatch": "^3.0.3",
+ "ngo-loader": "github:angular/ngo",
"node-modules-path": "^1.0.0",
"nopt": "^4.0.1",
"opn": "4.0.2",
diff --git a/packages/@angular/cli/commands/build.ts b/packages/@angular/cli/commands/build.ts
index 6b377513f2a1..00b4b2035d63 100644
--- a/packages/@angular/cli/commands/build.ts
+++ b/packages/@angular/cli/commands/build.ts
@@ -20,7 +20,7 @@ export const baseBuildCommandOptions: any = [
{
name: 'environment',
type: String,
- aliases: ['e'] ,
+ aliases: ['e'],
description: 'Defines the build environment.'
},
{
@@ -43,7 +43,6 @@ export const baseBuildCommandOptions: any = [
{
name: 'vendor-chunk',
type: Boolean,
- default: true,
aliases: ['vc'],
description: 'Use a separate bundle containing only vendor libraries.'
},
@@ -144,6 +143,12 @@ export const baseBuildCommandOptions: any = [
type: Boolean,
aliases: ['scd'],
description: 'Show circular dependency warnings on builds.'
+ },
+ {
+ name: 'ngo',
+ type: Boolean,
+ default: false,
+ description: 'Enables NGO optimizations when using `--aot`.'
}
];
@@ -158,15 +163,25 @@ const BuildCommand = Command.extend({
availableOptions: baseBuildCommandOptions.concat([
{
- name: 'stats-json',
- type: Boolean,
- default: false,
- description: oneLine`Generates a \`stats.json\` file which can be analyzed using tools
+ name: 'stats-json',
+ type: Boolean,
+ default: false,
+ description: oneLine`Generates a \`stats.json\` file which can be analyzed using tools
such as: \`webpack-bundle-analyzer\` or https://webpack.github.io/analyse.`
- }
+ }
]),
run: function (commandOptions: BuildTaskOptions) {
+
+ // Remove vendor chunk if undefined and --ngo if on.
+ if (commandOptions.vendorChunk === undefined) {
+ if (commandOptions.ngo) {
+ commandOptions.vendorChunk = false;
+ } else {
+ commandOptions.vendorChunk = true;
+ }
+ }
+
// Check angular version.
Version.assertAngularVersionIs2_3_1OrHigher(this.project.root);
diff --git a/packages/@angular/cli/models/build-options.ts b/packages/@angular/cli/models/build-options.ts
index 12c61595d79c..228ad6c6e206 100644
--- a/packages/@angular/cli/models/build-options.ts
+++ b/packages/@angular/cli/models/build-options.ts
@@ -21,4 +21,5 @@ export interface BuildOptions {
preserveSymlinks?: boolean;
extractLicenses?: boolean;
showCircularDependencies?: boolean;
+ ngo?: boolean;
}
diff --git a/packages/@angular/cli/models/webpack-config.ts b/packages/@angular/cli/models/webpack-config.ts
index 0df40d65817f..0e8d59dd76fe 100644
--- a/packages/@angular/cli/models/webpack-config.ts
+++ b/packages/@angular/cli/models/webpack-config.ts
@@ -70,6 +70,10 @@ export class NgCliWebpackConfig {
if (buildOptions.target !== 'development' && buildOptions.target !== 'production') {
throw new Error("Invalid build target. Only 'development' and 'production' are available.");
}
+
+ if (buildOptions.ngo && !(buildOptions.aot || buildOptions.target === 'production')) {
+ throw new Error('The `--ngo` option cannot be used without `--aot` (or `--prod`).');
+ }
}
// 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 99052f62a930..3ec50e3d638c 100644
--- a/packages/@angular/cli/models/webpack-configs/common.ts
+++ b/packages/@angular/cli/models/webpack-configs/common.ts
@@ -18,6 +18,7 @@ const CircularDependencyPlugin = require('circular-dependency-plugin');
* require('json-loader')
* require('url-loader')
* require('file-loader')
+ * require('ngo-loader')
*/
export function getCommonConfig(wco: WebpackConfigOptions) {
@@ -79,6 +80,16 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
}));
}
+ if (buildOptions.ngo) {
+ extraRules.push({
+ test: /\.js$/,
+ use: [{
+ loader: 'ngo-loader',
+ options: { sourceMap: buildOptions.sourcemaps }
+ }]
+ });
+ }
+
return {
resolve: {
extensions: ['.ts', '.js'],
diff --git a/packages/@angular/cli/models/webpack-configs/production.ts b/packages/@angular/cli/models/webpack-configs/production.ts
index 1a9003e5ad96..509798e4d21b 100644
--- a/packages/@angular/cli/models/webpack-configs/production.ts
+++ b/packages/@angular/cli/models/webpack-configs/production.ts
@@ -7,6 +7,7 @@ import { StaticAssetPlugin } from '../../plugins/static-asset';
import { GlobCopyWebpackPlugin } from '../../plugins/glob-copy-webpack-plugin';
import { WebpackConfigOptions } from '../webpack-config';
+const PurifyPlugin = require('ngo-loader').PurifyPlugin;
const licensePlugin = require('license-webpack-plugin');
export const getProdConfig = function (wco: WebpackConfigOptions) {
@@ -91,19 +92,27 @@ export const getProdConfig = function (wco: WebpackConfigOptions) {
}));
}
+ const uglifyCompressOptions: any = { screw_ie8: true, warnings: buildOptions.verbose };
+
+ if (buildOptions.ngo) {
+ // 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'
}),
new (webpack).HashedModuleIdsPlugin(),
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..7d1b4a20c6dc 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 ngoLoader: any = [];
+ if (buildOptions.ngo) {
+ ngoLoader = [{
+ loader: 'ngo-loader',
+ options: { sourceMap: buildOptions.sourcemaps }
+ }];
+ }
+
return {
- module: { rules: [{ test: /\.ts$/, loader: webpackLoader }] },
+ module: { rules: [{ test: /\.ts$/, use: [...ngoLoader, webpackLoader] }] },
plugins: [ _createAotPlugin(wco, pluginOptions) ]
};
};
diff --git a/packages/@angular/cli/package.json b/packages/@angular/cli/package.json
index 856b3f7bf6e2..65fd2f2a2571 100644
--- a/packages/@angular/cli/package.json
+++ b/packages/@angular/cli/package.json
@@ -59,6 +59,7 @@
"lodash": "^4.11.1",
"memory-fs": "^0.4.1",
"minimatch": "^3.0.3",
+ "ngo-loader": "github:angular/ngo",
"node-modules-path": "^1.0.0",
"nopt": "^4.0.1",
"opn": "4.0.2",
diff --git a/tests/e2e/tests/build/ngo.ts b/tests/e2e/tests/build/ngo.ts
new file mode 100644
index 000000000000..cf0a8c3cc599
--- /dev/null
+++ b/tests/e2e/tests/build/ngo.ts
@@ -0,0 +1,9 @@
+import { ng } from '../../utils/process';
+import { expectFileToMatch } from '../../utils/fs';
+import { expectToFail } from '../../utils/utils';
+
+
+export default function () {
+ return ng('build', '--aot', '--ngo')
+ .then(() => expectToFail(() => expectFileToMatch('dist/vendor.js', /\.decorators =/)));
+}
diff --git a/yarn.lock b/yarn.lock
index 6b3e58f8404c..9b2799d686cc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3053,7 +3053,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:
@@ -3239,6 +3239,15 @@ negotiator@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
+"ngo-loader@github:angular/ngo":
+ version "0.0.10"
+ resolved "https://codeload.github.com/angular/ngo/tar.gz/13e7ef35e3cc5a08bbde39ae228bee8d7bf544c9"
+ dependencies:
+ loader-utils "^1.1.0"
+ magic-string "^0.19.1"
+ source-map "^0.5.6"
+ typescript "^2.4.0-dev.20170608"
+
no-case@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.1.tgz#7aeba1c73a52184265554b7dc03baf720df80081"
@@ -4355,16 +4364,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"
@@ -5106,6 +5115,10 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+typescript@^2.4.0-dev.20170608:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc"
+
typescript@~2.3.1:
version "2.3.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.4.tgz#3d38321828231e434f287514959c37a82b629f42"