Skip to content

Commit

Permalink
Optimize Next.js bootup compilation (#50379)
Browse files Browse the repository at this point in the history
## What?

Currently we use the initial compile to add entrypoints that we know are
going to be used in the application. For example the Next.js runtime,
`react`, `react-dom`, and such. While this was the right default when
Next.js only had `pages`, it's no longer true when both `pages` and
`app` exist. E.g. when you're working on a page using App Router we'd
still compile `pages/_app`, `pages/_document`, and `pages/_error` even
though those would not be used. In case of larger applications (e.g.
Vercel's application) this would mean thousands of extra modules being
compiled even though you don't need them for the page you're looking at.
Similarly we'd compile the Next.js runtime for App Router even when
you're only using `pages`.

This PR changes the handling to only compile the entries that are needed
for the current set of visited pages (on-demand-entries). If that set
only includes `app` entrypoints then the `pages` related files will be
excluded. If the set contains both `app` and `pages` both will be
included. Similarly for `amp`, if you don't use `amp: true` / `amp:
hybrid` the development runtime will not be compiled.

📔 Note: This is specifically for webpack, Turbopack already compiles
everything needed lazily so it didn't have this limitation.

#### Before

```
- event compiled client and server successfully in 1079 ms (306 modules)
- event compiled client and server successfully in 155 ms (306 modules)
```

With opening `/`:

```
- event compiled client and server successfully in 1118 ms (306 modules)
- event compiled client and server successfully in 157 ms (306 modules)
- event compiled client and server successfully in 599 ms (486 modules)
```

Total: 1.874ms (Note: This number is much higher when `pages/_app`
imports many modules).

#### After

```
- event compiled client and server successfully in 118 ms (20 modules)
- event compiled client and server successfully in 65 ms (20 modules)
```

📔 Note: opening the page then causes the Next.js / React runtime to be
compiled ofcourse

```
- event compiled client and server successfully in 115 ms (20 modules)
- event compiled client and server successfully in 57 ms (20 modules)
- event compiled client and server successfully in 1137 ms (361 modules)
```

Total: 1.309ms (Note: This number is not affected by`pages/_app`
importing many modules).

## How?

We can only apply this optimization after we've looped over the list of
on-demand entries, as that has the required metadata. Hence why I went
with deleting the keys for each respective type of entrypoint (`pages`,
`app`, `amp`).

<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:

## For Contributors

### Improving Documentation or adding/fixing Examples

- The "examples guidelines" are followed from our contributing doc
https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md
- Make sure the linting passes by running `pnpm build && pnpm lint`. See
https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md

### Fixing a bug

- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md

### Adding a feature

- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md



## For Maintainers

- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Closes NEXT-
Fixes #

-->

---------

Co-authored-by: JJ Kasper <jj@jjsweb.site>
  • Loading branch information
timneutkens and ijjk committed May 30, 2023
1 parent 7adb273 commit f85b45f
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 8 deletions.
3 changes: 3 additions & 0 deletions packages/next/src/build/analysis/get-page-static-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface PageStaticInfo {
ssr?: boolean
rsc?: RSCModuleType
middleware?: Partial<MiddlewareConfig>
amp?: boolean | 'hybrid'
}

const CLIENT_MODULE_LABEL =
Expand Down Expand Up @@ -488,6 +489,7 @@ export async function getPageStaticInfo(params: {
ssr,
ssg,
rsc,
amp: config.amp || false,
...(middlewareConfig && { middleware: middlewareConfig }),
...(resolvedRuntime && { runtime: resolvedRuntime }),
preferredRegion,
Expand All @@ -498,6 +500,7 @@ export async function getPageStaticInfo(params: {
ssr: false,
ssg: false,
rsc: RSC_MODULE_TYPES.server,
amp: false,
runtime: undefined,
}
}
7 changes: 1 addition & 6 deletions packages/next/src/build/webpack/loaders/next-app-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,12 +380,7 @@ async function createTreeCodeFromPath(
const defaultPath =
(await resolver(
`${appDirPrefix}${segmentPath}/${actualSegment}/default`
)) ??
(await resolver(
`next/dist/client/components/parallel-route-default`,
false,
true
))
)) ?? 'next/dist/client/components/parallel-route-default'

props[normalizeParallelKey(adjacentParallelSegment)] = `[
'__DEFAULT__',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@ export class ClientReferenceEntryPlugin {
continue
}

// TODO-APP: Enable these lines. This ensures no entrypoint is created for layout/page when there are no client components.
// Currently disabled because it causes test failures in CI.
// if (clientImports.length === 0 && actionImports.length === 0) {
// continue
// }

const relativeRequest = isAbsoluteRequest
? path.relative(compilation.options.context, entryRequest)
: entryRequest
Expand Down
39 changes: 38 additions & 1 deletion packages/next/src/server/dev/hot-reloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ function erroredPages(compilation: webpack.Compilation) {
}

export default class HotReloader {
private hasAmpEntrypoints: boolean
private hasAppRouterEntrypoints: boolean
private hasPagesRouterEntrypoints: boolean
private dir: string
private buildId: string
private interceptors: any[]
Expand Down Expand Up @@ -223,6 +226,9 @@ export default class HotReloader {
telemetry: Telemetry
}
) {
this.hasAmpEntrypoints = false
this.hasAppRouterEntrypoints = false
this.hasPagesRouterEntrypoints = false
this.buildId = buildId
this.dir = dir
this.interceptors = []
Expand Down Expand Up @@ -719,6 +725,11 @@ export default class HotReloader {
}
}

// Ensure _error is considered a `pages` page.
if (page === '/_error') {
this.hasPagesRouterEntrypoints = true
}

const hasAppDir = !!this.appDir
const isAppPath = hasAppDir && bundlePath.startsWith('app/')
const staticInfo = isEntry
Expand All @@ -732,6 +743,10 @@ export default class HotReloader {
page,
})
: {}

if (staticInfo.amp === true || staticInfo.amp === 'hybrid') {
this.hasAmpEntrypoints = true
}
const isServerComponent =
isAppPath && staticInfo.rsc !== RSC_MODULE_TYPES.client

Expand All @@ -740,6 +755,14 @@ export default class HotReloader {
: entryData.bundlePath.startsWith('app/')
? 'app'
: 'root'

if (pageType === 'pages') {
this.hasPagesRouterEntrypoints = true
}
if (pageType === 'app') {
this.hasAppRouterEntrypoints = true
}

await runDependingOnPageType({
page,
pageRuntime: staticInfo.runtime,
Expand Down Expand Up @@ -879,6 +902,21 @@ export default class HotReloader {
})
})
)

if (!this.hasAmpEntrypoints) {
delete entrypoints.amp
}
if (!this.hasPagesRouterEntrypoints) {
delete entrypoints.main
delete entrypoints['pages/_app']
delete entrypoints['pages/_error']
delete entrypoints['/_error']
delete entrypoints['pages/_document']
}
if (!this.hasAppRouterEntrypoints) {
delete entrypoints['main-app']
}

return entrypoints
}
}
Expand Down Expand Up @@ -1082,7 +1120,6 @@ export default class HotReloader {
const documentChunk = compilation.namedChunks.get('pages/_document')
// If the document chunk can't be found we do nothing
if (!documentChunk) {
console.warn('_document.js chunk not found')
return
}

Expand Down
2 changes: 1 addition & 1 deletion test/e2e/app-dir/app-static/app-static.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ createNextDescribe(
}
})

it.each([
it.skip.each([
{
path: '/react-fetch-deduping-node',
},
Expand Down

0 comments on commit f85b45f

Please sign in to comment.