Skip to content

Commit 9c871b0

Browse files
Broccohansl
authored andcommittedJan 18, 2018
feat(@angular/cli): Ability to specify budgets for your apps
Closes #7139
1 parent 23fee3d commit 9c871b0

File tree

10 files changed

+621
-0
lines changed

10 files changed

+621
-0
lines changed
 

Diff for: ‎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)

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

Diff for: ‎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."

Diff for: ‎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.*)$/,

Diff for: ‎packages/@angular/cli/plugins/bundle-budget.ts

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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 {
Has a conversation. Original line has a conversation.
94+
let thresholds: Thresholds = {};
95+
if (budget.maximumWarning) {
96+
thresholds.maximumWarning = calculateBytes(budget.maximumWarning, budget.baseline, 'pos');
97+
}
98+
99+
if (budget.maximumError) {
100+
thresholds.maximumError = calculateBytes(budget.maximumError, budget.baseline, 'pos');
101+
}
102+
103+
if (budget.minimumWarning) {
104+
thresholds.minimumWarning = calculateBytes(budget.minimumWarning, budget.baseline, 'neg');
105+
}
106+
107+
if (budget.minimumError) {
108+
thresholds.minimumError = calculateBytes(budget.minimumError, budget.baseline, 'neg');
109+
}
110+
111+
if (budget.warning) {
112+
thresholds.warningLow = calculateBytes(budget.warning, budget.baseline, 'neg');
113+
}
114+
115+
if (budget.warning) {
116+
thresholds.warningHigh = calculateBytes(budget.warning, budget.baseline, 'pos');
117+
}
118+
119+
if (budget.error) {
120+
thresholds.errorLow = calculateBytes(budget.error, budget.baseline, 'neg');
121+
}
122+
123+
if (budget.error) {
124+
thresholds.errorHigh = calculateBytes(budget.error, budget.baseline, 'pos');
125+
}
126+
127+
return thresholds;
128+
}
129+
}

Diff for: ‎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';

Diff for: ‎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');

