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.
This commit is contained in:
Charles Lyding 2021-02-10 12:30:53 -05:00 committed by Charles
parent b105ed63c7
commit 40dc44b64b
6 changed files with 105 additions and 43 deletions

View File

@ -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> = A extends ReadonlyArray<infer T> ? T : never;
function generateBundleInfoStats(
bundle: ProcessBundleFile,
chunk: ArrayElement<webpack.Stats.ToJsonOutput['chunks']> | undefined,
chunk: JsonChunkStats | undefined,
chunkType: ChunkType,
): BundleStats {
return generateBundleStats(

View File

@ -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<Threshold
*/
function calculateSizes(
budget: Budget,
stats: webpack.Stats.ToJsonOutput,
stats: JsonCompilationStats,
processResults: ProcessBundleResult[],
): Size[] {
if (budget.type === Type.AnyComponentStyle) {
@ -115,7 +119,14 @@ function calculateSizes(
}
type NonComponentStyleBudgetTypes = Exclude<Budget['type'], Type.AnyComponentStyle>;
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<NonComponentStyleBudgetTypes, CalculatorTypes> = {
all: AllCalculator,
allScript: AllScriptCalculator,
@ -139,14 +150,11 @@ function calculateSizes(
return calculator.calculate();
}
type ArrayElement<T> = T extends Array<infer U> ? U : never;
type Chunk = ArrayElement<Exclude<webpack.Stats.ToJsonOutput['chunks'], undefined>>;
type Asset = ArrayElement<Exclude<webpack.Stats.ToJsonOutput['assets'], undefined>>;
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`.

View File

@ -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',

View File

@ -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<webpack.Stats.ToJsonOutput['chunks'], undefined> {
): JsonChunkStats[] {
const {chunks = [], entrypoints: entryPoints = {}} = webpackStats;
// Find all Webpack chunk IDs not injected into the main bundle. We don't have

View File

@ -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<webpack.Stats.ToJsonOutput['chunks'], undefined>);
]);
});
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<webpack.Stats.ToJsonOutput['chunks'], undefined>);
]);
});
});
});

View File

@ -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<string, JsonEntrypointStats>;
}
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 {