@@ -34,6 +34,7 @@ export interface PageInfo {
34
34
isAmp ?: boolean
35
35
isHybridAmp ?: boolean
36
36
size : number
37
+ totalSize : number
37
38
static : boolean
38
39
isSsg : boolean
39
40
ssgPageRoutes : string [ ] | null
@@ -46,12 +47,14 @@ export async function printTreeView(
46
47
serverless : boolean ,
47
48
{
48
49
distPath,
50
+ buildId,
49
51
pagesDir,
50
52
pageExtensions,
51
53
buildManifest,
52
54
isModern,
53
55
} : {
54
56
distPath : string
57
+ buildId : string
55
58
pagesDir : string
56
59
pageExtensions : string [ ]
57
60
buildManifest : BuildManifestShape
@@ -68,8 +71,12 @@ export async function printTreeView(
68
71
return chalk . red . bold ( size )
69
72
}
70
73
71
- const messages : [ string , string ] [ ] = [
72
- [ 'Page' , 'Size' ] . map ( entry => chalk . underline ( entry ) ) as [ string , string ] ,
74
+ const messages : [ string , string , string ] [ ] = [
75
+ [ 'Page' , 'Size' , 'First Load' ] . map ( entry => chalk . underline ( entry ) ) as [
76
+ string ,
77
+ string ,
78
+ string
79
+ ] ,
73
80
]
74
81
75
82
const hasCustomApp = await findPageFile ( pagesDir , '/_app' , pageExtensions )
@@ -113,7 +120,14 @@ export async function printTreeView(
113
120
? pageInfo . isAmp
114
121
? chalk . cyan ( 'AMP' )
115
122
: pageInfo . size >= 0
116
- ? getPrettySize ( pageInfo . size )
123
+ ? prettyBytes ( pageInfo . size )
124
+ : ''
125
+ : '' ,
126
+ pageInfo
127
+ ? pageInfo . isAmp
128
+ ? chalk . cyan ( 'AMP' )
129
+ : pageInfo . size >= 0
130
+ ? getPrettySize ( pageInfo . totalSize )
117
131
: ''
118
132
: '' ,
119
133
] )
@@ -131,34 +145,45 @@ export async function printTreeView(
131
145
132
146
routes . forEach ( ( slug , index , { length } ) => {
133
147
const innerSymbol = index === length - 1 ? '└' : '├'
134
- messages . push ( [ `${ contSymbol } ${ innerSymbol } ${ slug } ` , '' ] )
148
+ messages . push ( [ `${ contSymbol } ${ innerSymbol } ${ slug } ` , '' , '' ] )
135
149
} )
136
150
}
137
151
} )
138
152
139
153
const sharedData = await getSharedSizes (
140
154
distPath ,
141
155
buildManifest ,
156
+ buildId ,
142
157
isModern ,
143
158
pageInfos
144
159
)
145
160
146
- messages . push ( [ '+ shared by all' , getPrettySize ( sharedData . total ) ] )
161
+ messages . push ( [ '+ shared by all' , getPrettySize ( sharedData . total ) , '' ] )
147
162
Object . keys ( sharedData . files )
163
+ . map ( e => e . replace ( buildId , '<buildId>' ) )
148
164
. sort ( )
149
165
. forEach ( ( fileName , index , { length } ) => {
150
166
const innerSymbol = index === length - 1 ? '└' : '├'
167
+
168
+ const originalName = fileName . replace ( '<buildId>' , buildId )
169
+ const cleanName = fileName
170
+ // Trim off `static/`
171
+ . replace ( / ^ s t a t i c \/ / , '' )
172
+ // Re-add `static/` for root files
173
+ . replace ( / ^ < b u i l d I d > / , 'static' )
174
+ // Remove file hash
175
+ . replace ( / [ . - ] [ 0 - 9 a - z ] { 20 } (? = \. ) / , '' )
176
+
151
177
messages . push ( [
152
- ` ${ innerSymbol } ${ fileName
153
- . replace ( / ^ s t a t i c \/ / , '' )
154
- . replace ( / [ . - ] [ 0 - 9 a - z ] { 20 } (? = \. ) / , '' ) } `,
155
- getPrettySize ( sharedData . files [ fileName ] ) ,
178
+ ` ${ innerSymbol } ${ cleanName } ` ,
179
+ prettyBytes ( sharedData . files [ originalName ] ) ,
180
+ '' ,
156
181
] )
157
182
} )
158
183
159
184
console . log (
160
185
textTable ( messages , {
161
- align : [ 'l' , 'l' ] ,
186
+ align : [ 'l' , 'l' , 'r' ] ,
162
187
stringLength : str => stripAnsi ( str ) . length ,
163
188
} )
164
189
)
@@ -253,6 +278,7 @@ export function printCustomRoutes({
253
278
type BuildManifestShape = { pages : { [ k : string ] : string [ ] } }
254
279
type ComputeManifestShape = {
255
280
commonFiles : string [ ]
281
+ uniqueFiles : string [ ]
256
282
sizeCommonFile : { [ file : string ] : number }
257
283
sizeCommonFiles : number
258
284
}
@@ -266,6 +292,7 @@ let lastComputePageInfo: boolean | undefined
266
292
async function computeFromManifest (
267
293
manifest : BuildManifestShape ,
268
294
distPath : string ,
295
+ buildId : string ,
269
296
isModern : boolean ,
270
297
pageInfos ?: Map < string , PageInfo >
271
298
) : Promise < ComputeManifestShape > {
@@ -304,16 +331,30 @@ async function computeFromManifest(
304
331
return
305
332
}
306
333
307
- if ( files . has ( file ) ) {
334
+ if ( key === '/_app' ) {
335
+ files . set ( file , Infinity )
336
+ } else if ( files . has ( file ) ) {
308
337
files . set ( file , files . get ( file ) ! + 1 )
309
338
} else {
310
339
files . set ( file , 1 )
311
340
}
312
341
} )
313
342
} )
314
343
344
+ // Add well-known shared file
345
+ files . set (
346
+ path . join (
347
+ `static/${ buildId } /pages/` ,
348
+ `/_app${ isModern ? '.module' : '' } .js`
349
+ ) ,
350
+ Infinity
351
+ )
352
+
315
353
const commonFiles = [ ...files . entries ( ) ]
316
- . filter ( ( [ , len ] ) => len === expected )
354
+ . filter ( ( [ , len ] ) => len === expected || len === Infinity )
355
+ . map ( ( [ f ] ) => f )
356
+ const uniqueFiles = [ ...files . entries ( ) ]
357
+ . filter ( ( [ , len ] ) => len === 1 )
317
358
. map ( ( [ f ] ) => f )
318
359
319
360
let stats : [ string , number ] [ ]
@@ -330,6 +371,7 @@ async function computeFromManifest(
330
371
331
372
lastCompute = {
332
373
commonFiles,
374
+ uniqueFiles,
333
375
sizeCommonFile : stats . reduce (
334
376
( obj , n ) => Object . assign ( obj , { [ n [ 0 ] ] : n [ 1 ] } ) ,
335
377
{ }
@@ -349,15 +391,27 @@ function difference<T>(main: T[], sub: T[]): T[] {
349
391
return [ ...a ] . filter ( x => ! b . has ( x ) )
350
392
}
351
393
394
+ function intersect < T > ( main : T [ ] , sub : T [ ] ) : T [ ] {
395
+ const a = new Set ( main )
396
+ const b = new Set ( sub )
397
+ return [ ...new Set ( [ ...a ] . filter ( x => b . has ( x ) ) ) ]
398
+ }
399
+
400
+ function sum ( a : number [ ] ) : number {
401
+ return a . reduce ( ( size , stat ) => size + stat , 0 )
402
+ }
403
+
352
404
export async function getSharedSizes (
353
405
distPath : string ,
354
406
buildManifest : BuildManifestShape ,
407
+ buildId : string ,
355
408
isModern : boolean ,
356
409
pageInfos : Map < string , PageInfo >
357
410
) : Promise < { total : number ; files : { [ page : string ] : number } } > {
358
411
const data = await computeFromManifest (
359
412
buildManifest ,
360
413
distPath ,
414
+ buildId ,
361
415
isModern ,
362
416
pageInfos
363
417
)
@@ -370,27 +424,54 @@ export async function getPageSizeInKb(
370
424
buildId : string ,
371
425
buildManifest : BuildManifestShape ,
372
426
isModern : boolean
373
- ) : Promise < number > {
374
- const data = await computeFromManifest ( buildManifest , distPath , isModern )
375
- const deps = difference ( buildManifest . pages [ page ] || [ ] , data . commonFiles )
376
- . filter (
377
- entry =>
378
- entry . endsWith ( '.js' ) && entry . endsWith ( '.module.js' ) === isModern
379
- )
380
- . map ( dep => `${ distPath } /${ dep } ` )
427
+ ) : Promise < [ number , number ] > {
428
+ const data = await computeFromManifest (
429
+ buildManifest ,
430
+ distPath ,
431
+ buildId ,
432
+ isModern
433
+ )
434
+
435
+ const fnFilterModern = ( entry : string ) =>
436
+ entry . endsWith ( '.js' ) && entry . endsWith ( '.module.js' ) === isModern
437
+
438
+ const pageFiles = ( buildManifest . pages [ page ] || [ ] ) . filter ( fnFilterModern )
439
+ const appFiles = ( buildManifest . pages [ '/_app' ] || [ ] ) . filter ( fnFilterModern )
440
+
441
+ const fnMapRealPath = ( dep : string ) => `${ distPath } /${ dep } `
442
+
443
+ const allFilesReal = [ ...new Set ( [ ...pageFiles , ...appFiles ] ) ] . map (
444
+ fnMapRealPath
445
+ )
446
+ const selfFilesReal = difference (
447
+ intersect ( pageFiles , data . uniqueFiles ) ,
448
+ data . commonFiles
449
+ ) . map ( fnMapRealPath )
381
450
382
451
const clientBundle = path . join (
383
452
distPath ,
384
453
`static/${ buildId } /pages/` ,
385
454
`${ page } ${ isModern ? '.module' : '' } .js`
386
455
)
387
- deps . push ( clientBundle )
456
+ const appBundle = path . join (
457
+ distPath ,
458
+ `static/${ buildId } /pages/` ,
459
+ `/_app${ isModern ? '.module' : '' } .js`
460
+ )
461
+ selfFilesReal . push ( clientBundle )
462
+ allFilesReal . push ( clientBundle )
463
+ if ( clientBundle !== appBundle ) {
464
+ allFilesReal . push ( appBundle )
465
+ }
388
466
389
467
try {
390
- let depStats = await Promise . all ( deps . map ( fsStatGzip ) )
391
- return depStats . reduce ( ( size , stat ) => size + stat , 0 )
468
+ // Doesn't use `Promise.all`, as we'd double compute duplicate files. This
469
+ // function is memoized, so the second one will instantly resolve.
470
+ const allFilesSize = sum ( await Promise . all ( allFilesReal . map ( fsStatGzip ) ) )
471
+ const selfFilesSize = sum ( await Promise . all ( selfFilesReal . map ( fsStatGzip ) ) )
472
+ return [ selfFilesSize , allFilesSize ]
392
473
} catch ( _ ) { }
393
- return - 1
474
+ return [ - 1 , - 1 ]
394
475
}
395
476
396
477
export async function isPageStatic (
0 commit comments