From 40dc44b64be3702dad4103b0c99212777f57e78a Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 10 Feb 2021 12:30:53 -0500 Subject: [PATCH] refactor(@angular-devkit/build-angular): remove usage of Webpack Stats.ToJsonOutput type The `Stats.ToJsonOutput` type is not present in the Webpack 5 typings. There was also a large amount of forced typing in the code to successfully compile. Minimal Webpack JSON stat types are now used that represent the fields used by the tooling. --- .../build_angular/src/browser/index.ts | 4 +- .../src/utils/bundle-calculator.ts | 32 ++++++---- .../src/utils/bundle-calculator_spec.ts | 58 +++++++++++++------ .../src/webpack/utils/async-chunks.ts | 6 +- .../src/webpack/utils/async-chunks_spec.ts | 18 ++++-- .../build_angular/src/webpack/utils/stats.ts | 30 ++++++++-- 6 files changed, 105 insertions(+), 43 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/browser/index.ts b/packages/angular_devkit/build_angular/src/browser/index.ts index b8365b506a..3e6730bd9a 100644 --- a/packages/angular_devkit/build_angular/src/browser/index.ts +++ b/packages/angular_devkit/build_angular/src/browser/index.ts @@ -66,6 +66,7 @@ import { normalizeExtraEntryPoints } from '../webpack/utils/helpers'; import { BundleStats, ChunkType, + JsonChunkStats, generateBundleStats, statsErrorsToString, statsHasErrors, @@ -781,10 +782,9 @@ function assertNever(input: never): never { throw new Error(`Unexpected call to assertNever() with input: ${JSON.stringify(input, null /* replacer */, 4 /* tabSize */)}`); } -type ArrayElement = A extends ReadonlyArray ? T : never; function generateBundleInfoStats( bundle: ProcessBundleFile, - chunk: ArrayElement | undefined, + chunk: JsonChunkStats | undefined, chunkType: ChunkType, ): BundleStats { return generateBundleStats( diff --git a/packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts b/packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts index 96c6429fc4..ba64dd7973 100644 --- a/packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts +++ b/packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts @@ -6,10 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ import { basename } from 'path'; -import * as webpack from 'webpack'; import { Budget, Type } from '../browser/schema'; import { ProcessBundleFile, ProcessBundleResult } from '../utils/process-bundle'; -import { formatSize } from '../webpack/utils/stats'; +import { + JsonAssetStats, + JsonChunkStats, + JsonCompilationStats, + formatSize, +} from '../webpack/utils/stats'; interface Size { size: number; @@ -105,7 +109,7 @@ export function* calculateThresholds(budget: Budget): IterableIterator; - type CalculatorTypes = { new(budget: Budget, chunks: Chunk[], assets: Asset[], processResults: ProcessBundleResult[]): Calculator }; + type CalculatorTypes = { + new ( + budget: Budget, + chunks: JsonChunkStats[], + assets: JsonAssetStats[], + processResults: ProcessBundleResult[], + ): Calculator; + }; const calculatorMap: Record = { all: AllCalculator, allScript: AllScriptCalculator, @@ -139,14 +150,11 @@ function calculateSizes( return calculator.calculate(); } -type ArrayElement = T extends Array ? U : never; -type Chunk = ArrayElement>; -type Asset = ArrayElement>; abstract class Calculator { constructor ( protected budget: Budget, - protected chunks: Chunk[], - protected assets: Asset[], + protected chunks: JsonChunkStats[], + protected assets: JsonAssetStats[], protected processResults: ProcessBundleResult[], ) {} @@ -154,7 +162,7 @@ abstract class Calculator { /** Calculates the size of the given chunk for the provided build type. */ protected calculateChunkSize( - chunk: Chunk, + chunk: JsonChunkStats, buildType: DifferentialBuildType, ): number { // Look for a process result containing different builds for this chunk. @@ -183,7 +191,7 @@ abstract class Calculator { } } - protected getAssetSize(asset: Asset): number { + protected getAssetSize(asset: JsonAssetStats): number { if (asset.name.endsWith('.js')) { const processResult = this.processResults .find((processResult) => processResult.original && basename(processResult.original.filename) === asset.name); @@ -348,7 +356,7 @@ function calculateBytes( export function* checkBudgets( budgets: Budget[], - webpackStats: webpack.Stats.ToJsonOutput, + webpackStats: JsonCompilationStats, processResults: ProcessBundleResult[], ): IterableIterator<{ severity: ThresholdSeverity, message: string }> { // Ignore AnyComponentStyle budgets as these are handled in `AnyComponentStyleBudgetChecker`. diff --git a/packages/angular_devkit/build_angular/src/utils/bundle-calculator_spec.ts b/packages/angular_devkit/build_angular/src/utils/bundle-calculator_spec.ts index 0c3dd9e113..9eccf2052e 100644 --- a/packages/angular_devkit/build_angular/src/utils/bundle-calculator_spec.ts +++ b/packages/angular_devkit/build_angular/src/utils/bundle-calculator_spec.ts @@ -5,7 +5,6 @@ * 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 * as webpack from 'webpack'; import { Budget, Type } from '../browser/schema'; import { ThresholdSeverity, checkBudgets } from './bundle-calculator'; import { ProcessBundleResult } from './process-bundle'; @@ -33,7 +32,7 @@ describe('bundle-calculator', () => { size: 0.5 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); @@ -61,7 +60,7 @@ describe('bundle-calculator', () => { size: 0.5 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); @@ -81,6 +80,7 @@ describe('bundle-calculator', () => { const stats = { chunks: [ { + id: 0, names: [ 'foo' ], files: [ 'foo.js', 'bar.js' ], }, @@ -95,7 +95,7 @@ describe('bundle-calculator', () => { size: 0.75 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); @@ -121,7 +121,7 @@ describe('bundle-calculator', () => { }, ], assets: [], - } as unknown as webpack.Stats.ToJsonOutput; + }; const processResults: ProcessBundleResult[] = [ { name: '0', @@ -164,7 +164,7 @@ describe('bundle-calculator', () => { }, ], assets: [], - } as unknown as webpack.Stats.ToJsonOutput; + }; const processResults: ProcessBundleResult[] = [ { name: '0', @@ -195,7 +195,9 @@ describe('bundle-calculator', () => { const stats = { chunks: [ { + id: 0, initial: true, + names: [ 'foo' ], files: [ 'foo.js', 'bar.js' ], }, ], @@ -209,7 +211,7 @@ describe('bundle-calculator', () => { size: 0.75 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); @@ -230,11 +232,12 @@ describe('bundle-calculator', () => { { id: 0, initial: true, + names: [ 'foo' ], files: [ 'foo.js', 'bar.js' ], }, ], assets: [], - } as unknown as webpack.Stats.ToJsonOutput; + }; const processResults: ProcessBundleResult[] = [ { name: '0', @@ -273,11 +276,12 @@ describe('bundle-calculator', () => { { id: 0, initial: true, + names: [ 'foo' ], files: [ 'foo.js', 'bar.js' ], }, ], assets: [], - } as unknown as webpack.Stats.ToJsonOutput; + }; const processResults: ProcessBundleResult[] = [ { name: '0', @@ -308,7 +312,9 @@ describe('bundle-calculator', () => { const stats = { chunks: [ { + id: 0, initial: true, + names: [ 'foo' ], files: [ 'foo.js', 'bar.js' ], }, ], @@ -326,7 +332,7 @@ describe('bundle-calculator', () => { size: 1.5 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); @@ -345,7 +351,9 @@ describe('bundle-calculator', () => { const stats = { chunks: [ { + id: 0, initial: true, + names: [ 'foo' ], files: [ 'foo.js', 'bar.css' ], }, ], @@ -359,7 +367,7 @@ describe('bundle-calculator', () => { size: 0.75 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); @@ -378,7 +386,9 @@ describe('bundle-calculator', () => { const stats = { chunks: [ { + id: 0, initial: true, + names: [ 'foo' ], files: [ 'foo.css', 'bar.js' ], }, ], @@ -392,7 +402,7 @@ describe('bundle-calculator', () => { size: 0.5 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); @@ -407,7 +417,9 @@ describe('bundle-calculator', () => { const stats = { chunks: [ { + id: 0, initial: true, + names: [ 'foo' ], files: [ 'foo.js', 'bar.js' ], }, ], @@ -421,7 +433,7 @@ describe('bundle-calculator', () => { size: 0.5 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); @@ -440,7 +452,9 @@ describe('bundle-calculator', () => { const stats = { chunks: [ { + id: 0, initial: true, + names: [ 'foo' ], files: [ 'foo.ext', 'bar.ext' ], }, ], @@ -454,7 +468,7 @@ describe('bundle-calculator', () => { size: 0.5 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); @@ -473,7 +487,9 @@ describe('bundle-calculator', () => { const stats = { chunks: [ { + id: 0, initial: true, + names: [ 'foo' ], files: [ 'foo.js' ], }, ], @@ -483,7 +499,7 @@ describe('bundle-calculator', () => { size: 1.25 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const processResults: ProcessBundleResult[] = [ { name: '0', @@ -514,7 +530,9 @@ describe('bundle-calculator', () => { const stats = { chunks: [ { + id: 0, initial: true, + names: [ 'foo' ], files: [ 'foo.js' ], }, ], @@ -524,7 +542,7 @@ describe('bundle-calculator', () => { size: 1.25 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const processResults: ProcessBundleResult[] = [ { name: '0', @@ -555,7 +573,9 @@ describe('bundle-calculator', () => { const stats = { chunks: [ { + id: 0, initial: true, + names: [ 'foo' ], files: [ 'foo.js' ], }, ], @@ -565,7 +585,7 @@ describe('bundle-calculator', () => { size: 1.25 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const processResults: ProcessBundleResult[] = [ { name: '0', @@ -596,7 +616,9 @@ describe('bundle-calculator', () => { const stats = { chunks: [ { + id: 0, initial: true, + names: [ 'foo' ], files: [ 'foo.js' ], }, ], @@ -606,7 +628,7 @@ describe('bundle-calculator', () => { size: 1.25 * KB, }, ], - } as unknown as webpack.Stats.ToJsonOutput; + }; const processResults: ProcessBundleResult[] = [ { name: '0', diff --git a/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks.ts b/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks.ts index d2a48dbfb1..4023b41d51 100644 --- a/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks.ts +++ b/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks.ts @@ -5,8 +5,8 @@ * 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 * as webpack from 'webpack'; import { NormalizedEntryPoint } from './helpers'; +import { JsonChunkStats, JsonCompilationStats } from './stats'; /** * Webpack stats may incorrectly mark extra entry points `initial` chunks, when @@ -15,9 +15,9 @@ import { NormalizedEntryPoint } from './helpers'; * whereever necessary. Does not modify {@param webpackStats}. */ export function markAsyncChunksNonInitial( - webpackStats: webpack.Stats.ToJsonOutput, + webpackStats: JsonCompilationStats, extraEntryPoints: NormalizedEntryPoint[], -): Exclude { +): JsonChunkStats[] { const {chunks = [], entrypoints: entryPoints = {}} = webpackStats; // Find all Webpack chunk IDs not injected into the main bundle. We don't have diff --git a/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks_spec.ts b/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks_spec.ts index da690efb05..f6b9629380 100644 --- a/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks_spec.ts +++ b/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks_spec.ts @@ -16,16 +16,19 @@ describe('async-chunks', () => { id: 0, names: ['first'], initial: true, + files: [], }, { id: 1, names: ['second'], initial: true, + files: [], }, { id: 'third', // IDs can be strings too. names: ['third'], initial: true, + files: [], }, ]; const entrypoints = { @@ -39,7 +42,7 @@ describe('async-chunks', () => { chunks: ['third'], }, }; - const webpackStats = { chunks, entrypoints } as unknown as webpack.Stats.ToJsonOutput; + const webpackStats = { chunks, entrypoints }; const extraEntryPoints = [ { @@ -66,18 +69,21 @@ describe('async-chunks', () => { id: 0, names: ['first'], initial: false, // No longer initial because it was marked async. + files: [], }, { id: 1, names: ['second'], initial: true, + files: [], }, { id: 'third', names: ['third'], initial: false, // No longer initial because it was marked async. + files: [], }, - ] as Exclude); + ]); }); it('ignores runtime dependency of async chunks', () => { @@ -86,11 +92,13 @@ describe('async-chunks', () => { id: 0, names: ['asyncStuff'], initial: true, + files: [], }, { id: 1, names: ['runtime'], initial: true, + files: [], }, ]; const entrypoints = { @@ -98,7 +106,7 @@ describe('async-chunks', () => { chunks: [0, 1], // Includes runtime as a dependency. }, }; - const webpackStats = { chunks, entrypoints } as unknown as webpack.Stats.ToJsonOutput; + const webpackStats = { chunks, entrypoints }; const extraEntryPoints = [ { @@ -115,13 +123,15 @@ describe('async-chunks', () => { id: 0, names: ['asyncStuff'], initial: false, // No longer initial because it was marked async. + files: [], }, { id: 1, names: ['runtime'], initial: true, // Still initial, even though its a dependency. + files: [], }, - ] as Exclude); + ]); }); }); }); diff --git a/packages/angular_devkit/build_angular/src/webpack/utils/stats.ts b/packages/angular_devkit/build_angular/src/webpack/utils/stats.ts index 558310f23e..27e1fa91f6 100644 --- a/packages/angular_devkit/build_angular/src/webpack/utils/stats.ts +++ b/packages/angular_devkit/build_angular/src/webpack/utils/stats.ts @@ -15,6 +15,28 @@ import { colors as ansiColors, removeColor } from '../../utils/color'; import { Configuration, Stats } from 'webpack'; import { isWebpackFiveOrHigher } from '../../utils/webpack-version'; +export interface JsonAssetStats { + name: string; + size: number; +} + +export interface JsonChunkStats { + id: number | string; + initial?: boolean; + files: string[]; + names: string[]; +} + +export interface JsonEntrypointStats { + chunks: (number | string)[]; +} + +export interface JsonCompilationStats { + assets?: JsonAssetStats[]; + chunks?: JsonChunkStats[]; + entrypoints?: Record; +} + export function formatSize(size: number): string { if (size <= 0) { return '0 bytes'; @@ -43,8 +65,8 @@ export function generateBundleStats( size?: number; files: string[]; names?: string[]; - entry: boolean; - initial: boolean; + entry?: boolean; + initial?: boolean; rendered?: boolean; chunkType?: ChunkType, }, @@ -191,7 +213,7 @@ function statsToString(json: any, statsConfig: any, bundleState?: BundleStats[]) const statsTable = generateBuildStatsTable(changedChunksStats, colors, unchangedChunkNumber === 0); - // In some cases we do things outside of webpack context + // In some cases we do things outside of webpack context // Such us index generation, service worker augmentation etc... // This will correct the time and include these. const time = (Date.now() - json.builtAt) + json.time; @@ -346,7 +368,7 @@ export function createWebpackLoggingCallback( export function webpackStatsLogger( logger: logging.LoggerApi, - json: Stats.ToJsonOutput, + json: JsonCompilationStats, config: Configuration, bundleStats?: BundleStats[], ): void {