Diff for: ‎packages/@angular/cli/utilities/bundle-calculator.ts

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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+
export type BudgetType = 'all' | 'allScript' | 'any' | 'anyScript' | 'bundle' | 'initial';
9+
10+
export interface Budget {
11+
/**
12+
* The type of budget
13+
*/
14+
type: BudgetType;
15+
/**
16+
* The name of the bundle
17+
*/
18+
name?: string;
19+
/**
20+
* The baseline size for comparison.
21+
*/
22+
baseline?: string;
23+
/**
24+
* The maximum threshold for warning relative to the baseline.
25+
*/
26+
maximumWarning?: string;
27+
/**
28+
* The maximum threshold for error relative to the baseline.
29+
*/
30+
maximumError?: string;
31+
/**
32+
* The minimum threshold for warning relative to the baseline.
33+
*/
34+
minimumWarning?: string;
35+
/**
36+
* The minimum threshold for error relative to the baseline.
37+
*/
38+
minimumError?: string;
39+
/**
40+
* The threshold for warning relative to the baseline (min & max).
41+
*/
42+
warning?: string;
43+
/**
44+
* The threshold for error relative to the baseline (min & max).
45+
*/
46+
error?: string;
47+
}
48+
49+
export interface Compilation {
50+
assets: any;
51+
chunks: any[];
52+
warnings: string[];
53+
errors: string[];
54+
}
55+
56+
export interface Size {
57+
size: number;
58+
label?: string;
59+
}
60+
61+
export function calculateSizes(budget: Budget, compilation: Compilation): Size[] {
62+
const calculatorMap = {
63+
all: AllCalculator,
64+
allScript: AllScriptCalculator,
65+
any: AnyCalculator,
66+
anyScript: AnyScriptCalculator,
67+
bundle: BundleCalculator,
68+
initial: InitialCalculator,
69+
};
70+
const ctor = calculatorMap[budget.type];
71+
const calculator = new ctor(budget, compilation);
72+
return calculator.calculate();
73+
}
74+
75+
export abstract class Calculator {
76+
constructor (protected budget: Budget, protected compilation: Compilation) {}
77+
78+
abstract calculate(): Size[];
79+
}
80+
81+
/**
82+
* A named bundle.
83+
*/
84+
class BundleCalculator extends Calculator {
85+
calculate() {
86+
const size: number = this.compilation.chunks
87+
.filter(chunk => chunk.name === this.budget.name)
88+
.reduce((files, chunk) => [...files, ...chunk.files], [])
89+
.map((file: string) => this.compilation.assets[file].size())
90+
.reduce((total: number, size: number) => total + size, 0);
91+
return [{size, label: this.budget.name}];
92+
}
93+
}
94+
95+
/**
96+
* The sum of all initial chunks (marked as initial by webpack).
97+
*/
98+
class InitialCalculator extends Calculator {
99+
calculate() {
100+
const initialChunks = this.compilation.chunks.filter(chunk => chunk.isInitial);
101+
const size: number = initialChunks
102+
.reduce((files, chunk) => [...files, ...chunk.files], [])
103+
.map((file: string) => this.compilation.assets[file].size())
104+
.reduce((total: number, size: number) => total + size, 0);
105+
return [{size, label: 'initial'}];
106+
}
107+
}
108+
109+
/**
110+
* The sum of all the scripts portions.
111+
*/
112+
class AllScriptCalculator extends Calculator {
113+
calculate() {
114+
const size: number = Object.keys(this.compilation.assets)
115+
.filter(key => /\.js$/.test(key))
116+
.map(key => this.compilation.assets[key])
117+
.map(asset => asset.size())
118+
.reduce((total: number, size: number) => total + size, 0);
119+
return [{size, label: 'total scripts'}];
120+
}
121+
}
122+
123+
/**
124+
* All scripts and assets added together.
125+
*/
126+
class AllCalculator extends Calculator {
127+
calculate() {
128+
const size: number = Object.keys(this.compilation.assets)
129+
.map(key => this.compilation.assets[key].size())
130+
.reduce((total: number, size: number) => total + size, 0);
131+
return [{size, label: 'total'}];
132+
}
133+
}
134+
135+
/**
136+
* Any script, individually.
137+
*/
138+
class AnyScriptCalculator extends Calculator {
139+
calculate() {
140+
return Object.keys(this.compilation.assets)
141+
.filter(key => /\.js$/.test(key))
142+
.map(key => {
143+
const asset = this.compilation.assets[key];
144+
return {
145+
size: asset.size(),
146+
label: key
147+
};
148+
});
149+
}
150+
}
151+
152+
/**
153+
* Any script or asset (images, css, etc).
154+
*/
155+
class AnyCalculator extends Calculator {
156+
calculate() {
157+
return Object.keys(this.compilation.assets)
158+
.map(key => {
159+
const asset = this.compilation.assets[key];
160+
return {
161+
size: asset.size(),
162+
label: key
163+
};
164+
});
165+
}
166+
}
167+
168+
/**
169+
* Calculate the bytes given a string value.
170+
*/
171+
export function calculateBytes(val: string, baseline?: string, factor?: ('pos' | 'neg')): number {
172+
if (/^\d+$/.test(val)) {
173+
return parseFloat(val);
174+
}
175+
176+
if (/^(\d+)%$/.test(val)) {
177+
return calculatePercentBytes(val, baseline, factor);
178+
}
179+
180+
const multiplier = getMultiplier(val);
181+
182+
const numberVal = parseFloat(val.replace(/((k|m|M|)b?)$/, ''));
183+
const baselineVal = baseline ? parseFloat(baseline.replace(/((k|m|M|)b?)$/, '')) : 0;
184+
const baselineMultiplier = baseline ? getMultiplier(baseline) : 1;
185+
const factorMultiplier = factor ? (factor === 'pos' ? 1 : -1) : 1;
186+
187+
return numberVal * multiplier + baselineVal * baselineMultiplier * factorMultiplier;
188+
}
189+
190+
function getMultiplier(val?: string): number {
191+
if (/^(\d+)b?$/.test(val)) {
192+
return 1;
193+
} else if (/^(\d+)kb$/.test(val)) {
194+
return 1000;
195+
} else if (/^(\d+)(m|M)b$/.test(val)) {
196+
return 1000 * 1000;
197+
}
198+
}
199+
200+
function calculatePercentBytes(val: string, baseline?: string, factor?: ('pos' | 'neg')): number {
201+
const baselineBytes = calculateBytes(baseline);
202+
const percentage = parseFloat(val.replace(/%/g, ''));
203+
return baselineBytes + baselineBytes * percentage / 100 * (factor === 'pos' ? 1 : -1);
204+
}

