@@ -10,40 +10,15 @@ import { WebpackLoggingCallback } from '@angular-devkit/build-webpack';
10
10
import { logging } from '@angular-devkit/core' ;
11
11
import assert from 'node:assert' ;
12
12
import * as path from 'node:path' ;
13
- import { stripVTControlCharacters } from 'node:util' ;
14
13
import { Configuration , StatsCompilation } from 'webpack' ;
15
14
import { Schema as BrowserBuilderOptions } from '../../../builders/browser/schema' ;
16
15
import { normalizeOptimization } from '../../../utils' ;
17
16
import { BudgetCalculatorResult } from '../../../utils/bundle-calculator' ;
18
17
import { colors as ansiColors } from '../../../utils/color' ;
18
+ import { BundleStats , generateBuildStatsTable } from '../../../utils/stats-table' ;
19
19
import { markAsyncChunksNonInitial } from './async-chunks' ;
20
20
import { WebpackStatsOptions , getStatsOptions , normalizeExtraEntryPoints } from './helpers' ;
21
21
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
-
47
22
function getBuildDuration ( webpackStats : StatsCompilation ) : number {
48
23
assert ( webpackStats . builtAt , 'buildAt cannot be undefined' ) ;
49
24
assert ( webpackStats . time , 'time cannot be undefined' ) ;
@@ -76,273 +51,6 @@ function generateBundleStats(info: {
76
51
} ;
77
52
}
78
53
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
-
346
54
// We use this cache because we can have multiple builders running in the same process,
347
55
// where each builder has different output path.
348
56
0 commit comments