Skip to content

Commit

Permalink
Separate Self Size and First Load Size (#10014)
Browse files Browse the repository at this point in the history
* Separate Self Size and First Load Size

* Tweak tests
  • Loading branch information
Timer authored Jan 9, 2020
1 parent a86640b commit 0fd15ad
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 38 deletions.
6 changes: 4 additions & 2 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ export default async function build(dir: string, conf = null): Promise<void> {
await Promise.all(
pageKeys.map(async page => {
const actualPage = page === '/' ? '/index' : page
const size = await getPageSizeInKb(
const [selfSize, allSize] = await getPageSizeInKb(
actualPage,
distDir,
buildId,
Expand Down Expand Up @@ -508,7 +508,8 @@ export default async function build(dir: string, conf = null): Promise<void> {
}

pageInfos.set(page, {
size,
size: selfSize,
totalSize: allSize,
serverBundle,
static: isStatic,
isSsg,
Expand Down Expand Up @@ -754,6 +755,7 @@ export default async function build(dir: string, conf = null): Promise<void> {
isLikeServerless,
{
distPath: distDir,
buildId: buildId,
pagesDir,
pageExtensions: config.pageExtensions,
buildManifest,
Expand Down
129 changes: 105 additions & 24 deletions packages/next/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface PageInfo {
isAmp?: boolean
isHybridAmp?: boolean
size: number
totalSize: number
static: boolean
isSsg: boolean
ssgPageRoutes: string[] | null
Expand All @@ -46,12 +47,14 @@ export async function printTreeView(
serverless: boolean,
{
distPath,
buildId,
pagesDir,
pageExtensions,
buildManifest,
isModern,
}: {
distPath: string
buildId: string
pagesDir: string
pageExtensions: string[]
buildManifest: BuildManifestShape
Expand All @@ -68,8 +71,12 @@ export async function printTreeView(
return chalk.red.bold(size)
}

const messages: [string, string][] = [
['Page', 'Size'].map(entry => chalk.underline(entry)) as [string, string],
const messages: [string, string, string][] = [
['Page', 'Size', 'First Load'].map(entry => chalk.underline(entry)) as [
string,
string,
string
],
]

const hasCustomApp = await findPageFile(pagesDir, '/_app', pageExtensions)
Expand Down Expand Up @@ -113,7 +120,14 @@ export async function printTreeView(
? pageInfo.isAmp
? chalk.cyan('AMP')
: pageInfo.size >= 0
? getPrettySize(pageInfo.size)
? prettyBytes(pageInfo.size)
: ''
: '',
pageInfo
? pageInfo.isAmp
? chalk.cyan('AMP')
: pageInfo.size >= 0
? getPrettySize(pageInfo.totalSize)
: ''
: '',
])
Expand All @@ -131,34 +145,45 @@ export async function printTreeView(

routes.forEach((slug, index, { length }) => {
const innerSymbol = index === length - 1 ? '└' : '├'
messages.push([`${contSymbol} ${innerSymbol} ${slug}`, ''])
messages.push([`${contSymbol} ${innerSymbol} ${slug}`, '', ''])
})
}
})

const sharedData = await getSharedSizes(
distPath,
buildManifest,
buildId,
isModern,
pageInfos
)

messages.push(['+ shared by all', getPrettySize(sharedData.total)])
messages.push(['+ shared by all', getPrettySize(sharedData.total), ''])
Object.keys(sharedData.files)
.map(e => e.replace(buildId, '<buildId>'))
.sort()
.forEach((fileName, index, { length }) => {
const innerSymbol = index === length - 1 ? '└' : '├'

const originalName = fileName.replace('<buildId>', buildId)
const cleanName = fileName
// Trim off `static/`
.replace(/^static\//, '')
// Re-add `static/` for root files
.replace(/^<buildId>/, 'static')
// Remove file hash
.replace(/[.-][0-9a-z]{20}(?=\.)/, '')

messages.push([
` ${innerSymbol} ${fileName
.replace(/^static\//, '')
.replace(/[.-][0-9a-z]{20}(?=\.)/, '')}`,
getPrettySize(sharedData.files[fileName]),
` ${innerSymbol} ${cleanName}`,
prettyBytes(sharedData.files[originalName]),
'',
])
})

console.log(
textTable(messages, {
align: ['l', 'l'],
align: ['l', 'l', 'r'],
stringLength: str => stripAnsi(str).length,
})
)
Expand Down Expand Up @@ -253,6 +278,7 @@ export function printCustomRoutes({
type BuildManifestShape = { pages: { [k: string]: string[] } }
type ComputeManifestShape = {
commonFiles: string[]
uniqueFiles: string[]
sizeCommonFile: { [file: string]: number }
sizeCommonFiles: number
}
Expand All @@ -266,6 +292,7 @@ let lastComputePageInfo: boolean | undefined
async function computeFromManifest(
manifest: BuildManifestShape,
distPath: string,
buildId: string,
isModern: boolean,
pageInfos?: Map<string, PageInfo>
): Promise<ComputeManifestShape> {
Expand Down Expand Up @@ -304,16 +331,30 @@ async function computeFromManifest(
return
}

if (files.has(file)) {
if (key === '/_app') {
files.set(file, Infinity)
} else if (files.has(file)) {
files.set(file, files.get(file)! + 1)
} else {
files.set(file, 1)
}
})
})

// Add well-known shared file
files.set(
path.join(
`static/${buildId}/pages/`,
`/_app${isModern ? '.module' : ''}.js`
),
Infinity
)

const commonFiles = [...files.entries()]
.filter(([, len]) => len === expected)
.filter(([, len]) => len === expected || len === Infinity)
.map(([f]) => f)
const uniqueFiles = [...files.entries()]
.filter(([, len]) => len === 1)
.map(([f]) => f)

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

lastCompute = {
commonFiles,
uniqueFiles,
sizeCommonFile: stats.reduce(
(obj, n) => Object.assign(obj, { [n[0]]: n[1] }),
{}
Expand All @@ -349,15 +391,27 @@ function difference<T>(main: T[], sub: T[]): T[] {
return [...a].filter(x => !b.has(x))
}

function intersect<T>(main: T[], sub: T[]): T[] {
const a = new Set(main)
const b = new Set(sub)
return [...new Set([...a].filter(x => b.has(x)))]
}

function sum(a: number[]): number {
return a.reduce((size, stat) => size + stat, 0)
}

export async function getSharedSizes(
distPath: string,
buildManifest: BuildManifestShape,
buildId: string,
isModern: boolean,
pageInfos: Map<string, PageInfo>
): Promise<{ total: number; files: { [page: string]: number } }> {
const data = await computeFromManifest(
buildManifest,
distPath,
buildId,
isModern,
pageInfos
)
Expand All @@ -370,27 +424,54 @@ export async function getPageSizeInKb(
buildId: string,
buildManifest: BuildManifestShape,
isModern: boolean
): Promise<number> {
const data = await computeFromManifest(buildManifest, distPath, isModern)
const deps = difference(buildManifest.pages[page] || [], data.commonFiles)
.filter(
entry =>
entry.endsWith('.js') && entry.endsWith('.module.js') === isModern
)
.map(dep => `${distPath}/${dep}`)
): Promise<[number, number]> {
const data = await computeFromManifest(
buildManifest,
distPath,
buildId,
isModern
)

const fnFilterModern = (entry: string) =>
entry.endsWith('.js') && entry.endsWith('.module.js') === isModern

const pageFiles = (buildManifest.pages[page] || []).filter(fnFilterModern)
const appFiles = (buildManifest.pages['/_app'] || []).filter(fnFilterModern)

const fnMapRealPath = (dep: string) => `${distPath}/${dep}`

const allFilesReal = [...new Set([...pageFiles, ...appFiles])].map(
fnMapRealPath
)
const selfFilesReal = difference(
intersect(pageFiles, data.uniqueFiles),
data.commonFiles
).map(fnMapRealPath)

const clientBundle = path.join(
distPath,
`static/${buildId}/pages/`,
`${page}${isModern ? '.module' : ''}.js`
)
deps.push(clientBundle)
const appBundle = path.join(
distPath,
`static/${buildId}/pages/`,
`/_app${isModern ? '.module' : ''}.js`
)
selfFilesReal.push(clientBundle)
allFilesReal.push(clientBundle)
if (clientBundle !== appBundle) {
allFilesReal.push(appBundle)
}

try {
let depStats = await Promise.all(deps.map(fsStatGzip))
return depStats.reduce((size, stat) => size + stat, 0)
// Doesn't use `Promise.all`, as we'd double compute duplicate files. This
// function is memoized, so the second one will instantly resolve.
const allFilesSize = sum(await Promise.all(allFilesReal.map(fsStatGzip)))
const selfFilesSize = sum(await Promise.all(selfFilesReal.map(fsStatGzip)))
return [selfFilesSize, allFilesSize]
} catch (_) {}
return -1
return [-1, -1]
}

export async function isPageStatic(
Expand Down
24 changes: 12 additions & 12 deletions test/integration/build-output/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ describe('Build Output', () => {
expect(stdout).toMatch(/ runtime\/main\.js [ 0-9.]* kB/)
expect(stdout).toMatch(/ chunks\/framework\.js [ 0-9. ]* kB/)

expect(stdout).not.toContain('/_document')
expect(stdout).not.toContain('/_app')
expect(stdout).not.toContain('/_error')
expect(stdout).not.toContain(' /_document')
expect(stdout).not.toContain(' /_app')
expect(stdout).not.toContain(' /_error')

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

expect(stdout).not.toContain('/_document')
expect(stdout).not.toContain('/_error')
expect(stdout).not.toContain(' /_document')
expect(stdout).not.toContain(' /_error')

expect(stdout).toContain('/_app')
expect(stdout).toContain(' /_app')
expect(stdout).toContain('○ /')
})
})
Expand All @@ -73,15 +73,15 @@ describe('Build Output', () => {
stdout: true,
})

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

expect(stdout).not.toContain('/_document')
expect(stdout).not.toContain('/_error')
expect(stdout).not.toContain(' /_document')
expect(stdout).not.toContain(' /_error')

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

expect(stdout).not.toContain('/_document')
expect(stdout).not.toContain('/_app')
expect(stdout).not.toContain(' /_document')
expect(stdout).not.toContain(' /_app')

expect(stdout).toContain('/_error')
expect(stdout).toContain(' /_error')
expect(stdout).toContain('○ /')
})
})
Expand Down

0 comments on commit 0fd15ad

Please sign in to comment.