Skip to content

Commit bf1e8af

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

File tree

10 files changed

+636
-0
lines changed

10 files changed

+636
-0
lines changed

docs/documentation/stories.md

+1
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

+61
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 sized are relative to baseline.
61+
Percentages are not valid for baseline values.

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

+47
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

+5
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.*)$/,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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+
if (budgetCheck.thresholds.maximumWarning) {
47+
if (budgetCheck.thresholds.maximumWarning < size.size) {
48+
compilation.warnings.push(`budgets, maximum exceeded for ${size.label}.`);
49+
}
50+
}
51+
if (budgetCheck.thresholds.maximumError) {
52+
if (budgetCheck.thresholds.maximumError < size.size) {
53+
compilation.errors.push(`budgets, maximum exceeded for ${size.label}.`);
54+
}
55+
}
56+
if (budgetCheck.thresholds.minimumWarning) {
57+
if (budgetCheck.thresholds.minimumWarning > size.size) {
58+
compilation.warnings.push(`budgets, minimum exceeded for ${size.label}.`);
59+
}
60+
}
61+
if (budgetCheck.thresholds.minimumError) {
62+
if (budgetCheck.thresholds.minimumError > size.size) {
63+
compilation.errors.push(`budgets, minimum exceeded for ${size.label}.`);
64+
}
65+
}
66+
if (budgetCheck.thresholds.warningLow) {
67+
if (budgetCheck.thresholds.warningLow > size.size) {
68+
compilation.warnings.push(`budgets, minimum exceeded for ${size.label}.`);
69+
}
70+
}
71+
if (budgetCheck.thresholds.warningHigh) {
72+
if (budgetCheck.thresholds.warningHigh < size.size) {
73+
compilation.warnings.push(`budgets, maximum exceeded for ${size.label}.`);
74+
}
75+
}
76+
if (budgetCheck.thresholds.errorLow) {
77+
if (budgetCheck.thresholds.errorLow > size.size) {
78+
compilation.errors.push(`budgets, minimum exceeded for ${size.label}.`);
79+
}
80+
}
81+
if (budgetCheck.thresholds.errorHigh) {
82+
if (budgetCheck.thresholds.errorHigh < size.size) {
83+
compilation.errors.push(`budgets, maximum exceeded for ${size.label}.`);
84+
}
85+
}
86+
});
87+
88+
});
89+
cb();
90+
});
91+
}
92+
93+
private calcualte(budget: Budget): Thresholds {
94+
let thresholds: Thresholds = {};
95+
if (budget.maximumWarning) {
96+
thresholds = { ...thresholds,
97+
maximumWarning: calculateBytes(budget.maximumWarning, budget.baseline, 'pos')
98+
};
99+
}
100+
if (budget.maximumError) {
101+
thresholds = { ...thresholds,
102+
maximumError: calculateBytes(budget.maximumError, budget.baseline, 'pos')
103+
};
104+
}
105+
106+
if (budget.minimumWarning) {
107+
thresholds = { ...thresholds,
108+
minimumWarning: calculateBytes(budget.minimumWarning, budget.baseline, 'neg')
109+
};
110+
}
111+
112+
if (budget.minimumError) {
113+
thresholds = { ...thresholds,
114+
minimumError: calculateBytes(budget.minimumError, budget.baseline, 'neg')
115+
};
116+
}
117+
118+
if (budget.warning) {
119+
thresholds = { ...thresholds,
120+
warningLow: calculateBytes(budget.warning, budget.baseline, 'neg')
121+
};
122+
}
123+
124+
if (budget.warning) {
125+
thresholds = { ...thresholds,
126+
warningHigh: calculateBytes(budget.warning, budget.baseline, 'pos')
127+
};
128+
}
129+
130+
if (budget.error) {
131+
thresholds = { ...thresholds,
132+
errorLow: calculateBytes(budget.error, budget.baseline, 'neg')
133+
};
134+
}
135+
136+
if (budget.error) {
137+
thresholds = { ...thresholds,
138+
errorHigh: calculateBytes(budget.error, budget.baseline, 'pos')
139+
};
140+
}
141+
142+
return thresholds;
143+
}
144+
}

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

+1
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

+13
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)