Skip to content

Commit bb4be8c

Browse files
committed
feat(@angular/cli): Ability to specify budgets for your apps
Closes #7139
1 parent b6b8acc commit bb4be8c

File tree

10 files changed

+623
-0
lines changed

10 files changed

+623
-0
lines changed

docs/documentation/stories.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- [Angular Material](stories/include-angular-material)
1515
- [AngularFire](stories/include-angularfire)
1616
- [Bootstrap](stories/include-bootstrap)
17+
- [Budgets](stories/budgets)
1718
- [Font Awesome](stories/include-font-awesome)
1819
- [Moving Into the CLI](stories/moving-into-the-cli)
1920
- [Moving Out of the CLI](stories/moving-out-of-the-cli)

docs/documentation/stories/budgets.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Budgets
2+
3+
As applications grow in functionality, they also grow in size. Budgets is a feature in the
4+
Angular CLI which allows you to set budget thresholds in your configuration to ensure parts
5+
of your application stay within boundries which you set.
6+
7+
**.angular-cli.json**
8+
```
9+
{
10+
...
11+
apps: [
12+
{
13+
...
14+
budgets: []
15+
}
16+
]
17+
}
18+
```
19+
20+
## Budget Definition
21+
22+
- type
23+
- The type of budget.
24+
- Possible values:
25+
- bundle - The size of a specific bundle.
26+
- initial - The initial size of the app.
27+
- allScript - The size of all scripts.
28+
- all - The size of the entire app.
29+
- anyScript - The size of any one script.
30+
- any - The size of any file.
31+
- name
32+
- The name of the bundle.
33+
- Required only for type of "bundle"
34+
- baseline
35+
- The baseline size for comparison.
36+
- maximumWarning
37+
- The maximum threshold for warning relative to the baseline.
38+
- maximumError
39+
- The maximum threshold for error relative to the baseline.
40+
- minimumWarning
41+
- The minimum threshold for warning relative to the baseline.
42+
- minimumError
43+
- The minimum threshold for error relative to the baseline.
44+
- warning
45+
- The threshold for warning relative to the baseline (min & max).
46+
- error
47+
- The threshold for error relative to the baseline (min & max).
48+
49+
## Specifying sizes
50+
51+
Available formats:
52+
123 - size in bytes
53+
123b - size in bytes
54+
123kb - size in kilobytes
55+
123mb - size in megabytes
56+
12% - percentage
57+
58+
## NOTES
59+
60+
All sizes are relative to baseline.
61+
Percentages are not valid for baseline values.

packages/@angular/cli/lib/config/schema.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,53 @@
9999
},
100100
"default": []
101101
},
102+
"budgets": {
103+
"type": "array",
104+
"description": "Threshold definitions for bundle sizes.",
105+
"items": {
106+
"type": "object",
107+
"properties": {
108+
"type": {
109+
"type": "string",
110+
"enum": ["bundle", "initial", "allScript", "all", "anyScript", "any"],
111+
"description": "The type of budget"
112+
},
113+
"name": {
114+
"type": "string",
115+
"description": "The name of the bundle"
116+
},
117+
"baseline": {
118+
"type": "string",
119+
"description": "The baseline size for comparison."
120+
},
121+
"maximumWarning": {
122+
"type": "string",
123+
"description": "The maximum threshold for warning relative to the baseline."
124+
},
125+
"maximumError": {
126+
"type": "string",
127+
"description": "The maximum threshold for error relative to the baseline."
128+
},
129+
"minimumWarning": {
130+
"type": "string",
131+
"description": "The minimum threshold for warning relative to the baseline."
132+
},
133+
"minimumError": {
134+
"type": "string",
135+
"description": "The minimum threshold for error relative to the baseline."
136+
},
137+
"warning": {
138+
"type": "string",
139+
"description": "The threshold for warning relative to the baseline (min & max)."
140+
},
141+
"error": {
142+
"type": "string",
143+
"description": "The threshold for error relative to the baseline (min & max)."
144+
}
145+
}
146+
},
147+
"default": []
148+
},
102149
"deployUrl": {
103150
"type": "string",
104151
"description": "URL where files will be deployed."

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as semver from 'semver';
55
import { stripIndent } from 'common-tags';
66
import { LicenseWebpackPlugin } from 'license-webpack-plugin';
77
import { PurifyPlugin } from '@angular-devkit/build-optimizer';
8+
import { BundleBudgetPlugin } from '../../plugins/bundle-budget';
89
import { StaticAssetPlugin } from '../../plugins/static-asset';
910
import { GlobCopyWebpackPlugin } from '../../plugins/glob-copy-webpack-plugin';
1011
import { WebpackConfigOptions } from '../webpack-config';
@@ -108,6 +109,10 @@ export function getProdConfig(wco: WebpackConfigOptions) {
108109
}
109110
}
110111

