Skip to content

Commit

Permalink
Merge branch 'canary' into sam/fix-fetch-cache-tags
Browse files Browse the repository at this point in the history
  • Loading branch information
samcx authored Apr 9, 2024
2 parents 947fa92 + 8c9c1d2 commit dce5b09
Show file tree
Hide file tree
Showing 36 changed files with 792 additions and 28 deletions.
38 changes: 38 additions & 0 deletions docs/02-app/02-api-reference/01-components/image.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Here's a summary of the props available for the Image Component:
| [`onError`](#onerror) | `onError(event => fail()}` | Function | - |
| [`loading`](#loading) | `loading="lazy"` | String | - |
| [`blurDataURL`](#blurdataurl) | `blurDataURL="data:image/jpeg..."` | String | - |
| [`overrideSrc`](#overridesrc) | `overrideSrc="/seo.png"` | String | - |
</div>

## Required Props
Expand Down Expand Up @@ -424,6 +425,42 @@ module.exports = {
}
```

### `overrideSrc`

When providing the `src` prop to the `<Image>` component, both the `srcset` and `src` attributes are generated automatically for the resulting `<img>`.

```jsx filename="input.js"
<Image src="/me.jpg" />
```

```html filename="output.html"
<img
srcset="
/_next/image?url=%2Fme.jpg&w=640&q=75 1x,
/_next/image?url=%2Fme.jpg&w=828&q=75 2x
"
src="/_next/image?url=%2Fme.jpg&w=828&q=75"
/>
```

In some cases, it is not desirable to have the `src` attribute generated and you may wish to override it using the `overrideSrc` prop.

For example, when upgrading an existing website from `<img>` to `<Image>`, you may wish to maintain the same `src` attribute for SEO purposes such as image ranking or avoiding recrawl.

```jsx filename="input.js"
<Image src="/me.jpg" overrideSrc="/override.jpg" />
```

```html filename="output.html"
<img
srcset="
/_next/image?url=%2Fme.jpg&w=640&q=75 1x,
/_next/image?url=%2Fme.jpg&w=828&q=75 2x
"
src="/override.jpg"
/>
```

### Other Props

Other properties on the `<Image />` component will be passed to the underlying
Expand Down Expand Up @@ -963,6 +1000,7 @@ This `next/image` component uses browser native [lazy loading](https://caniuse.c

| Version | Changes |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `v14.2.0` | `overrideSrc` prop added. |
| `v14.1.0` | `getImageProps()` is stable. |
| `v14.0.0` | `onLoadingComplete` prop and `domains` config deprecated. |
| `v13.4.14` | `placeholder` prop support for `data:/image...` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Next.js includes a [short list of popular packages](https://github.com/vercel/ne
- `ts-node`
- `typescript`
- `vscode-oniguruma`
- `undici`
- `webpack`
- `websocket`
- `zeromq`
4 changes: 4 additions & 0 deletions packages/next/src/bin/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ program
)}`
)
.option('-d, --debug', 'Enables a more verbose build output.')
.option(
'--experimental-debug-memory-usage',
'Enables memory profiling features to debug memory consumption.'
)
.option('--profile', 'Enables production profiling for React.')
.option('--no-lint', 'Disables linting.')
.option('--no-mangling', 'Disables mangling.')
Expand Down
8 changes: 8 additions & 0 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ import { TurbopackManifestLoader } from '../server/dev/turbopack/manifest-loader
import type { Entrypoints } from '../server/dev/turbopack/types'
import { buildCustomRoute } from '../lib/build-custom-route'
import { createProgress } from './progress'
import { traceMemoryUsage } from '../lib/memory/trace'
import { generateEncryptionKeyBase64 } from '../server/app-render/encryption-utils'

interface ExperimentalBypassForInfo {
Expand Down Expand Up @@ -1580,6 +1581,7 @@ export default async function build(
}

Log.info('Creating an optimized production build ...')
traceMemoryUsage('Starting build', nextBuildSpan)

if (!isGenerateMode) {
if (runServerAndEdgeInParallel || collectServerBuildTracesInParallel) {
Expand All @@ -1588,6 +1590,7 @@ export default async function build(
const serverBuildPromise = webpackBuild(useBuildWorker, [
'server',
]).then((res) => {
traceMemoryUsage('Finished server compilation', nextBuildSpan)
buildTraceContext = res.buildTraceContext
durationInSeconds += res.duration

Expand Down Expand Up @@ -1626,6 +1629,7 @@ export default async function build(
'edge-server',
]).then((res) => {
durationInSeconds += res.duration
traceMemoryUsage('Finished edge-server compilation', nextBuildSpan)
})
if (runServerAndEdgeInParallel) {
await serverBuildPromise
Expand All @@ -1634,6 +1638,7 @@ export default async function build(

await webpackBuild(useBuildWorker, ['client']).then((res) => {
durationInSeconds += res.duration
traceMemoryUsage('Finished client compilation', nextBuildSpan)
})

Log.event('Compiled successfully')
Expand All @@ -1648,6 +1653,7 @@ export default async function build(
const { duration: compilerDuration, ...rest } = turboNextBuild
? await turbopackBuild()
: await webpackBuild(useBuildWorker, null)
traceMemoryUsage('Finished build', nextBuildSpan)

buildTraceContext = rest.buildTraceContext

Expand All @@ -1663,6 +1669,7 @@ export default async function build(
// For app directory, we run type checking after build.
if (appDir && !isCompileMode && !isGenerateMode) {
await startTypeChecking(typeCheckingOptions)
traceMemoryUsage('Finished type checking', nextBuildSpan)
}

const postCompileSpinner = createSpinner('Collecting page data')
Expand Down Expand Up @@ -2302,6 +2309,7 @@ export default async function build(
})

if (postCompileSpinner) postCompileSpinner.stopAndPersist()
traceMemoryUsage('Finished collecting page data', nextBuildSpan)

if (customAppGetInitialProps) {
console.warn(
Expand Down
52 changes: 35 additions & 17 deletions packages/next/src/cli/next-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import { warn } from '../build/output/log'
import { printAndExit } from '../server/lib/utils'
import isError from '../lib/is-error'
import { getProjectDir } from '../lib/get-project-dir'
import { enableMemoryDebuggingMode } from '../lib/memory/startup'
import { disableMemoryDebuggingMode } from '../lib/memory/shutdown'

type NextBuildOptions = {
debug?: boolean
profile?: boolean
lint: boolean
mangling: boolean
experimentalDebugMemoryUsage: boolean
experimentalAppOnly?: boolean
experimentalTurbo?: boolean
experimentalBuildMode: 'default' | 'compile' | 'generate'
Expand All @@ -25,6 +28,7 @@ const nextBuild = (options: NextBuildOptions, directory?: string) => {

const {
debug,
experimentalDebugMemoryUsage,
profile,
lint,
mangling,
Expand All @@ -49,6 +53,11 @@ const nextBuild = (options: NextBuildOptions, directory?: string) => {
)
}

if (experimentalDebugMemoryUsage) {
process.env.EXPERIMENTAL_DEBUG_MEMORY_USAGE = '1'
enableMemoryDebuggingMode()
}

const dir = getProjectDir(directory)

// Check if the provided directory exists
Expand All @@ -69,23 +78,32 @@ const nextBuild = (options: NextBuildOptions, directory?: string) => {
experimentalAppOnly,
!!process.env.TURBOPACK,
experimentalBuildMode
).catch((err) => {
console.error('')
if (
isError(err) &&
(err.code === 'INVALID_RESOLVE_ALIAS' ||
err.code === 'WEBPACK_ERRORS' ||
err.code === 'BUILD_OPTIMIZATION_FAILED' ||
err.code === 'NEXT_EXPORT_ERROR' ||
err.code === 'NEXT_STATIC_GEN_BAILOUT' ||
err.code === 'EDGE_RUNTIME_UNSUPPORTED_API')
) {
printAndExit(`> ${err.message}`)
} else {
console.error('> Build error occurred')
printAndExit(err)
}
})
)
.catch((err) => {
if (experimentalDebugMemoryUsage) {
disableMemoryDebuggingMode()
}
console.error('')
if (
isError(err) &&
(err.code === 'INVALID_RESOLVE_ALIAS' ||
err.code === 'WEBPACK_ERRORS' ||
err.code === 'BUILD_OPTIMIZATION_FAILED' ||
err.code === 'NEXT_EXPORT_ERROR' ||
err.code === 'NEXT_STATIC_GEN_BAILOUT' ||
err.code === 'EDGE_RUNTIME_UNSUPPORTED_API')
) {
printAndExit(`> ${err.message}`)
} else {
console.error('> Build error occurred')
printAndExit(err)
}
})
.finally(() => {
if (experimentalDebugMemoryUsage) {
disableMemoryDebuggingMode()
}
})
}

export { nextBuild }
12 changes: 12 additions & 0 deletions packages/next/src/lib/memory/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Debug Memory Usage

This directory contains a number of utilities to help in debugging
memory usage.

When enabled, code in this directory will:

- Periodically print out memory usage statistics
- Print a report with a summary of memory usage and suggestions for how to
improve.
- Generate heap snapshots automatically when too much memory is being consumed
- Monitor garbage collection events for long running GCs
36 changes: 36 additions & 0 deletions packages/next/src/lib/memory/gc-observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { PerformanceObserver } from 'perf_hooks'
import { warn } from '../../build/output/log'
import { bold } from '../picocolors'

const LONG_RUNNING_GC_THRESHOLD_MS = 15

const gcEvents: PerformanceEntry[] = []
const obs = new PerformanceObserver((list) => {
const entry = list.getEntries()[0]
gcEvents.push(entry)

if (entry.duration > LONG_RUNNING_GC_THRESHOLD_MS) {
warn(bold(`Long running GC detected: ${entry.duration.toFixed(2)}ms`))
}
})

/**
* Starts recording garbage collection events in the process and warn on long
* running GCs. To disable, call `stopObservingGc`.
*/
export function startObservingGc() {
obs.observe({ entryTypes: ['gc'] })
}

export function stopObservingGc() {
obs.disconnect()
}

/**
* Returns all recorded garbage collection events. This function will only
* return information from when `startObservingGc` was enabled and before
* `stopObservingGc` was called.
*/
export function getGcEvents() {
return gcEvents
}
28 changes: 28 additions & 0 deletions packages/next/src/lib/memory/shutdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { info } from '../../build/output/log'
import { bold } from '../picocolors'
import { getGcEvents, stopObservingGc } from './gc-observer'
import { getAllMemoryUsageSpans, stopPeriodicMemoryUsageTracing } from './trace'

export function disableMemoryDebuggingMode(): void {
stopPeriodicMemoryUsageTracing()
stopObservingGc()

info(bold('Memory usage report:'))

const gcEvents = getGcEvents()
const totalTimeInGcMs = gcEvents.reduce(
(acc, event) => acc + event.duration,
0
)
info(` - Total time spent in GC: ${totalTimeInGcMs.toFixed(2)}ms`)

const allMemoryUsage = getAllMemoryUsageSpans()
const peakHeapUsage = Math.max(
...allMemoryUsage.map((usage) => usage['memory.heapUsed'])
)
const peakRssUsage = Math.max(
...allMemoryUsage.map((usage) => usage['memory.rss'])
)
info(` - Peak heap usage: ${(peakHeapUsage / 1024 / 1024).toFixed(2)} MB`)
info(` - Peak RSS usage: ${(peakRssUsage / 1024 / 1024).toFixed(2)} MB`)
}
52 changes: 52 additions & 0 deletions packages/next/src/lib/memory/startup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import v8 from 'v8'
import { info, warn } from '../../build/output/log'
import { italic } from '../picocolors'
import { startObservingGc } from './gc-observer'
import { startPeriodicMemoryUsageTracing } from './trace'

export function enableMemoryDebuggingMode(): void {
// This will generate a heap snapshot when the program is close to the
// memory limit. It does not give any warning to the user though which
// can be jarring. If memory is large, this may take a long time.
if ('setHeapSnapshotNearHeapLimit' in v8) {
// @ts-expect-error - this method exists since Node 16.
v8.setHeapSnapshotNearHeapLimit(1)
}

// This flag will kill the process when it starts to GC thrash when it's
// close to the memory limit rather than continuing to try to collect
// memory ineffectively.
v8.setFlagsFromString('--detect-ineffective-gcs-near-heap-limit')

// This allows users to generate a heap snapshot on demand just by sending
// a signal to the process.
process.on('SIGUSR2', () => {
warn(
`Received SIGUSR2 signal. Generating heap snapshot. ${italic(
'Note: this will take some time.'
)}`
)
v8.writeHeapSnapshot()
})

startObservingGc()
startPeriodicMemoryUsageTracing()

warn(
`Memory debugging mode is enabled. ${italic(
'Note: This will affect performance.'
)}`
)
info(
' - Heap snapshots will be automatically generated when the process reaches more than 70% of the memory limit and again when the process is just about to run out of memory.'
)
info(
` - To manually generate a heap snapshot, send the process a SIGUSR2 signal: \`kill -SIGUSR2 ${process.pid}\``
)
info(
' - Heap snapshots when there is high memory will take a very long time to complete and may be difficult to analyze in tools.'
)
info(
' - See https://nextjs.org/docs/app/building-your-application/optimizing/memory-usage for more information.'
)
}
Loading

0 comments on commit dce5b09

Please sign in to comment.