Skip to content

Commit 67e32a8

Browse files
clydinvikerman
authored andcommitted
fix(@angular-devkit/build-angular): improve bundle size value parsing (#12026)
Fixes #12013
1 parent 9af2368 commit 67e32a8

File tree

3 files changed

+133
-57
lines changed

3 files changed

+133
-57
lines changed

packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
1-
// tslint:disable
2-
// TODO: cleanup this file, it's copied as is from Angular CLI.
3-
41
/**
52
* @license
63
* Copyright Google Inc. All Rights Reserved.
74
*
85
* Use of this source code is governed by an MIT-style license that can be
96
* found in the LICENSE file at https://angular.io/license
107
*/
11-
12-
import { Size, calculateBytes, calculateSizes } from '../utilities/bundle-calculator';
8+
import { Compiler, compilation } from 'webpack';
139
import { Budget } from '../../browser/schema';
10+
import { Size, calculateBytes, calculateSizes } from '../utilities/bundle-calculator';
1411
import { formatSize } from '../utilities/stats';
1512

1613
interface Thresholds {
@@ -31,19 +28,20 @@ export interface BundleBudgetPluginOptions {
3128
export class BundleBudgetPlugin {
3229
constructor(private options: BundleBudgetPluginOptions) { }
3330

34-
apply(compiler: any): void {
31+
apply(compiler: Compiler): void {
3532
const { budgets } = this.options;
36-
compiler.hooks.afterEmit.tap('BundleBudgetPlugin', (compilation: any) => {
33+
compiler.hooks.afterEmit.tap('BundleBudgetPlugin', (compilation: compilation.Compilation) => {
3734
if (!budgets || budgets.length === 0) {
3835
return;
3936
}
4037

4138
budgets.map(budget => {
4239
const thresholds = this.calculate(budget);
40+
4341
return {
4442
budget,
4543
thresholds,
46-
sizes: calculateSizes(budget, compilation)
44+
sizes: calculateSizes(budget, compilation),
4745
};
4846
})
4947
.forEach(budgetCheck => {
@@ -62,7 +60,7 @@ export class BundleBudgetPlugin {
6260
});
6361
}
6462

65-
private checkMinimum(threshold: number | undefined, size: Size, messages: any) {
63+
private checkMinimum(threshold: number | undefined, size: Size, messages: string[]) {
6664
if (threshold) {
6765
if (threshold > size.size) {
6866
const sizeDifference = formatSize(threshold - size.size);
@@ -72,7 +70,7 @@ export class BundleBudgetPlugin {
7270
}
7371
}
7472

75-
private checkMaximum(threshold: number | undefined, size: Size, messages: any) {
73+
private checkMaximum(threshold: number | undefined, size: Size, messages: string[]) {
7674
if (threshold) {
7775
if (threshold < size.size) {
7876
const sizeDifference = formatSize(size.size - threshold);
@@ -83,37 +81,37 @@ export class BundleBudgetPlugin {
8381
}
8482

8583
private calculate(budget: Budget): Thresholds {
86-
let thresholds: Thresholds = {};
84+
const thresholds: Thresholds = {};
8785
if (budget.maximumWarning) {
88-
thresholds.maximumWarning = calculateBytes(budget.maximumWarning, budget.baseline, 'pos');
86+
thresholds.maximumWarning = calculateBytes(budget.maximumWarning, budget.baseline, 1);
8987
}
9088

9189
if (budget.maximumError) {
92-
thresholds.maximumError = calculateBytes(budget.maximumError, budget.baseline, 'pos');
90+
thresholds.maximumError = calculateBytes(budget.maximumError, budget.baseline, 1);
9391
}
9492

9593
if (budget.minimumWarning) {
96-
thresholds.minimumWarning = calculateBytes(budget.minimumWarning, budget.baseline, 'neg');
94+
thresholds.minimumWarning = calculateBytes(budget.minimumWarning, budget.baseline, -1);
9795
}
9896

9997
if (budget.minimumError) {
100-
thresholds.minimumError = calculateBytes(budget.minimumError, budget.baseline, 'neg');
98+
thresholds.minimumError = calculateBytes(budget.minimumError, budget.baseline, -1);
10199
}
102100

103101
if (budget.warning) {
104-
thresholds.warningLow = calculateBytes(budget.warning, budget.baseline, 'neg');
102+
thresholds.warningLow = calculateBytes(budget.warning, budget.baseline, -1);
105103
}
106104

107105
if (budget.warning) {
108-
thresholds.warningHigh = calculateBytes(budget.warning, budget.baseline, 'pos');
106+
thresholds.warningHigh = calculateBytes(budget.warning, budget.baseline, 1);
109107
}
110108

111109
if (budget.error) {
112-
thresholds.errorLow = calculateBytes(budget.error, budget.baseline, 'neg');
110+
thresholds.errorLow = calculateBytes(budget.error, budget.baseline, -1);
113111
}
114112

115113
if (budget.error) {
116-
thresholds.errorHigh = calculateBytes(budget.error, budget.baseline, 'pos');
114+
thresholds.errorHigh = calculateBytes(budget.error, budget.baseline, 1);
117115
}
118116

119117
return thresholds;

packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// tslint:disable
2-
// TODO: cleanup this file, it's copied as is from Angular CLI.
3-
41
/**
52
* @license
63
* Copyright Google Inc. All Rights Reserved.
@@ -11,8 +8,8 @@
118
import { Budget } from '../../browser/schema';
129

1310
export interface Compilation {
14-
assets: any;
15-
chunks: any[];
11+
assets: { [name: string]: { size: () => number } };
12+
chunks: { name: string, files: string[], isOnlyInitial: () => boolean }[];
1613
warnings: string[];
1714
errors: string[];
1815
}
@@ -33,6 +30,7 @@ export function calculateSizes(budget: Budget, compilation: Compilation): Size[]
3330
};
3431
const ctor = calculatorMap[budget.type];
3532
const calculator = new ctor(budget, compilation);
33+
3634
return calculator.calculate();
3735
}
3836

@@ -52,6 +50,7 @@ class BundleCalculator extends Calculator {
5250
.reduce((files, chunk) => [...files, ...chunk.files], [])
5351
.map((file: string) => this.compilation.assets[file].size())
5452
.reduce((total: number, size: number) => total + size, 0);
53+
5554
return [{size, label: this.budget.name}];
5655
}
5756
}
@@ -67,6 +66,7 @@ class InitialCalculator extends Calculator {
6766
.filter((file: string) => !file.endsWith('.map'))
6867
.map((file: string) => this.compilation.assets[file].size())
6968
.reduce((total: number, size: number) => total + size, 0);
69+
7070
return [{size, label: 'initial'}];
7171
}
7272
}
@@ -81,6 +81,7 @@ class AllScriptCalculator extends Calculator {
8181
.map(key => this.compilation.assets[key])
8282
.map(asset => asset.size())
8383
.reduce((total: number, size: number) => total + size, 0);
84+
8485
return [{size, label: 'total scripts'}];
8586
}
8687
}
@@ -94,6 +95,7 @@ class AllCalculator extends Calculator {
9495
.filter(key => !key.endsWith('.map'))
9596
.map(key => this.compilation.assets[key].size())
9697
.reduce((total: number, size: number) => total + size, 0);
98+
9799
return [{size, label: 'total'}];
98100
}
99101
}
@@ -107,9 +109,10 @@ class AnyScriptCalculator extends Calculator {
107109
.filter(key => key.endsWith('.js'))
108110
.map(key => {
109111
const asset = this.compilation.assets[key];
112+
110113
return {
111114
size: asset.size(),
112-
label: key
115+
label: key,
113116
};
114117
});
115118
}
@@ -124,9 +127,10 @@ class AnyCalculator extends Calculator {
124127
.filter(key => !key.endsWith('.map'))
125128
.map(key => {
126129
const asset = this.compilation.assets[key];
130+
127131
return {
128132
size: asset.size(),
129-
label: key
133+
label: key,
130134
};
131135
});
132136
}
@@ -135,39 +139,33 @@ class AnyCalculator extends Calculator {
135139
/**
136140
* Calculate the bytes given a string value.
137141
*/
138-
export function calculateBytes(val: string, baseline?: string, factor?: ('pos' | 'neg')): number {
139-
if (/^\d+$/.test(val)) {
140-
return parseFloat(val);
142+
export function calculateBytes(
143+
input: string,
144+
baseline?: string,
145+
factor: 1 | -1 = 1,
146+
): number {
147+
const matches = input.match(/^\s*(\d+(?:\.\d+)?)\s*(%|(?:[mM]|[kK]|[gG])?[bB])?\s*$/);
148+
if (!matches) {
149+
return NaN;
141150
}
142151

143-
if (/^(\d+)%$/.test(val)) {
144-
return calculatePercentBytes(val, baseline, factor);
152+
const baselineBytes = baseline && calculateBytes(baseline) || 0;
153+
154+
let value = Number(matches[1]);
155+
switch (matches[2] && matches[2].toLowerCase()) {
156+
case '%':
157+
value = baselineBytes * value / 100 * factor;
158+
break;
159+
case 'kb':
160+
value *= 1024;
161+
break;
162+
case 'mb':
163+
value *= 1024 * 1024;
164+
break;
165+
case 'gb':
166+
value *= 1024 * 1024 * 1024;
167+
break;
145168
}
146169

147-
const multiplier = getMultiplier(val);
148-
149-
const numberVal = parseFloat(val.replace(/((k|m|M|)b?)$/, ''));
150-
const baselineVal = baseline ? parseFloat(baseline.replace(/((k|m|M|)b?)$/, '')) : 0;
151-
const baselineMultiplier = baseline ? getMultiplier(baseline) : 1;
152-
const factorMultiplier = factor ? (factor === 'pos' ? 1 : -1) : 1;
153-
154-
return numberVal * multiplier + baselineVal * baselineMultiplier * factorMultiplier;
155-
}
156-
157-
function getMultiplier(val: string): number {
158-
if (/^(\d+)b?$/.test(val)) {
159-
return 1;
160-
} else if (/^(\d+)kb$/.test(val)) {
161-
return 1000;
162-
} else if (/^(\d+)(m|M)b$/.test(val)) {
163-
return 1000 * 1000;
164-
} else {
165-
return 1;
166-
}
167-
}
168-
169-
function calculatePercentBytes(val: string, baseline?: string, factor?: ('pos' | 'neg')): number {
170-
const baselineBytes = calculateBytes(baseline as string);
171-
const percentage = parseFloat(val.replace(/%/g, ''));
172-
return baselineBytes + baselineBytes * percentage / 100 * (factor === 'pos' ? 1 : -1);
170+
return value + baselineBytes;
173171
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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 { calculateBytes } from './bundle-calculator';
9+
10+
describe('bundle-calculator', () => {
11+
it('converts an integer with no postfix', () => {
12+
expect(calculateBytes('0')).toBe(0);
13+
expect(calculateBytes('5')).toBe(5);
14+
expect(calculateBytes('190')).toBe(190);
15+
expect(calculateBytes('92')).toBe(92);
16+
});
17+
18+
it('converts a decimal with no postfix', () => {
19+
expect(calculateBytes('3.14')).toBe(3.14);
20+
expect(calculateBytes('0.25')).toBe(0.25);
21+
expect(calculateBytes('90.5')).toBe(90.5);
22+
expect(calculateBytes('25.0')).toBe(25);
23+
});
24+
25+
it('converts an integer with kb postfix', () => {
26+
expect(calculateBytes('0kb')).toBe(0);
27+
expect(calculateBytes('5kb')).toBe(5 * 1024);
28+
expect(calculateBytes('190KB')).toBe(190 * 1024);
29+
expect(calculateBytes('92Kb')).toBe(92 * 1024);
30+
expect(calculateBytes('25kB')).toBe(25 * 1024);
31+
});
32+
33+
it('converts a decimal with kb postfix', () => {
34+
expect(calculateBytes('3.14kb')).toBe(3.14 * 1024);
35+
expect(calculateBytes('0.25KB')).toBe(0.25 * 1024);
36+
expect(calculateBytes('90.5Kb')).toBe(90.5 * 1024);
37+
expect(calculateBytes('25.0kB')).toBe(25 * 1024);
38+
});
39+
40+
it('converts an integer with mb postfix', () => {
41+
expect(calculateBytes('0mb')).toBe(0);
42+
expect(calculateBytes('5mb')).toBe(5 * 1024 * 1024);
43+
expect(calculateBytes('190MB')).toBe(190 * 1024 * 1024);
44+
expect(calculateBytes('92Mb')).toBe(92 * 1024 * 1024);
45+
expect(calculateBytes('25mB')).toBe(25 * 1024 * 1024);
46+
});
47+
48+
it('converts a decimal with mb postfix', () => {
49+
expect(calculateBytes('3.14mb')).toBe(3.14 * 1024 * 1024);
50+
expect(calculateBytes('0.25MB')).toBe(0.25 * 1024 * 1024);
51+
expect(calculateBytes('90.5Mb')).toBe(90.5 * 1024 * 1024);
52+
expect(calculateBytes('25.0mB')).toBe(25 * 1024 * 1024);
53+
});
54+
55+
it('converts an integer with gb postfix', () => {
56+
expect(calculateBytes('0gb')).toBe(0);
57+
expect(calculateBytes('5gb')).toBe(5 * 1024 * 1024 * 1024);
58+
expect(calculateBytes('190GB')).toBe(190 * 1024 * 1024 * 1024);
59+
expect(calculateBytes('92Gb')).toBe(92 * 1024 * 1024 * 1024);
60+
expect(calculateBytes('25gB')).toBe(25 * 1024 * 1024 * 1024);
61+
});
62+
63+
it('converts a decimal with gb postfix', () => {
64+
expect(calculateBytes('3.14gb')).toBe(3.14 * 1024 * 1024 * 1024);
65+
expect(calculateBytes('0.25GB')).toBe(0.25 * 1024 * 1024 * 1024);
66+
expect(calculateBytes('90.5Gb')).toBe(90.5 * 1024 * 1024 * 1024);
67+
expect(calculateBytes('25.0gB')).toBe(25 * 1024 * 1024 * 1024);
68+
});
69+
70+
it ('converts a percentage with baseline', () => {
71+
expect(calculateBytes('20%', '1mb')).toBe(1024 * 1024 * 1.2);
72+
expect(calculateBytes('20%', '1mb', -1)).toBe(1024 * 1024 * 0.8);
73+
});
74+
75+
it ('supports whitespace', () => {
76+
expect(calculateBytes(' 5kb ')).toBe(5 * 1024);
77+
expect(calculateBytes('0.25 MB')).toBe(0.25 * 1024 * 1024);
78+
expect(calculateBytes(' 20 % ', ' 1 mb ')).toBe(1024 * 1024 * 1.2);
79+
});
80+
});

0 commit comments

Comments
 (0)