112+
extraPlugins.push(new BundleBudgetPlugin({
113+
budgets: appConfig.budgets
114+
}));
115+
111116
if (buildOptions.extractLicenses) {
112117
extraPlugins.push(new LicenseWebpackPlugin({
113118
pattern: /^(MIT|ISC|BSD.*)$/,
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { Budget, calculateBytes, calculateSizes } from '../utilities/bundle-calculator';
9+
10+
interface Thresholds {
11+
maximumWarning?: number;
12+
maximumError?: number;
13+
minimumWarning?: number;
14+
minimumError?: number;
15+
warningLow?: number;
16+
warningHigh?: number;
17+
errorLow?: number;
18+
errorHigh?: number;
19+
}
20+
21+
export interface BundleBudgetPluginOptions {
22+
budgets: Budget[];
23+
}
24+
25+
export class BundleBudgetPlugin {
26+
constructor(private options: BundleBudgetPluginOptions) {}
27+
28+
apply(compiler: any): void {
29+
const { budgets } = this.options;
30+
compiler.plugin('after-emit', (compilation: any, cb: Function) => {
31+
if (!budgets || budgets.length === 0) {
32+
cb();
33+
return;
34+
}
35+
36+
budgets.map(budget => {
37+
const thresholds = this.calcualte(budget);
38+
return {
39+
budget,
40+
thresholds,
41+
sizes: calculateSizes(budget, compilation)
42+
};
43+
})
44+
.forEach(budgetCheck => {
45+
budgetCheck.sizes.forEach(size => {
46+
console.log('\nactual: ' + size.size);
47+
console.log(' comp: ' + budgetCheck.thresholds.maximumError);
48+
if (budgetCheck.thresholds.maximumWarning) {
49+
if (budgetCheck.thresholds.maximumWarning < size.size) {
50+
compilation.warnings.push(`budgets, maximum exceeded for ${size.label}.`);
51+
}
52+
}
53+
if (budgetCheck.thresholds.maximumError) {
54+
if (budgetCheck.thresholds.maximumError < size.size) {
55+
compilation.errors.push(`budgets, maximum exceeded for ${size.label}.`);
56+
}
57+
}
58+
if (budgetCheck.thresholds.minimumWarning) {
59+
if (budgetCheck.thresholds.minimumWarning > size.size) {
60+
compilation.warnings.push(`budgets, minimum exceeded for ${size.label}.`);
61+
}
62+
}
63+
if (budgetCheck.thresholds.minimumError) {
64+
if (budgetCheck.thresholds.minimumError > size.size) {
65+
compilation.errors.push(`budgets, minimum exceeded for ${size.label}.`);
66+
}
67+
}
68+
if (budgetCheck.thresholds.warningLow) {
69+
if (budgetCheck.thresholds.warningLow > size.size) {
70+
compilation.warnings.push(`budgets, minimum exceeded for ${size.label}.`);
71+
}
72+
}
73+
if (budgetCheck.thresholds.warningHigh) {
74+
if (budgetCheck.thresholds.warningHigh < size.size) {
75+
compilation.warnings.push(`budgets, maximum exceeded for ${size.label}.`);
76+
}
77+
}
78+
if (budgetCheck.thresholds.errorLow) {
79+
if (budgetCheck.thresholds.errorLow > size.size) {
80+
compilation.errors.push(`budgets, minimum exceeded for ${size.label}.`);
81+
}
82+
}
83+
if (budgetCheck.thresholds.errorHigh) {
84+
if (budgetCheck.thresholds.errorHigh < size.size) {
85+
compilation.errors.push(`budgets, maximum exceeded for ${size.label}.`);
86+
}
87+
}
88+
});
89+
90+
});
91+
cb();
92+
});
93+
}
94+
95+
private calcualte(budget: Budget): Thresholds {
96+
let thresholds: Thresholds = {};
97+
if (budget.maximumWarning) {
98+
thresholds.maximumWarning = calculateBytes(budget.maximumWarning, budget.baseline, 'pos');
99+
}
100+
101+
if (budget.maximumError) {
102+
thresholds.maximumError = calculateBytes(budget.maximumError, budget.baseline, 'pos');
103+
}
104+
105+
if (budget.minimumWarning) {
106+
thresholds.minimumWarning = calculateBytes(budget.minimumWarning, budget.baseline, 'neg');
107+
}
108+
109+
if (budget.minimumError) {
110+
thresholds.minimumError = calculateBytes(budget.minimumError, budget.baseline, 'neg');
111+
}
112+
113+
if (budget.warning) {
114+
thresholds.warningLow = calculateBytes(budget.warning, budget.baseline, 'neg');
115+
}
116+
117+
if (budget.warning) {
118+
thresholds.warningHigh = calculateBytes(budget.warning, budget.baseline, 'pos');
119+
}
120+
121+
if (budget.error) {
122+
thresholds.errorLow = calculateBytes(budget.error, budget.baseline, 'neg');
123+
}
124+
125+
if (budget.error) {
126+
thresholds.errorHigh = calculateBytes(budget.error, budget.baseline, 'pos');
127+
}
128+
129+
return thresholds;
130+
}
131+
}

packages/@angular/cli/plugins/webpack.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
export { BaseHrefWebpackPlugin } from '../lib/base-href-webpack/base-href-webpack-plugin';
33
export { CleanCssWebpackPlugin, CleanCssWebpackPluginOptions } from './cleancss-webpack-plugin';
44
export { GlobCopyWebpackPlugin, GlobCopyWebpackPluginOptions } from './glob-copy-webpack-plugin';
5+
export { BundleBudgetPlugin, BundleBudgetPluginOptions } from './bundle-budget';
56
export { NamedLazyChunksWebpackPlugin } from './named-lazy-chunks-webpack-plugin';
67
export { ScriptsWebpackPlugin, ScriptsWebpackPluginOptions } from './scripts-webpack-plugin';
78
export { SuppressExtractedTextChunksWebpackPlugin } from './suppress-entry-chunks-webpack-plugin';

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ class JsonWebpackSerializer {
9090
};
9191
}
9292

93+
private _bundleBudgetPluginSerialize(value: any): any {
94+
console.log('VALUE!!!');
95+
console.log(value);
96+
let budgets = value.options.budgets;
97+
return {
98+
budgets
99+
};
100+
}
101+
93102
private _insertConcatAssetsWebpackPluginSerialize(value: any): any {
94103
return value.entryNames;
95104
}
@@ -200,6 +209,10 @@ class JsonWebpackSerializer {
200209
args = this._globCopyWebpackPluginSerialize(plugin);
201210
this._addImport('@angular/cli/plugins/webpack', 'GlobCopyWebpackPlugin');
202211
break;
212+
case angularCliPlugins.BundleBudgetPlugin:
213+
args = this._bundleBudgetPluginSerialize(plugin);
214+
this._addImport('@angular/cli/plugins/webpack', 'BundleBudgetPlugin');
215+
break;
203216
case angularCliPlugins.InsertConcatAssetsWebpackPlugin:
204217
args = this._insertConcatAssetsWebpackPluginSerialize(plugin);
205218
this._addImport('@angular/cli/plugins/webpack', 'InsertConcatAssetsWebpackPlugin');

0 commit comments

Comments
 (0)