Diff for: ‎tests/acceptance/bundle-calculator.spec.ts

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import * as path from 'path';
2+
import { calculateBytes, calculateSizes } from '@angular/cli/utilities/bundle-calculator';
3+
import mockFs = require('mock-fs');
4+
5+
6+
describe('bundle calculator', () => {
7+
describe('calculateBytes', () => {
8+
const kb = (n: number) => n * 1000;
9+
const mb = (n: number) => n * 1000 * 1000;
10+
const scenarios: any[] = [
11+
{ expect: 1, val: '1' },
12+
{ expect: 1, val: '1b' },
13+
{ expect: kb(1), val: '1kb' },
14+
{ expect: mb(1), val: '1mb' },
15+
{ expect: 110, val: '100b', baseline: '10', factor: 'pos' },
16+
{ expect: 110, val: '100b', baseline: '10b', factor: 'pos' },
17+
{ expect: 90, val: '100b', baseline: '10', factor: 'neg' },
18+
{ expect: 90, val: '100b', baseline: '10b', factor: 'neg' },
19+
{ expect: 15, val: '50%', baseline: '10', factor: 'pos' },
20+
{ expect: 5, val: '50%', baseline: '10', factor: 'neg' },
21+
{ expect: kb(50) + mb(1), val: '50kb', baseline: '1mb', factor: 'pos' },
22+
{ expect: mb(1.25), val: '25%', baseline: '1mb', factor: 'pos' },
23+
{ expect: mb(0.75), val: '25%', baseline: '1mb', factor: 'neg' },
24+
];
25+
scenarios.forEach(s => {
26+
const specMsg = `${s.val} => ${s.expect}`;
27+
const baselineMsg = s.baseline ? ` (baseline: ${s.baseline})` : ``;
28+
const factor = s.factor ? ` (factor: ${s.factor})` : ``;
29+
it(`should calculateBytes ${specMsg}${baselineMsg}${factor}`, () => {
30+
const result = calculateBytes(s.val, s.baseline, s.factor);
31+
expect(s.expect).toEqual(result);
32+
});
33+
});
34+
});
35+
36+
describe('calculateSizes', () => {
37+
let compilation: any;
38+
beforeEach(() => {
39+
compilation = {
40+
assets: {
41+
'asset1.js': { size: () => 1 },
42+
'asset2': { size: () => 2 },
43+
'asset3.js': { size: () => 4 },
44+
'asset4': { size: () => 8 },
45+
'asset5': { size: () => 16 },
46+
},
47+
chunks: [
48+
{ name: 'chunk1', files: ['asset1.js'], isInitial: true },
49+
{ name: 'chunk2', files: ['asset2'], isInitial: false },
50+
{ name: 'chunk3', files: ['asset3.js', 'asset4'], isInitial: false }
51+
]
52+
};
53+
});
54+
55+
const scenarios: any[] = [
56+
{ expect: [{size: 31, label: 'total'}], budget: { type: 'all' } },
57+
{ expect: [{size: 5, label: 'total scripts'}], budget: { type: 'allScript' } },
58+
{ expect: [
59+
{size: 1, label: 'asset1.js'},
60+
{size: 2, label: 'asset2'},
61+
{size: 4, label: 'asset3.js'},
62+
{size: 8, label: 'asset4'},
63+
{size: 16, label: 'asset5'},
64+
], budget: { type: 'any' } },
65+
{ expect: [
66+
{size: 1, label: 'asset1.js'},
67+
{size: 4, label: 'asset3.js'},
68+
], budget: { type: 'anyScript' } },
69+
{ expect: [{size: 2, label: 'chunk2'}], budget: { type: 'bundle', name: 'chunk2' } },
70+
{ expect: [{size: 12, label: 'chunk3'}], budget: { type: 'bundle', name: 'chunk3' } },
71+
{ expect: [{size: 1, label: 'initial'}], budget: { type: 'initial' } },
72+
];
73+
74+
scenarios.forEach(s => {
75+
const budgetName = s.budget.name ? ` (${s.budget.name})` : '';
76+
it(`should calulate sizes for ${s.budget.type}${budgetName}`, () => {
77+
const sizes = calculateSizes(s.budget, compilation);
78+
expect(sizes.length).toEqual(s.expect.length);
79+
for (let i = 0; i < sizes.length; i++) {
80+
expect(sizes[i].size).toEqual((s.expect[i].size));
81+
expect(sizes[i].label).toEqual((s.expect[i].label));
82+
}
83+
});
84+
});
85+
});
86+
});

