Skip to content

Commit 9642296

Browse files
committed
feat(@angular/cli): Allow ability to set budget sizes for your bundles
Closes #7139
1 parent 5459d37 commit 9642296

File tree

3 files changed

+60
-3
lines changed

3 files changed

+60
-3
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,22 @@
192192
"description": "Name and corresponding file for environment config.",
193193
"type": "object",
194194
"additionalProperties": true
195+
},
196+
"budgets": {
197+
"description": "Defines the budget allotments for bundles.",
198+
"type": "array",
199+
"items": {"type": "object",
200+
"properties": {
201+
"name": {
202+
"description": "Name of the bundle.",
203+
"type": "string"
204+
},
205+
"budget": {
206+
"description": "Threshold to measure against. (in kb)",
207+
"type": "number"
208+
}
209+
}
210+
}
195211
}
196212
},
197213
"additionalProperties": false

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as fs from 'fs-extra';
22
import * as path from 'path';
33
import * as webpack from 'webpack';
4+
import { red } from 'chalk';
45

56
import { getAppFromConfig } from '../utilities/app-utils';
67
import { BuildTaskOptions } from '../commands/build';
@@ -17,9 +18,9 @@ export default Task.extend({
1718
run: function (runTaskOptions: BuildTaskOptions) {
1819
const config = CliConfig.fromProject().config;
1920

20-
const app = getAppFromConfig(runTaskOptions.app);
21+
const appConfig = getAppFromConfig(runTaskOptions.app);
2122

22-
const outputPath = runTaskOptions.outputPath || app.outDir;
23+
const outputPath = runTaskOptions.outputPath || appConfig.outDir;
2324
if (this.project.root === path.resolve(outputPath)) {
2425
throw new SilentError('Output path MUST not be project root directory!');
2526
}
@@ -30,7 +31,7 @@ export default Task.extend({
3031
fs.removeSync(path.resolve(this.project.root, outputPath));
3132
}
3233

33-
const webpackConfig = new NgCliWebpackConfig(runTaskOptions, app).buildConfig();
34+
const webpackConfig = new NgCliWebpackConfig(runTaskOptions, appConfig).buildConfig();
3435
const webpackCompiler = webpack(webpackConfig);
3536
const statsConfig = getWebpackStatsConfig(runTaskOptions.verbose);
3637

@@ -47,6 +48,24 @@ export default Task.extend({
4748
this.ui.writeLine(statsToString(json, statsConfig));
4849
}
4950

51+
if (runTaskOptions.target === 'production' && !!appConfig.budgets) {
52+
let budgetErrors: string[] = [];
53+
appConfig.budgets.forEach((budget: {name: string; budget: number}) => {
54+
const chunk = json.chunks.filter((c: {names: string[];}) => c.names.indexOf(budget.name) !== -1)[0];
55+
const asset = json.assets.filter((x: any) => x.name == chunk.files[0])[0];
56+
const size = asset.size / 1000;
57+
if (size > budget.budget) {
58+
budgetErrors.push(`${budget.name} budget: ${budget.budget} kB size: ${size.toPrecision(3)} kB`);
59+
}
60+
});
61+
if (budgetErrors.length > 0) {
62+
const budgetError = 'Allowed bundle budgets have been exceeded';
63+
this.ui.writeLine(red(`\n...${budgetError}`))
64+
budgetErrors.forEach(err => this.ui.writeLine(red(` ${err}`)));
65+
reject(budgetError);
66+
}
67+
}
68+
5069
if (stats.hasWarnings()) {
5170
this.ui.writeLine(statsWarningsToString(json, statsConfig));
5271
}

tests/e2e/tests/build/budgets.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {ng} from '../../utils/process';
2+
import {updateJsonFile} from '../../utils/project';
3+
import {expectToFail} from '../../utils/utils';
4+
5+
6+
export default function() {
7+
// Skip this in ejected tests.
8+
9+
// Can't use the `ng` helper because somewhere the environment gets
10+
// stuck to the first build done
11+
return Promise.resolve()
12+
.then(() => updateJsonFile('.angular-cli.json', configJson => {
13+
const app = configJson['apps'][0];
14+
app['budgets'] = [{ name: 'main', budget: 100000 }]
15+
}))
16+
.then(() => ng('build', '--prod'))
17+
.then(() => updateJsonFile('.angular-cli.json', configJson => {
18+
const app = configJson['apps'][0];
19+
app['budgets'] = [{ name: 'main', budget: 0 }]
20+
}))
21+
.then(() => expectToFail(() => ng('build', '--prod')));
22+
}

0 commit comments

Comments
 (0)