@@ -10,40 +10,15 @@ import { WebpackLoggingCallback } from '@angular-devkit/build-webpack';
1010import { logging } from '@angular-devkit/core' ;
1111import assert from 'node:assert' ;
1212import * as path from 'node:path' ;
13- import { stripVTControlCharacters } from 'node:util' ;
1413import { Configuration , StatsCompilation } from 'webpack' ;
1514import { Schema as BrowserBuilderOptions } from '../../../builders/browser/schema' ;
1615import { normalizeOptimization } from '../../../utils' ;
1716import { BudgetCalculatorResult } from '../../../utils/bundle-calculator' ;
1817import { colors as ansiColors } from '../../../utils/color' ;
18+ import { BundleStats , generateBuildStatsTable } from '../../../utils/stats-table' ;
1919import { markAsyncChunksNonInitial } from './async-chunks' ;
2020import { WebpackStatsOptions , getStatsOptions , normalizeExtraEntryPoints } from './helpers' ;
2121
22- export function formatSize ( size : number ) : string {
23- if ( size <= 0 ) {
24- return '0 bytes' ;
25- }
26-
27- const abbreviations = [ 'bytes' , 'kB' , 'MB' , 'GB' ] ;
28- const index = Math . floor ( Math . log ( size ) / Math . log ( 1024 ) ) ;
29- const roundedSize = size / Math . pow ( 1024 , index ) ;
30- // bytes don't have a fraction
31- const fractionDigits = index === 0 ? 0 : 2 ;
32-
33- return `${ roundedSize . toFixed ( fractionDigits ) } ${ abbreviations [ index ] } ` ;
34- }
35-
36- export type BundleStatsData = [
37- files : string ,
38- names : string ,
39- rawSize : number | string ,
40- estimatedTransferSize : number | string ,
41- ] ;
42- export interface BundleStats {
43- initial : boolean ;
44- stats : BundleStatsData ;
45- }
46-
4722function getBuildDuration ( webpackStats : StatsCompilation ) : number {
4823 assert ( webpackStats . builtAt , 'buildAt cannot be undefined' ) ;
4924 assert ( webpackStats . time , 'time cannot be undefined' ) ;
@@ -76,273 +51,6 @@ function generateBundleStats(info: {
7651 } ;
7752}
7853
79- export function generateEsbuildBuildStatsTable (
80- [ browserStats , serverStats ] : [ browserStats : BundleStats [ ] , serverStats : BundleStats [ ] ] ,
81- colors : boolean ,
82- showTotalSize : boolean ,
83- showEstimatedTransferSize : boolean ,
84- budgetFailures ?: BudgetCalculatorResult [ ] ,
85- verbose ?: boolean ,
86- ) : string {
87- const bundleInfo = generateBuildStatsData (
88- browserStats ,
89- colors ,
90- showTotalSize ,
91- showEstimatedTransferSize ,
92- budgetFailures ,
93- verbose ,
94- ) ;
95-
96- if ( serverStats . length ) {
97- const m = ( x : string ) => ( colors ? ansiColors . magenta ( x ) : x ) ;
98- if ( browserStats . length ) {
99- bundleInfo . unshift ( [ m ( 'Browser bundles' ) ] ) ;
100- // Add seperators between browser and server logs
101- bundleInfo . push ( [ ] , [ ] ) ;
102- }
103-
104- bundleInfo . push (
105- [ m ( 'Server bundles' ) ] ,
106- ...generateBuildStatsData ( serverStats , colors , false , false , undefined , verbose ) ,
107- ) ;
108- }
109-
110- return generateTableText ( bundleInfo , colors ) ;
111- }
112-
113- export function generateBuildStatsTable (
114- data : BundleStats [ ] ,
115- colors : boolean ,
116- showTotalSize : boolean ,
117- showEstimatedTransferSize : boolean ,
118- budgetFailures ?: BudgetCalculatorResult [ ] ,
119- ) : string {
120- const bundleInfo = generateBuildStatsData (
121- data ,
122- colors ,
123- showTotalSize ,
124- showEstimatedTransferSize ,
125- budgetFailures ,
126- true ,
127- ) ;
128-
129- return generateTableText ( bundleInfo , colors ) ;
130- }
131-
132- function generateBuildStatsData (
133- data : BundleStats [ ] ,
134- colors : boolean ,
135- showTotalSize : boolean ,
136- showEstimatedTransferSize : boolean ,
137- budgetFailures ?: BudgetCalculatorResult [ ] ,
138- verbose ?: boolean ,
139- ) : ( string | number ) [ ] [ ] {
140- if ( data . length === 0 ) {
141- return [ ] ;
142- }
143-
144- const g = ( x : string ) => ( colors ? ansiColors . green ( x ) : x ) ;
145- const c = ( x : string ) => ( colors ? ansiColors . cyan ( x ) : x ) ;
146- const r = ( x : string ) => ( colors ? ansiColors . redBright ( x ) : x ) ;
147- const y = ( x : string ) => ( colors ? ansiColors . yellowBright ( x ) : x ) ;
148- const bold = ( x : string ) => ( colors ? ansiColors . bold ( x ) : x ) ;
149- const dim = ( x : string ) => ( colors ? ansiColors . dim ( x ) : x ) ;
150-
151- const getSizeColor = ( name : string , file ?: string , defaultColor = c ) => {
152- const severity = budgets . get ( name ) || ( file && budgets . get ( file ) ) ;
153- switch ( severity ) {
154- case 'warning' :
155- return y ;
156- case 'error' :
157- return r ;
158- default :
159- return defaultColor ;
160- }
161- } ;
162-
163- const changedEntryChunksStats : BundleStatsData [ ] = [ ] ;
164- const changedLazyChunksStats : BundleStatsData [ ] = [ ] ;
165-
166- let initialTotalRawSize = 0 ;
167- let changedLazyChunksCount = 0 ;
168- let initialTotalEstimatedTransferSize ;
169- const maxLazyChunksWithoutBudgetFailures = 15 ;
170-
171- const budgets = new Map < string , string > ( ) ;
172- if ( budgetFailures ) {
173- for ( const { label, severity } of budgetFailures ) {
174- // In some cases a file can have multiple budget failures.
175- // Favor error.
176- if ( label && ( ! budgets . has ( label ) || budgets . get ( label ) === 'warning' ) ) {
177- budgets . set ( label , severity ) ;
178- }
179- }
180- }
181-
182- // Sort descending by raw size
183- data . sort ( ( a , b ) => {
184- if ( a . stats [ 2 ] > b . stats [ 2 ] ) {
185- return - 1 ;
186- }
187-
188- if ( a . stats [ 2 ] < b . stats [ 2 ] ) {
189- return 1 ;
190- }
191-
192- return 0 ;
193- } ) ;
194-
195- for ( const { initial, stats } of data ) {
196- const [ files , names , rawSize , estimatedTransferSize ] = stats ;
197- if (
198- ! initial &&
199- ! verbose &&
200- changedLazyChunksStats . length >= maxLazyChunksWithoutBudgetFailures &&
201- ! budgets . has ( names ) &&
202- ! budgets . has ( files )
203- ) {
204- // Limit the number of lazy chunks displayed in the stats table when there is no budget failure and not in verbose mode.
205- changedLazyChunksCount ++ ;
206- continue ;
207- }
208-
209- const getRawSizeColor = getSizeColor ( names , files ) ;
210- let data : BundleStatsData ;
211- if ( showEstimatedTransferSize ) {
212- data = [
213- g ( files ) ,
214- dim ( names ) ,
215- getRawSizeColor ( typeof rawSize === 'number' ? formatSize ( rawSize ) : rawSize ) ,
216- c (
217- typeof estimatedTransferSize === 'number'
218- ? formatSize ( estimatedTransferSize )
219- : estimatedTransferSize ,
220- ) ,
221- ] ;
222- } else {
223- data = [
224- g ( files ) ,
225- dim ( names ) ,
226- getRawSizeColor ( typeof rawSize === 'number' ? formatSize ( rawSize ) : rawSize ) ,
227- '' ,
228- ] ;
229- }
230-
231- if ( initial ) {
232- changedEntryChunksStats . push ( data ) ;
233- if ( typeof rawSize === 'number' ) {
234- initialTotalRawSize += rawSize ;
235- }
236- if ( showEstimatedTransferSize && typeof estimatedTransferSize === 'number' ) {
237- if ( initialTotalEstimatedTransferSize === undefined ) {
238- initialTotalEstimatedTransferSize = 0 ;
239- }
240- initialTotalEstimatedTransferSize += estimatedTransferSize ;
241- }
242- } else {
243- changedLazyChunksStats . push ( data ) ;
244- changedLazyChunksCount ++ ;
245- }
246- }
247-
248- const bundleInfo : ( string | number ) [ ] [ ] = [ ] ;
249- const baseTitles = [ 'Names' , 'Raw size' ] ;
250-
251- if ( showEstimatedTransferSize ) {
252- baseTitles . push ( 'Estimated transfer size' ) ;
253- }
254-
255- // Entry chunks
256- if ( changedEntryChunksStats . length ) {
257- bundleInfo . push ( [ 'Initial chunk files' , ...baseTitles ] . map ( bold ) , ...changedEntryChunksStats ) ;
258-
259- if ( showTotalSize ) {
260- const initialSizeTotalColor = getSizeColor ( 'bundle initial' , undefined , ( x ) => x ) ;
261- const totalSizeElements = [
262- ' ' ,
263- 'Initial total' ,
264- initialSizeTotalColor ( formatSize ( initialTotalRawSize ) ) ,
265- ] ;
266- if ( showEstimatedTransferSize ) {
267- totalSizeElements . push (
268- typeof initialTotalEstimatedTransferSize === 'number'
269- ? formatSize ( initialTotalEstimatedTransferSize )
270- : '-' ,
271- ) ;
272- }
273- bundleInfo . push ( [ ] , totalSizeElements . map ( bold ) ) ;
274- }
275- }
276-
277- // Seperator
278- if ( changedEntryChunksStats . length && changedLazyChunksStats . length ) {
279- bundleInfo . push ( [ ] ) ;
280- }
281-
282- // Lazy chunks
283- if ( changedLazyChunksStats . length ) {
284- bundleInfo . push ( [ 'Lazy chunk files' , ...baseTitles ] . map ( bold ) , ...changedLazyChunksStats ) ;
285-
286- if ( changedLazyChunksCount > changedLazyChunksStats . length ) {
287- bundleInfo . push ( [
288- dim (
289- `...and ${ changedLazyChunksCount - changedLazyChunksStats . length } more lazy chunks files. ` +
290- 'Use "--verbose" to show all the files.' ,
291- ) ,
292- ] ) ;
293- }
294- }
295-
296- return bundleInfo ;
297- }
298-
299- function generateTableText ( bundleInfo : ( string | number ) [ ] [ ] , colors : boolean ) : string {
300- const skipText = ( value : string ) => value . includes ( '...and ' ) ;
301- const longest : number [ ] = [ ] ;
302- for ( const item of bundleInfo ) {
303- for ( let i = 0 ; i < item . length ; i ++ ) {
304- if ( item [ i ] === undefined ) {
305- continue ;
306- }
307-
308- const currentItem = item [ i ] . toString ( ) ;
309- if ( skipText ( currentItem ) ) {
310- continue ;
311- }
312-
313- const currentLongest = ( longest [ i ] ??= 0 ) ;
314- const currentItemLength = stripVTControlCharacters ( currentItem ) . length ;
315- if ( currentLongest < currentItemLength ) {
316- longest [ i ] = currentItemLength ;
317- }
318- }
319- }
320-
321- const seperator = colors ? ansiColors . dim ( ' | ' ) : ' | ' ;
322- const outputTable : string [ ] = [ ] ;
323- for ( const item of bundleInfo ) {
324- for ( let i = 0 ; i < longest . length ; i ++ ) {
325- if ( item [ i ] === undefined ) {
326- continue ;
327- }
328-
329- const currentItem = item [ i ] . toString ( ) ;
330- if ( skipText ( currentItem ) ) {
331- continue ;
332- }
333-
334- const currentItemLength = stripVTControlCharacters ( currentItem ) . length ;
335- const stringPad = ' ' . repeat ( longest [ i ] - currentItemLength ) ;
336- // Values in columns at index 2 and 3 (Raw and Estimated sizes) are always right aligned.
337- item [ i ] = i >= 2 ? stringPad + currentItem : currentItem + stringPad ;
338- }
339-
340- outputTable . push ( item . join ( seperator ) ) ;
341- }
342-
343- return outputTable . join ( '\n' ) ;
344- }
345-
34654// We use this cache because we can have multiple builders running in the same process,
34755// where each builder has different output path.
34856
0 commit comments