mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-18 11:44:05 +08:00
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.
This commit is contained in:
parent
5c3146c49e
commit
9ec5b4ed66
@ -332,3 +332,13 @@ Note: service worker support is experimental and subject to change.
|
|||||||
Show circular dependency warnings on builds.
|
Show circular dependency warnings on builds.
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>build-optimizer</summary>
|
||||||
|
<p>
|
||||||
|
<code>--build-optimizer</code> (aliases: <code>-bo</code>)
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
(Experimental) Enables @angular-devkit/build-optimizer optimizations when using `--aot`.
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/angular/angular-cli",
|
"homepage": "https://github.com/angular/angular-cli",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@angular-devkit/build-optimizer": "0.0.3",
|
||||||
"autoprefixer": "^6.5.3",
|
"autoprefixer": "^6.5.3",
|
||||||
"chalk": "^1.1.3",
|
"chalk": "^1.1.3",
|
||||||
"circular-dependency-plugin": "^3.0.0",
|
"circular-dependency-plugin": "^3.0.0",
|
||||||
|
@ -48,7 +48,6 @@ export const baseBuildCommandOptions: any = [
|
|||||||
{
|
{
|
||||||
name: 'vendor-chunk',
|
name: 'vendor-chunk',
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
|
||||||
aliases: ['vc'],
|
aliases: ['vc'],
|
||||||
description: 'Use a separate bundle containing only vendor libraries.'
|
description: 'Use a separate bundle containing only vendor libraries.'
|
||||||
},
|
},
|
||||||
@ -159,6 +158,14 @@ export const baseBuildCommandOptions: any = [
|
|||||||
aliases: ['scd'],
|
aliases: ['scd'],
|
||||||
description: 'Show circular dependency warnings on builds.',
|
description: 'Show circular dependency warnings on builds.',
|
||||||
default: buildConfigDefaults['showCircularDependencies']
|
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.
|
// Check angular version.
|
||||||
Version.assertAngularVersionIs2_3_1OrHigher(this.project.root);
|
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 = require('../tasks/build').default;
|
||||||
|
|
||||||
const buildTask = new BuildTask({
|
const buildTask = new BuildTask({
|
||||||
|
@ -32,6 +32,12 @@ const EjectCommand = Command.extend({
|
|||||||
availableOptions: baseEjectCommandOptions,
|
availableOptions: baseEjectCommandOptions,
|
||||||
|
|
||||||
run: function (commandOptions: EjectTaskOptions) {
|
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 = require('../tasks/eject').default;
|
||||||
const ejectTask = new EjectTask({
|
const ejectTask = new EjectTask({
|
||||||
project: this.project,
|
project: this.project,
|
||||||
|
@ -121,6 +121,12 @@ const ServeCommand = Command.extend({
|
|||||||
const ServeTask = require('../tasks/serve').default;
|
const ServeTask = require('../tasks/serve').default;
|
||||||
|
|
||||||
Version.assertAngularVersionIs2_3_1OrHigher(this.project.root);
|
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)
|
return checkPort(commandOptions.port, commandOptions.host, defaultPort)
|
||||||
.then(port => {
|
.then(port => {
|
||||||
commandOptions.port = port;
|
commandOptions.port = port;
|
||||||
|
@ -22,4 +22,5 @@ export interface BuildOptions {
|
|||||||
preserveSymlinks?: boolean;
|
preserveSymlinks?: boolean;
|
||||||
extractLicenses?: boolean;
|
extractLicenses?: boolean;
|
||||||
showCircularDependencies?: boolean;
|
showCircularDependencies?: boolean;
|
||||||
|
buildOptimizer?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,11 @@ export class NgCliWebpackConfig {
|
|||||||
if (buildOptions.target !== 'development' && buildOptions.target !== 'production') {
|
if (buildOptions.target !== 'development' && buildOptions.target !== 'production') {
|
||||||
throw new Error("Invalid build target. Only 'development' and 'production' are available.");
|
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
|
// Fill in defaults for build targets
|
||||||
|
@ -19,6 +19,7 @@ const CircularDependencyPlugin = require('circular-dependency-plugin');
|
|||||||
* require('json-loader')
|
* require('json-loader')
|
||||||
* require('url-loader')
|
* require('url-loader')
|
||||||
* require('file-loader')
|
* require('file-loader')
|
||||||
|
* require('@angular-devkit/build-optimizer')
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function getCommonConfig(wco: WebpackConfigOptions) {
|
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 {
|
return {
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
@ -107,7 +118,7 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
|
|||||||
node: {
|
node: {
|
||||||
fs: 'empty',
|
fs: 'empty',
|
||||||
// `global` should be kept true, removing it resulted in a
|
// `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,
|
global: true,
|
||||||
crypto: 'empty',
|
crypto: 'empty',
|
||||||
tls: 'empty',
|
tls: 'empty',
|
||||||
|
@ -3,6 +3,7 @@ import * as webpack from 'webpack';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
import { stripIndent } from 'common-tags';
|
import { stripIndent } from 'common-tags';
|
||||||
|
import { PurifyPlugin } from '@angular-devkit/build-optimizer';
|
||||||
import { StaticAssetPlugin } from '../../plugins/static-asset';
|
import { StaticAssetPlugin } from '../../plugins/static-asset';
|
||||||
import { GlobCopyWebpackPlugin } from '../../plugins/glob-copy-webpack-plugin';
|
import { GlobCopyWebpackPlugin } from '../../plugins/glob-copy-webpack-plugin';
|
||||||
import { WebpackConfigOptions } from '../webpack-config';
|
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 {
|
return {
|
||||||
entry: entryPoints,
|
entry: entryPoints,
|
||||||
plugins: [
|
plugins: extraPlugins.concat([
|
||||||
new webpack.EnvironmentPlugin({
|
new webpack.EnvironmentPlugin({
|
||||||
'NODE_ENV': 'production'
|
'NODE_ENV': 'production'
|
||||||
}),
|
}),
|
||||||
@ -101,10 +110,10 @@ export const getProdConfig = function (wco: WebpackConfigOptions) {
|
|||||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||||
new webpack.optimize.UglifyJsPlugin(<any>{
|
new webpack.optimize.UglifyJsPlugin(<any>{
|
||||||
mangle: { screw_ie8: true },
|
mangle: { screw_ie8: true },
|
||||||
compress: { screw_ie8: true, warnings: buildOptions.verbose },
|
compress: uglifyCompressOptions,
|
||||||
sourceMap: buildOptions.sourcemaps,
|
sourceMap: buildOptions.sourcemaps,
|
||||||
comments: false
|
comments: false
|
||||||
})
|
})
|
||||||
].concat(extraPlugins)
|
])
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -74,7 +74,6 @@ function _createAotPlugin(wco: WebpackConfigOptions, options: any) {
|
|||||||
}, options));
|
}, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getNonAotConfig = function(wco: WebpackConfigOptions) {
|
export const getNonAotConfig = function(wco: WebpackConfigOptions) {
|
||||||
const { appConfig, projectRoot } = wco;
|
const { appConfig, projectRoot } = wco;
|
||||||
const tsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.tsconfig);
|
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) {
|
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 tsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.tsconfig);
|
||||||
const testTsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.testTsconfig);
|
const testTsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.testTsconfig);
|
||||||
|
|
||||||
@ -99,8 +98,16 @@ export const getAotConfig = function(wco: WebpackConfigOptions) {
|
|||||||
pluginOptions.exclude = exclude;
|
pluginOptions.exclude = exclude;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let boLoader: any = [];
|
||||||
|
if (buildOptions.buildOptimizer) {
|
||||||
|
boLoader = [{
|
||||||
|
loader: '@angular-devkit/build-optimizer/webpack-loader',
|
||||||
|
options: { sourceMap: buildOptions.sourcemaps }
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
module: { rules: [{ test: /\.ts$/, loader: webpackLoader }] },
|
module: { rules: [{ test: /\.ts$/, use: [...boLoader, webpackLoader] }] },
|
||||||
plugins: [ _createAotPlugin(wco, pluginOptions) ]
|
plugins: [ _createAotPlugin(wco, pluginOptions) ]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/angular/angular-cli",
|
"homepage": "https://github.com/angular/angular-cli",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@angular-devkit/build-optimizer": "0.0.3",
|
||||||
"@ngtools/json-schema": "1.1.0",
|
"@ngtools/json-schema": "1.1.0",
|
||||||
"@ngtools/webpack": "1.6.0-beta.1",
|
"@ngtools/webpack": "1.6.0-beta.1",
|
||||||
"autoprefixer": "^6.5.3",
|
"autoprefixer": "^6.5.3",
|
||||||
|
10
tests/e2e/tests/build/build-optimizer.ts
Normal file
10
tests/e2e/tests/build/build-optimizer.ts
Normal file
@ -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 =/)));
|
||||||
|
}
|
13
yarn.lock
13
yarn.lock
@ -2,6 +2,15 @@
|
|||||||
# yarn lockfile v1
|
# 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":
|
"@angular/compiler-cli@^4.0.0":
|
||||||
version "4.2.4"
|
version "4.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.2.4.tgz#cce941a28362fc1c042ab85890fcaab1e233dd57"
|
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"
|
version "0.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
|
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"
|
version "0.19.1"
|
||||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.19.1.tgz#14d768013caf2ec8fdea16a49af82fc377e75201"
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.19.1.tgz#14d768013caf2ec8fdea16a49af82fc377e75201"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5242,7 +5251,7 @@ typedarray@^0.0.6:
|
|||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
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"
|
version "2.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.4.tgz#3d38321828231e434f287514959c37a82b629f42"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.4.tgz#3d38321828231e434f287514959c37a82b629f42"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user