Skip to content

Commit 0fd15ad

Browse files
authored
Separate Self Size and First Load Size (vercel#10014)
* Separate Self Size and First Load Size * Tweak tests
1 parent a86640b commit 0fd15ad

File tree

3 files changed

+121
-38
lines changed

3 files changed

+121
-38
lines changed

packages/next/build/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ export default async function build(dir: string, conf = null): Promise<void> {
421421
await Promise.all(
422422
pageKeys.map(async page => {
423423
const actualPage = page === '/' ? '/index' : page
424-
const size = await getPageSizeInKb(
424+
const [selfSize, allSize] = await getPageSizeInKb(
425425
actualPage,
426426
distDir,
427427
buildId,
@@ -508,7 +508,8 @@ export default async function build(dir: string, conf = null): Promise<void> {
508508
}
509509

510510
pageInfos.set(page, {
511-
size,
511+
size: selfSize,
512+
totalSize: allSize,
512513
serverBundle,
513514
static: isStatic,
514515
isSsg,
@@ -754,6 +755,7 @@ export default async function build(dir: string, conf = null): Promise<void> {
754755
isLikeServerless,
755756
{
756757
distPath: distDir,
758+
buildId: buildId,
757759
pagesDir,
758760
pageExtensions: config.pageExtensions,
759761
buildManifest,

packages/next/build/utils.ts

Lines changed: 105 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface PageInfo {
3434
isAmp?: boolean
3535
isHybridAmp?: boolean
3636
size: number
37+
totalSize: number
3738
static: boolean
3839
isSsg: boolean
3940
ssgPageRoutes: string[] | null
@@ -46,12 +47,14 @@ export async function printTreeView(
4647
serverless: boolean,
4748
{
4849
distPath,
50+
buildId,
4951
pagesDir,
5052
pageExtensions,
5153
buildManifest,
5254
isModern,
5355
}: {
5456
distPath: string
57+
buildId: string
5558
pagesDir: string
5659
pageExtensions: string[]
5760
buildManifest: BuildManifestShape
@@ -68,8 +71,12 @@ export async function printTreeView(
6871
return chalk.red.bold(size)
6972
}
7073

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+
],
7380
]
7481

7582
const hasCustomApp = await findPageFile(pagesDir, '/_app', pageExtensions)
@@ -113,7 +120,14 @@ export async function printTreeView(
113120
? pageInfo.isAmp
114121
? chalk.cyan('AMP')
115122
: 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)
117131
: ''
118132
: '',
119133
])
@@ -131,34 +145,45 @@ export async function printTreeView(
131145

132146
routes.forEach((slug, index, { length }) => {
133147
const innerSymbol = index === length - 1 ? '└' : '├'
134-
messages.push([`${contSymbol} ${innerSymbol} ${slug}`, ''])
148+
messages.push([`${contSymbol} ${innerSymbol} ${slug}`, '', ''])
135149
})
136150
}
137151
})
138152

139153
const sharedData = await getSharedSizes(
140154
distPath,
141155
buildManifest,
156+
buildId,
142157
isModern,
143158
pageInfos
144159
)
145160

146-
messages.push(['+ shared by all', getPrettySize(sharedData.total)])
161+
messages.push(['+ shared by all', getPrettySize(sharedData.total), ''])
147162
Object.keys(sharedData.files)
163+
.map(e => e.replace(buildId, '<buildId>'))
148164
.sort()
149165
.forEach((fileName, index, { length }) => {
150166
const innerSymbol = index === length - 1 ? '└' : '├'
167+
168+
const originalName = fileName.replace('<buildId>', buildId)
169+
const cleanName = fileName
170+
// Trim off `static/`
171+
.replace(/^static\//, '')
172+
// Re-add `static/` for root files
173+
.replace(/^<buildId>/, 'static')
174+
// Remove file hash
175+
.replace(/[.-][0-9a-z]{20}(?=\.)/, '')
176+
151177
messages.push([
152-
` ${innerSymbol} ${fileName
153-
.replace(/^static\//, '')
154-
.replace(/[.-][0-9a-z]{20}(?=\.)/, '')}`,
155-
getPrettySize(sharedData.files[fileName]),
178+
` ${innerSymbol} ${cleanName}`,
179+
prettyBytes(sharedData.files[originalName]),
180+
'',
156181
])
157182
})
158183

159184
console.log(
160185
textTable(messages, {
161-
align: ['l', 'l'],
186+
align: ['l', 'l', 'r'],
162187
stringLength: str => stripAnsi(str).length,
163188
})
164189
)
@@ -253,6 +278,7 @@ export function printCustomRoutes({
253278
type BuildManifestShape = { pages: { [k: string]: string[] } }
254279
type ComputeManifestShape = {
255280
commonFiles: string[]
281+
uniqueFiles: string[]
256282
sizeCommonFile: { [file: string]: number }
257283
sizeCommonFiles: number
258284
}
@@ -266,6 +292,7 @@ let lastComputePageInfo: boolean | undefined
266292
async function computeFromManifest(
267293
manifest: BuildManifestShape,
268294
distPath: string,
295+
buildId: string,
269296
isModern: boolean,
270297
pageInfos?: Map<string, PageInfo>
271298
): Promise<ComputeManifestShape> {
@@ -304,16 +331,30 @@ async function computeFromManifest(
304331
return
305332
}
306333

307-
if (files.has(file)) {
334+
if (key === '/_app') {
335+
files.set(file, Infinity)
336+
} else if (files.has(file)) {
308337
files.set(file, files.get(file)! + 1)
309338
} else {
310339
files.set(file, 1)
311340
}
312341
})
313342
})
314343

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+
315353
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)
317358
.map(([f]) => f)
318359

319360
let stats: [string, number][]
@@ -330,6 +371,7 @@ async function computeFromManifest(
330371

331372
lastCompute = {
332373
commonFiles,
374+
uniqueFiles,
333375
sizeCommonFile: stats.reduce(
334376
(obj, n) => Object.assign(obj, { [n[0]]: n[1] }),
335377
{}
@@ -349,15 +391,27 @@ function difference<T>(main: T[], sub: T[]): T[] {
349391
return [...a].filter(x => !b.has(x))
350392
}
351393

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+
352404
export async function getSharedSizes(
353405
distPath: string,
354406
buildManifest: BuildManifestShape,
407+
buildId: string,
355408
isModern: boolean,
356409
pageInfos: Map<string, PageInfo>
357410
): Promise<{ total: number; files: { [page: string]: number } }> {
358411
const data = await computeFromManifest(
359412
buildManifest,
360413
distPath,
414+
buildId,
361415
isModern,
362416
pageInfos
363417
)
@@ -370,27 +424,54 @@ export async function getPageSizeInKb(
370424
buildId: string,
371425
buildManifest: BuildManifestShape,
372426
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)
381450

382451
const clientBundle = path.join(
383452
distPath,
384453
`static/${buildId}/pages/`,
385454
`${page}${isModern ? '.module' : ''}.js`
386455
)
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+
}
388466

389467
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]
392473
} catch (_) {}
393-
return -1
474+
return [-1, -1]
394475
}
395476

396477
export async function isPageStatic(

test/integration/build-output/test/index.test.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ describe('Build Output', () => {
2727
expect(stdout).toMatch(/ runtime\/main\.js [ 0-9.]* kB/)
2828
expect(stdout).toMatch(/ chunks\/framework\.js [ 0-9. ]* kB/)
2929

30-
expect(stdout).not.toContain('/_document')
31-
expect(stdout).not.toContain('/_app')
32-
expect(stdout).not.toContain('/_error')
30+
expect(stdout).not.toContain(' /_document')
31+
expect(stdout).not.toContain(' /_app')
32+
expect(stdout).not.toContain(' /_error')
3333

3434
expect(stdout).toContain('○ /')
3535
})
@@ -53,10 +53,10 @@ describe('Build Output', () => {
5353
expect(stdout).toMatch(/ runtime\/main\.js [ 0-9.]* kB/)
5454
expect(stdout).toMatch(/ chunks\/framework\.js [ 0-9. ]* kB/)
5555

56-
expect(stdout).not.toContain('/_document')
57-
expect(stdout).not.toContain('/_error')
56+
expect(stdout).not.toContain(' /_document')
57+
expect(stdout).not.toContain(' /_error')
5858

59-
expect(stdout).toContain('/_app')
59+
expect(stdout).toContain(' /_app')
6060
expect(stdout).toContain('○ /')
6161
})
6262
})
@@ -73,15 +73,15 @@ describe('Build Output', () => {
7373
stdout: true,
7474
})
7575

76-
expect(stdout).toMatch(/\/ [ 0-9.]* kB/)
76+
expect(stdout).toMatch(/\/ [ 0-9.]* B [ 0-9.]* kB/)
7777
expect(stdout).toMatch(/\/amp .* AMP/)
7878
expect(stdout).toMatch(/\/hybrid [ 0-9.]* B/)
7979
expect(stdout).toMatch(/\+ shared by all [ 0-9.]* kB/)
8080
expect(stdout).toMatch(/ runtime\/main\.js [ 0-9.]* kB/)
8181
expect(stdout).toMatch(/ chunks\/framework\.js [ 0-9. ]* kB/)
8282

83-
expect(stdout).not.toContain('/_document')
84-
expect(stdout).not.toContain('/_error')
83+
expect(stdout).not.toContain(' /_document')
84+
expect(stdout).not.toContain(' /_error')
8585

8686
expect(stdout).toContain('○ /')
8787
})
@@ -105,10 +105,10 @@ describe('Build Output', () => {
105105
expect(stdout).toMatch(/ runtime\/main\.js [ 0-9.]* kB/)
106106
expect(stdout).toMatch(/ chunks\/framework\.js [ 0-9. ]* kB/)
107107

108-
expect(stdout).not.toContain('/_document')
109-
expect(stdout).not.toContain('/_app')
108+
expect(stdout).not.toContain(' /_document')
109+
expect(stdout).not.toContain(' /_app')
110110

111-
expect(stdout).toContain('/_error')
111+
expect(stdout).toContain(' /_error')
112112
expect(stdout).toContain('○ /')
113113
})
114114
})

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy