Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): improve bundle size value parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
clydin committed Aug 28, 2018
1 parent 02bfde5 commit c9bdffa
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
// tslint:disable
// TODO: cleanup this file, it's copied as is from Angular CLI.

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { Size, calculateBytes, calculateSizes } from '../utilities/bundle-calculator';
import { Compiler, compilation } from 'webpack';
import { Budget } from '../../browser/schema';
import { Size, calculateBytes, calculateSizes } from '../utilities/bundle-calculator';
import { formatSize } from '../utilities/stats';

interface Thresholds {
Expand All @@ -31,19 +28,20 @@ export interface BundleBudgetPluginOptions {
export class BundleBudgetPlugin {
constructor(private options: BundleBudgetPluginOptions) { }

apply(compiler: any): void {
apply(compiler: Compiler): void {
const { budgets } = this.options;
compiler.hooks.afterEmit.tap('BundleBudgetPlugin', (compilation: any) => {
compiler.hooks.afterEmit.tap('BundleBudgetPlugin', (compilation: compilation.Compilation) => {
if (!budgets || budgets.length === 0) {
return;
}

budgets.map(budget => {
const thresholds = this.calculate(budget);

return {
budget,
thresholds,
sizes: calculateSizes(budget, compilation)
sizes: calculateSizes(budget, compilation),
};
})
.forEach(budgetCheck => {
Expand All @@ -62,7 +60,7 @@ export class BundleBudgetPlugin {
});
}

private checkMinimum(threshold: number | undefined, size: Size, messages: any) {
private checkMinimum(threshold: number | undefined, size: Size, messages: string[]) {
if (threshold) {
if (threshold > size.size) {
const sizeDifference = formatSize(threshold - size.size);
Expand All @@ -72,7 +70,7 @@ export class BundleBudgetPlugin {
}
}

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

private calculate(budget: Budget): Thresholds {
let thresholds: Thresholds = {};
const thresholds: Thresholds = {};
if (budget.maximumWarning) {
thresholds.maximumWarning = calculateBytes(budget.maximumWarning, budget.baseline, 'pos');
thresholds.maximumWarning = calculateBytes(budget.maximumWarning, budget.baseline, 1);
}

if (budget.maximumError) {
thresholds.maximumError = calculateBytes(budget.maximumError, budget.baseline, 'pos');
thresholds.maximumError = calculateBytes(budget.maximumError, budget.baseline, 1);
}

if (budget.minimumWarning) {
thresholds.minimumWarning = calculateBytes(budget.minimumWarning, budget.baseline, 'neg');
thresholds.minimumWarning = calculateBytes(budget.minimumWarning, budget.baseline, -1);
}

if (budget.minimumError) {
thresholds.minimumError = calculateBytes(budget.minimumError, budget.baseline, 'neg');
thresholds.minimumError = calculateBytes(budget.minimumError, budget.baseline, -1);
}

if (budget.warning) {
thresholds.warningLow = calculateBytes(budget.warning, budget.baseline, 'neg');
thresholds.warningLow = calculateBytes(budget.warning, budget.baseline, -1);
}

if (budget.warning) {
thresholds.warningHigh = calculateBytes(budget.warning, budget.baseline, 'pos');
thresholds.warningHigh = calculateBytes(budget.warning, budget.baseline, 1);
}

if (budget.error) {
thresholds.errorLow = calculateBytes(budget.error, budget.baseline, 'neg');
thresholds.errorLow = calculateBytes(budget.error, budget.baseline, -1);
}

if (budget.error) {
thresholds.errorHigh = calculateBytes(budget.error, budget.baseline, 'pos');
thresholds.errorHigh = calculateBytes(budget.error, budget.baseline, 1);
}

return thresholds;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// tslint:disable
// TODO: cleanup this file, it's copied as is from Angular CLI.

/**
* @license
* Copyright Google Inc. All Rights Reserved.
Expand All @@ -11,8 +8,8 @@
import { Budget } from '../../browser/schema';

export interface Compilation {
assets: any;
chunks: any[];
assets: { [name: string]: { size: () => number } };
chunks: { name: string, files: string[], isOnlyInitial: () => boolean }[];
warnings: string[];
errors: string[];
}
Expand All @@ -33,6 +30,7 @@ export function calculateSizes(budget: Budget, compilation: Compilation): Size[]
};
const ctor = calculatorMap[budget.type];
const calculator = new ctor(budget, compilation);

return calculator.calculate();
}

Expand All @@ -52,6 +50,7 @@ class BundleCalculator extends Calculator {
.reduce((files, chunk) => [...files, ...chunk.files], [])
.map((file: string) => this.compilation.assets[file].size())
.reduce((total: number, size: number) => total + size, 0);

return [{size, label: this.budget.name}];
}
}
Expand All @@ -66,6 +65,7 @@ class InitialCalculator extends Calculator {
.reduce((files, chunk) => [...files, ...chunk.files], [])
.map((file: string) => this.compilation.assets[file].size())
.reduce((total: number, size: number) => total + size, 0);

return [{size, label: 'initial'}];
}
}
Expand All @@ -80,6 +80,7 @@ class AllScriptCalculator extends Calculator {
.map(key => this.compilation.assets[key])
.map(asset => asset.size())
.reduce((total: number, size: number) => total + size, 0);

return [{size, label: 'total scripts'}];
}
}
Expand All @@ -92,6 +93,7 @@ class AllCalculator extends Calculator {
const size: number = Object.keys(this.compilation.assets)
.map(key => this.compilation.assets[key].size())
.reduce((total: number, size: number) => total + size, 0);

return [{size, label: 'total'}];
}
}
Expand All @@ -105,9 +107,10 @@ class AnyScriptCalculator extends Calculator {
.filter(key => /\.js$/.test(key))
.map(key => {
const asset = this.compilation.assets[key];

return {
size: asset.size(),
label: key
label: key,
};
});
}
Expand All @@ -121,9 +124,10 @@ class AnyCalculator extends Calculator {
return Object.keys(this.compilation.assets)
.map(key => {
const asset = this.compilation.assets[key];

return {
size: asset.size(),
label: key
label: key,
};
});
}
Expand All @@ -132,39 +136,33 @@ class AnyCalculator extends Calculator {
/**
* Calculate the bytes given a string value.
*/
export function calculateBytes(val: string, baseline?: string, factor?: ('pos' | 'neg')): number {
if (/^\d+$/.test(val)) {
return parseFloat(val);
export function calculateBytes(
input: string,
baseline?: string,
factor: 1 | -1 = 1,
): number {
const matches = input.match(/^\s*(\d+(?:\.\d+)?)\s*(%|(?:[mM]|[kK]|[gG])?[bB])?\s*$/);
if (!matches) {
return NaN;
}

if (/^(\d+)%$/.test(val)) {
return calculatePercentBytes(val, baseline, factor);
const baselineBytes = baseline && calculateBytes(baseline) || 0;

let value = Number(matches[1]);
switch (matches[2] && matches[2].toLowerCase()) {
case '%':
value = baselineBytes * value / 100 * factor;
break;
case 'kb':
value *= 1024;
break;
case 'mb':
value *= 1024 * 1024;
break;
case 'gb':
value *= 1024 * 1024 * 1024;
break;
}

const multiplier = getMultiplier(val);

const numberVal = parseFloat(val.replace(/((k|m|M|)b?)$/, ''));
const baselineVal = baseline ? parseFloat(baseline.replace(/((k|m|M|)b?)$/, '')) : 0;
const baselineMultiplier = baseline ? getMultiplier(baseline) : 1;
const factorMultiplier = factor ? (factor === 'pos' ? 1 : -1) : 1;

return numberVal * multiplier + baselineVal * baselineMultiplier * factorMultiplier;
}

function getMultiplier(val: string): number {
if (/^(\d+)b?$/.test(val)) {
return 1;
} else if (/^(\d+)kb$/.test(val)) {
return 1000;
} else if (/^(\d+)(m|M)b$/.test(val)) {
return 1000 * 1000;
} else {
return 1;
}
}

function calculatePercentBytes(val: string, baseline?: string, factor?: ('pos' | 'neg')): number {
const baselineBytes = calculateBytes(baseline as string);
const percentage = parseFloat(val.replace(/%/g, ''));
return baselineBytes + baselineBytes * percentage / 100 * (factor === 'pos' ? 1 : -1);
return value + baselineBytes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { calculateBytes } from './bundle-calculator';

describe('bundle-calculator', () => {
it('converts an integer with no postfix', () => {
expect(calculateBytes('0')).toBe(0);
expect(calculateBytes('5')).toBe(5);
expect(calculateBytes('190')).toBe(190);
expect(calculateBytes('92')).toBe(92);
});

it('converts a decimal with no postfix', () => {
expect(calculateBytes('3.14')).toBe(3.14);
expect(calculateBytes('0.25')).toBe(0.25);
expect(calculateBytes('90.5')).toBe(90.5);
expect(calculateBytes('25.0')).toBe(25);
});

it('converts an integer with kb postfix', () => {
expect(calculateBytes('0kb')).toBe(0);
expect(calculateBytes('5kb')).toBe(5 * 1024);
expect(calculateBytes('190KB')).toBe(190 * 1024);
expect(calculateBytes('92Kb')).toBe(92 * 1024);
expect(calculateBytes('25kB')).toBe(25 * 1024);
});

it('converts a decimal with kb postfix', () => {
expect(calculateBytes('3.14kb')).toBe(3.14 * 1024);
expect(calculateBytes('0.25KB')).toBe(0.25 * 1024);
expect(calculateBytes('90.5Kb')).toBe(90.5 * 1024);
expect(calculateBytes('25.0kB')).toBe(25 * 1024);
});

it('converts an integer with mb postfix', () => {
expect(calculateBytes('0mb')).toBe(0);
expect(calculateBytes('5mb')).toBe(5 * 1024 * 1024);
expect(calculateBytes('190MB')).toBe(190 * 1024 * 1024);
expect(calculateBytes('92Mb')).toBe(92 * 1024 * 1024);
expect(calculateBytes('25mB')).toBe(25 * 1024 * 1024);
});

it('converts a decimal with mb postfix', () => {
expect(calculateBytes('3.14mb')).toBe(3.14 * 1024 * 1024);
expect(calculateBytes('0.25MB')).toBe(0.25 * 1024 * 1024);
expect(calculateBytes('90.5Mb')).toBe(90.5 * 1024 * 1024);
expect(calculateBytes('25.0mB')).toBe(25 * 1024 * 1024);
});

it('converts an integer with gb postfix', () => {
expect(calculateBytes('0gb')).toBe(0);
expect(calculateBytes('5gb')).toBe(5 * 1024 * 1024 * 1024);
expect(calculateBytes('190GB')).toBe(190 * 1024 * 1024 * 1024);
expect(calculateBytes('92Gb')).toBe(92 * 1024 * 1024 * 1024);
expect(calculateBytes('25gB')).toBe(25 * 1024 * 1024 * 1024);
});

it('converts a decimal with gb postfix', () => {
expect(calculateBytes('3.14gb')).toBe(3.14 * 1024 * 1024 * 1024);
expect(calculateBytes('0.25GB')).toBe(0.25 * 1024 * 1024 * 1024);
expect(calculateBytes('90.5Gb')).toBe(90.5 * 1024 * 1024 * 1024);
expect(calculateBytes('25.0gB')).toBe(25 * 1024 * 1024 * 1024);
});

it ('converts a percentage with baseline', () => {
expect(calculateBytes('20%', '1mb')).toBe(1024 * 1024 * 1.2);
expect(calculateBytes('20%', '1mb', -1)).toBe(1024 * 1024 * 0.8);
});

it ('supports whitespace', () => {
expect(calculateBytes(' 5kb ')).toBe(5 * 1024);
expect(calculateBytes('0.25 MB')).toBe(0.25 * 1024 * 1024);
expect(calculateBytes(' 20 % ', ' 1 mb ')).toBe(1024 * 1024 * 1.2);
});
});

0 comments on commit c9bdffa

Please sign in to comment.