fix(next): next build --debug log output layout is broken (#63193)

### Why?

The output layout breaks when running `next build --debug`

#### Current

```sh
 ✓ Generating static pages (10/10) 
   Finalizing page optimization  .   Collecting build traces  .Redirects

┌ source: /:path+/
├ destination: /:path+
└ permanent: true
 

 ✓ Collecting build traces    
 ✓ Finalizing page optimization    
```

#### Expected

```sh
✓ Generating static pages (4/4) 
   Finalizing page optimization ...
   Collecting build traces ...


Redirects
┌ source: /:path+/
├ destination: /:path+
└ permanent: true
```

### How?

Moved the `debug` output right above the `routes` output.
Also, ensured that the output layout has a consistent number of line
breaks (example below marked as `>`):

> Two line breaks for the next `option`, a single line break within the
same content.

```sh
   Collecting build traces ...
>
>
Redirects
┌ source: /:path+/
├ destination: /:path+
└ permanent: true
>
┌ source: /redirects
├ destination: /
└ permanent: true
>
>
Headers
┌ source: /
└ headers:
  └ x-custom-headers: headers
>
>
Rewrites
┌ source: /rewrites
└ destination: /
>
>
Route (app)                              Size     First Load JS
┌ ○ /                                    141 B          86.2 kB
└ ○ /_not-found                          876 B          86.9 kB
```

Fixes #63192

---------
This commit is contained in:
Jiwon Choi 2024-04-03 07:05:28 +09:00 committed by GitHub
parent 17907b4316
commit 1511433212
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 140 additions and 52 deletions

View file

@ -3308,12 +3308,6 @@ export default async function build(
return Promise.reject(err)
})
if (debugOutput) {
nextBuildSpan
.traceChild('print-custom-routes')
.traceFn(() => printCustomRoutes({ redirects, rewrites, headers }))
}
// TODO: remove in the next major version
if (config.analyticsId) {
Log.warn(
@ -3370,6 +3364,12 @@ export default async function build(
if (postBuildSpinner) postBuildSpinner.stopAndPersist()
console.log()
if (debugOutput) {
nextBuildSpan
.traceChild('print-custom-routes')
.traceFn(() => printCustomRoutes({ redirects, rewrites, headers }))
}
await nextBuildSpan.traceChild('print-tree-view').traceAsyncFn(() =>
printTreeView(pageKeys, pageInfos, {
distPath: distDir,

View file

@ -773,7 +773,6 @@ export function printCustomRoutes({
const isRedirects = type === 'Redirects'
const isHeaders = type === 'Headers'
print(underline(type))
print()
/*
source
@ -815,9 +814,10 @@ export function printCustomRoutes({
})
.join('\n')
print(routesStr, '\n')
print(`${routesStr}\n`)
}
print()
if (redirects.length) {
printRoutes(redirects, 'Redirects')
}

View file

@ -0,0 +1,7 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}

View file

@ -0,0 +1,3 @@
export default function Page() {
return <p>hello world</p>
}

View file

@ -0,0 +1,44 @@
import { nextTestSetup } from 'e2e-utils'
import stripAnsi from 'strip-ansi'
describe('next build --debug', () => {
const { next } = nextTestSetup({
files: __dirname,
buildCommand: 'pnpm next build --debug',
})
let output = ''
beforeAll(() => {
output = stripAnsi(next.cliOutput)
})
const str = `
Redirects
source: /:path+/
destination: /:path+
permanent: true
source: /redirects
destination: /
permanent: true
Headers
source: /
headers:
x-custom-headers: headers
Rewrites
source: /rewrites
destination: /
Route (app)`
it('should log Redirects above Route(app)', async () => {
expect(output).toContain(str)
})
})

View file

@ -0,0 +1,33 @@
/** @type {import('next').NextConfig} */
module.exports = {
async redirects() {
return [
{
source: '/redirects',
destination: '/',
permanent: true,
},
]
},
async rewrites() {
return [
{
source: '/rewrites',
destination: '/',
},
]
},
async headers() {
return [
{
source: '/',
headers: [
{
key: 'x-custom-headers',
value: 'headers',
},
],
},
]
},
}

View file

@ -0,0 +1,7 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}

View file

@ -0,0 +1,3 @@
export default function Page() {
return <p>hello world</p>
}

View file

@ -1,50 +1,41 @@
import { createNextDescribe } from 'e2e-utils'
import { nextTestSetup } from 'e2e-utils'
import stripAnsi from 'strip-ansi'
createNextDescribe(
'production - app dir - build output',
{
files: {
'app/page.tsx': `export default function Page() { return null }`,
'app/layout.tsx': `export default function Layout({ children }) {
return (
<html><body>{children}</body></html>
)
}`,
},
},
({ next }) => {
let output = ''
beforeAll(() => {
output = stripAnsi(next.cliOutput)
})
describe('production - app dir - build output', () => {
const { next } = nextTestSetup({
files: __dirname,
})
it('should only log app routes', async () => {
expect(output).toContain('Route (app)')
expect(output).not.toContain('Route (pages)')
expect(output).not.toContain('/favicon.ico')
})
let output = ''
beforeAll(() => {
output = stripAnsi(next.cliOutput)
})
it('should always log version first then the rest jobs', async () => {
const indexOfVersion = output.indexOf('▲ Next.js')
const indexOfStartCompiling = output.indexOf(
'Creating an optimized production build'
)
const indexOfLinting = output.indexOf(
'Linting and checking validity of types'
)
expect(indexOfVersion).toBeLessThan(indexOfLinting)
expect(indexOfStartCompiling).toBeLessThan(indexOfLinting)
})
it('should only log app routes', async () => {
expect(output).toContain('Route (app)')
expect(output).not.toContain('Route (pages)')
expect(output).not.toContain('/favicon.ico')
})
it('should match the expected output format', async () => {
expect(output).toContain('Size')
expect(output).toContain('First Load JS')
expect(output).toContain('+ First Load JS shared by all')
expect(output).toContain('└ other shared chunks (total)')
it('should always log version first then the rest jobs', async () => {
const indexOfVersion = output.indexOf('▲ Next.js')
const indexOfStartCompiling = output.indexOf(
'Creating an optimized production build'
)
const indexOfLinting = output.indexOf(
'Linting and checking validity of types'
)
expect(indexOfVersion).toBeLessThan(indexOfLinting)
expect(indexOfStartCompiling).toBeLessThan(indexOfLinting)
})
// output type
expect(output).toContain('○ (Static) prerendered as static content')
})
}
)
it('should match the expected output format', async () => {
expect(output).toContain('Size')
expect(output).toContain('First Load JS')
expect(output).toContain('+ First Load JS shared by all')
expect(output).toContain('└ other shared chunks (total)')
// output type
expect(output).toContain('○ (Static) prerendered as static content')
})
})