diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts index 6400ce9491b9..4ee676886846 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts @@ -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. @@ -8,9 +5,9 @@ * 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 { @@ -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 => { @@ -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); @@ -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); @@ -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; diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts index 810899e0d868..745fb6ac97dd 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts @@ -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. @@ -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[]; } @@ -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(); } @@ -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}]; } } @@ -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'}]; } } @@ -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'}]; } } @@ -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'}]; } } @@ -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, }; }); } @@ -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, }; }); } @@ -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; } diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator_spec.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator_spec.ts new file mode 100644 index 000000000000..c610706ebe0c --- /dev/null +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator_spec.ts @@ -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); + }); +});