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"