Skip to content

Commit b57204b

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

File tree

3 files changed

+64
-3
lines changed

3 files changed

+64
-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: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
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';
78
import { NgCliWebpackConfig } from '../models/webpack-config';
89
import { getWebpackStatsConfig } from '../models/webpack-configs/utils';
910
import { CliConfig } from '../models/config';
1011
import { statsToString, statsWarningsToString, statsErrorsToString } from '../utilities/stats';
12+
import { oneLine } from 'common-tags';
1113

1214
const Task = require('../ember-cli/lib/models/task');
1315
const SilentError = require('silent-error');
@@ -17,9 +19,9 @@ export default Task.extend({
1719
run: function (runTaskOptions: BuildTaskOptions) {
1820
const config = CliConfig.fromProject().config;
1921

20-
const app = getAppFromConfig(runTaskOptions.app);
22+
const appConfig = getAppFromConfig(runTaskOptions.app);
2123

22-
const outputPath = runTaskOptions.outputPath || app.outDir;
24+
const outputPath = runTaskOptions.outputPath || appConfig.outDir;
2325
if (this.project.root === path.resolve(outputPath)) {
2426
throw new SilentError('Output path MUST not be project root directory!');
2527
}
@@ -30,7 +32,7 @@ export default Task.extend({
3032
fs.removeSync(path.resolve(this.project.root, outputPath));
3133
}
3234

33-
const webpackConfig = new NgCliWebpackConfig(runTaskOptions, app).buildConfig();
35+
const webpackConfig = new NgCliWebpackConfig(runTaskOptions, appConfig).buildConfig();
3436
const webpackCompiler = webpack(webpackConfig);
3537
const statsConfig = getWebpackStatsConfig(runTaskOptions.verbose);
3638

@@ -47,6 +49,27 @@ export default Task.extend({
4749
this.ui.writeLine(statsToString(json, statsConfig));
4850
}
4951

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

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)