Skip to content

Commit 4f1ae22

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

File tree

8 files changed

+574
-0
lines changed

8 files changed

+574
-0
lines changed

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.calcualteThresholds(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 calcualteThresholds(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+
}
+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Exports the webpack plugins we use internally.
22
export { BaseHrefWebpackPlugin } from '../lib/base-href-webpack/base-href-webpack-plugin';
33
export { GlobCopyWebpackPlugin, GlobCopyWebpackPluginOptions } from './glob-copy-webpack-plugin';
4+
export { BundleBudgetPlugin, BundleBudgetPluginOptions } from './bundle-budget';
45
export { NamedLazyChunksWebpackPlugin } from './named-lazy-chunks-webpack-plugin';
56
export { ScriptsWebpackPlugin, ScriptsWebpackPluginOptions } from './scripts-webpack-plugin';
67
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
}
@@ -199,6 +208,10 @@ class JsonWebpackSerializer {
199208
args = this._globCopyWebpackPluginSerialize(plugin);
200209
this._addImport('@angular/cli/plugins/webpack', 'GlobCopyWebpackPlugin');
201210
break;
211+
case angularCliPlugins.BundleBudgetPlugin:
212+
args = this._bundleBudgetPluginSerialize(plugin);
213+
this._addImport('@angular/cli/plugins/webpack', 'BundleBudgetPlugin');
214+
break;
202215
case angularCliPlugins.InsertConcatAssetsWebpackPlugin:
203216
args = this._insertConcatAssetsWebpackPluginSerialize(plugin);
204217
this._addImport('@angular/cli/plugins/webpack', 'InsertConcatAssetsWebpackPlugin');

0 commit comments

Comments
 (0)