Diff for: ‎tests/e2e/tests/build/bundle-budgets.ts

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
9+
import { getGlobalVariable } from '../../utils/env';
10+
import { ng } from '../../utils/process';
11+
import { updateJsonFile } from '../../utils/project';
12+
import { expectToFail } from '../../utils/utils';
13+
14+
// tslint:disable:max-line-length
15+
export default function () {
16+
const budgetConfigs = [
17+
{
18+
expectation: 'pass',
19+
message: 'BIG max for all, should not error',
20+
budget: { type: 'allScript', maximumError: '100mb' },
21+
},
22+
{
23+
expectation: 'error',
24+
message: 'Budget error: all, max error',
25+
budget: { type: 'all', maximumError: '100b' },
26+
},
27+
{
28+
expectation: 'warning',
29+
message: 'Budget warning: all, min warning',
30+
budget: { type: 'all', minimumWarning: '100mb' },
31+
}
32+
];
33+
34+
const promiseFactories = budgetConfigs.map(cfg => {
35+
if (cfg.expectation === 'error') {
36+
return () => {
37+
return updateJsonFile('.angular-cli.json', (json) => { json.apps[0].budgets = [cfg.budget]; })
38+
.then(() => expectToFail(() => ng('build', '--prod')))
39+
.then(errorMessage => {
40+
if (!/ERROR in budgets/.test(errorMessage)) {
41+
throw new Error(cfg.message);
42+
}
43+
});
44+
};
45+
} else if (cfg.expectation === 'warning') {
46+
return () => {
47+
return updateJsonFile('.angular-cli.json', (json) => { json.apps[0].budgets = [cfg.budget]; })
48+
.then(() => ng('build', '--prod'))
49+
.then(({ stdout }) => {
50+
if (!/WARNING in budgets/.test(stdout)) {
51+
throw new Error(cfg.message);
52+
}
53+
});
54+
};
55+
} else { // pass
56+
return () => {
57+
return updateJsonFile('.angular-cli.json', (json) => { json.apps[0].budgets = [cfg.budget]; })
58+
.then(() => ng('build', '--prod'))
59+
.then(({ stdout }) => {
60+
if (/(WARNING|ERROR)/.test(stdout)) {
61+
throw new Error(cfg.message);
62+
}
63+
});
64+
};
65+
}
66+
});
67+
68+
let promiseChain = Promise.resolve();
69+
for (let i = 0; i < promiseFactories.length; i++) {
70+
promiseChain = promiseChain.then(promiseFactories[i]);
71+
}
72+
73+
return promiseChain;
74+
}

0 commit comments

Comments
 (0)
Please sign in to comment.