Skip to content

Commit

Permalink
Merge branch 'canary' into fix-bug-60532-get-static-props-and-generat…
Browse files Browse the repository at this point in the history
…e-static-params
  • Loading branch information
ijjk authored Oct 10, 2024
2 parents 767bfd3 + 5bd4271 commit bc4f701
Show file tree
Hide file tree
Showing 30 changed files with 1,038 additions and 190 deletions.
2 changes: 1 addition & 1 deletion crates/next-api/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1281,7 +1281,7 @@ impl Project {
}
None => match *self.next_mode().await? {
NextMode::Development => Ok(Vc::upcast(DevModuleIdStrategy::new())),
NextMode::Build => Ok(Vc::upcast(GlobalModuleIdStrategyBuilder::build(self))),
NextMode::Build => Ok(Vc::upcast(DevModuleIdStrategy::new())),
},
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
```jsx filename="app/@analytics/layout.js" switcher
import Link from 'next/link'

export default function Layout({ children }: { children: React.ReactNode }) {
export default function Layout({ children }) {
return (
<>
<nav>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,9 @@ Replacing `<transform>` and `<path>` with appropriate values.

### 15.0

#### Transform App Router Route Segment Config `runtime` value from `experimental-edge` to `edge`

##### `app-dir-runtime-config-experimental-edge`

> **Note**: This codemod is App Router specific.
```bash filename="Terminal"
npx @next/codemod@latest app-dir-runtime-config-experimental-edge .
```

This codemod transforms [Route Segment Config `runtime`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#runtime) value `experimental-edge` to `edge`.

For example:

```ts
export const runtime = 'experimental-edge'
```

Transforms into:

```ts
export const runtime = 'edge'
```

#### Migrate to async Dynamic APIs

APIs that opted into dynamic rendering that previously supported synchronous access are now asynchronous. You can read more about this breaking change in the [upgrade guide](/docs/app/building-your-application/upgrading/version-15)
APIs that opted into dynamic rendering that previously supported synchronous access are now asynchronous. You can read more about this breaking change in the [upgrade guide](/docs/app/building-your-application/upgrading/version-15).

##### `next-async-request-api`

Expand Down Expand Up @@ -117,7 +93,7 @@ export default function Page({
searchParams,
}: {
params: { slug: string }
searchParams: { [key: string]: string | undefined }
searchParams: { [key: string]: string | string[] | undefined }
}) {
const { value } = searchParams
if (value === 'foo') {
Expand All @@ -138,7 +114,7 @@ Transforms into:
// page.tsx
export default function Page(props: {
params: { slug: string }
searchParams: { [key: string]: string | undefined }
searchParams: { [key: string]: string | string[] | undefined }
}) {
const { value } = await props.searchParams
if (value === 'foo') {
Expand Down Expand Up @@ -311,6 +287,32 @@ Transforms into:
import { Inter } from 'next/font/google'
```

### 13.1.2

#### Transform App Router Route Segment Config `runtime` value from `experimental-edge` to `edge`

##### `app-dir-runtime-config-experimental-edge`

> **Note**: This codemod is App Router specific.
```bash filename="Terminal"
npx @next/codemod@latest app-dir-runtime-config-experimental-edge .
```

This codemod transforms [Route Segment Config `runtime`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#runtime) value `experimental-edge` to `edge`.

For example:

```ts
export const runtime = 'experimental-edge'
```

Transforms into:

```ts
export const runtime = 'edge'
```

### 13.0

#### Rename Next Image Imports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export default async function Layout({
children: React.ReactNode
params: Params
}) {
const { slug } = await props
const { slug } = await params
}
```

Expand Down
2 changes: 1 addition & 1 deletion errors/sync-dynamic-apis.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Dynamic APIs are:
- The `params` and `searchParams` props that get provided to pages, layouts, metadata APIs, and route handlers.
- `cookies()`, `draftMode()`, and `headers()` from `next/headers`

In Next 15, these APIs have been made asynchronous. You can read more about this in the Next.js 15 [Upgrade Guide](/docs/app/building-your-application/upgrading/version-15)
In Next 15, these APIs have been made asynchronous. You can read more about this in the Next.js 15 [Upgrade Guide](/docs/app/building-your-application/upgrading/version-15).

For example, the following code will issue a warning:

Expand Down
12 changes: 6 additions & 6 deletions packages/next-codemod/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ export const TRANSFORMER_INQUIRER_CHOICES = [
value: 'next-image-to-legacy-image',
version: '13.0',
},
{
title:
'Transform App Router Route Segment Config `runtime` value from `experimental-edge` to `edge`',
value: 'app-dir-runtime-config-experimental-edge',
version: '13.1.2',
},
{
title: 'Uninstall `@next/font` and transform imports to `next/font`',
value: 'built-in-next-font',
Expand Down Expand Up @@ -116,10 +122,4 @@ export const TRANSFORMER_INQUIRER_CHOICES = [
value: 'next-async-request-api',
version: '15.0.0-canary.171',
},
{
title:
'Transform App Router Route Segment Config `runtime` value from `experimental-edge` to `edge`',
value: 'app-dir-runtime-config-experimental-edge',
version: '15.0.0-canary.179',
},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export async function GET(
req: NextRequest,
ctx: any,
): Promise<Response> {
callback(ctx.propDoesNotExist);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export async function GET(
req: NextRequest,
ctx: any,
): Promise<Response> {
callback(ctx.propDoesNotExist);
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ function awaitMemberAccessOfProp(
memberAccess.forEach((memberAccessPath) => {
const member = memberAccessPath.value

const memberProperty = member.property
const isAccessingMatchedProperty =
j.Identifier.check(memberProperty) &&
TARGET_PROP_NAMES.has(memberProperty.name)

if (!isAccessingMatchedProperty) {
return
}

if (isParentPromiseAllCallExpression(memberAccessPath, j)) {
return
}
Expand Down
2 changes: 2 additions & 0 deletions packages/next/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const cacheExports = {
unstable_noStore:
require('next/dist/server/web/spec-extension/unstable-no-store')
.unstable_noStore,
unstable_cacheLife: require('next/dist/server/use-cache/cache-life'),
unstable_cacheTag: require('next/dist/server/use-cache/cache-tag'),
}

// https://nodejs.org/api/esm.html#commonjs-namespaces
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/build/swc/generated-native.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ export interface NapiUpdateInfo {
}
/**
* Subscribes to lifecycle events of the compilation.
*
* Emits an [UpdateMessage::Start] event when any computation starts.
* Emits an [UpdateMessage::End] event when there was no computation for the
* specified time (`aggregation_ms`). The [UpdateMessage::End] event contains
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ export type PrerenderStore =
export type UseCacheStore = {
type: 'cache'
// Collected revalidate times and tags for this cache entry during the cache render.
revalidate: number // in seconds. INFINITE_CACHE and higher means never revalidate.
revalidate: number // implicit revalidate time from inner caches / fetches
explicitRevalidate: undefined | number // explicit revalidate time from cacheLife() calls
tags: null | string[]
}

Expand Down
143 changes: 143 additions & 0 deletions packages/next/src/server/use-cache/cache-life.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { workUnitAsyncStorage } from '../app-render/work-unit-async-storage.external'

export type CacheLife = {
// How long the client can cache a value without checking with the server.
stale?: number
// How frequently you want the cache to refresh on the server.
// Stale values may be served while revalidating.
revalidate?: number
// In the worst case scenario, where you haven't had traffic in a while,
// how stale can a value be until you prefer deopting to dynamic.
// Must be longer than revalidate.
expire?: number
}
// The equivalent header is kind of like:
// Cache-Control: max-age=[stale],s-max-age=[revalidate],stale-while-revalidate=[expire-revalidate],stale-if-error=[expire-revalidate]
// Except that stale-while-revalidate/stale-if-error only applies to shared caches - not private caches.

const cacheLifeProfileMap: Map<string, CacheLife> = new Map()

// The default revalidates relatively frequently but doesn't expire to ensure it's always
// able to serve fast results but by default doesn't hang.

export const defaultCacheLife = {
stale: Number(process.env.__NEXT_CLIENT_ROUTER_STATIC_STALETIME),
revalidate: 15 * 60, // Note: This is a new take on the defaults.
expire: Infinity,
}

cacheLifeProfileMap.set('default', defaultCacheLife)

type CacheLifeProfiles = 'default' // TODO: Generate from the config

function validateCacheLife(profile: CacheLife) {
if (profile.stale !== undefined) {
if ((profile.stale as any) === false) {
throw new Error(
'Pass `Infinity` instead of `false` if you want to cache on the client forever ' +
'without checking with the server.'
)
} else if (typeof profile.stale !== 'number') {
throw new Error('The stale option must be a number of seconds.')
}
}
if (profile.revalidate !== undefined) {
if ((profile.revalidate as any) === false) {
throw new Error(
'Pass `Infinity` instead of `false` if you do not want to revalidate by time.'
)
} else if (typeof profile.revalidate !== 'number') {
throw new Error('The revalidate option must be a number of seconds.')
}
}
if (profile.expire !== undefined) {
if ((profile.expire as any) === false) {
throw new Error(
'Pass `Infinity` instead of `false` if you want to cache on the client forever ' +
'without checking with the server.'
)
} else if (typeof profile.expire !== 'number') {
throw new Error('The expire option must be a number of seconds.')
}
}

if (profile.revalidate !== undefined && profile.expire !== undefined) {
if (profile.revalidate > profile.expire) {
throw new Error(
'If providing both the revalidate and expire options, ' +
'the expire option must be greater than the revalidate option.' +
'The expire option indicates how many seconds from the start ' +
'until it can no longer be used.'
)
}
}

if (profile.stale !== undefined && profile.expire !== undefined) {
if (profile.stale > profile.expire) {
throw new Error(
'If providing both the stale and expire options, ' +
'the expire option must be greater than the stale option.' +
'The expire option indicates how many seconds from the start ' +
'until it can no longer be used.'
)
}
}
}

export function cacheLife(profile: CacheLifeProfiles | CacheLife): void {
if (!process.env.__NEXT_DYNAMIC_IO) {
throw new Error(
'cacheLife() is only available with the experimental.dynamicIO config.'
)
}

const workUnitStore = workUnitAsyncStorage.getStore()
if (!workUnitStore || workUnitStore.type !== 'cache') {
throw new Error(
'cacheLife() can only be called inside a "use cache" function.'
)
}

if (typeof profile === 'string') {
const configuredProfile = cacheLifeProfileMap.get(profile)
if (configuredProfile === undefined) {
if (cacheLifeProfileMap.has(profile.trim())) {
throw new Error(
`Unknown cacheLife profile "${profile}" is not configured in next.config.js\n` +
`Did you mean "${profile.trim()}" without the spaces?`
)
}
throw new Error(
`Unknown cacheLife profile "${profile}" is not configured in next.config.js\n` +
'module.exports = {\n' +
' experimental: {\n' +
' cacheLife: {\n' +
` "${profile}": ...\n` +
' }\n' +
' }\n' +
'}'
)
}
profile = configuredProfile
} else if (
typeof profile !== 'object' ||
profile === null ||
Array.isArray(profile)
) {
throw new Error(
'Invalid cacheLife() option. Either pass a profile name or object.'
)
} else {
validateCacheLife(profile)
}

if (profile.revalidate !== undefined) {
// Track the explicit revalidate time.
if (
workUnitStore.explicitRevalidate === undefined ||
workUnitStore.explicitRevalidate > profile.revalidate
) {
workUnitStore.explicitRevalidate = profile.revalidate
}
}
}
25 changes: 25 additions & 0 deletions packages/next/src/server/use-cache/cache-tag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { workUnitAsyncStorage } from '../app-render/work-unit-async-storage.external'
import { validateTags } from '../lib/patch-fetch'

export function cacheTag(...tags: string[]): void {
if (!process.env.__NEXT_DYNAMIC_IO) {
throw new Error(
'cacheTag() is only available with the experimental.dynamicIO config.'
)
}

const workUnitStore = workUnitAsyncStorage.getStore()
if (!workUnitStore || workUnitStore.type !== 'cache') {
throw new Error(
'cacheTag() can only be called inside a "use cache" function.'
)
}

const validTags = validateTags(tags, 'cacheTag()')

if (!workUnitStore.tags) {
workUnitStore.tags = validTags
} else {
workUnitStore.tags.push(...validTags)
}
}
Loading

0 comments on commit bc4f701

Please sign in to comment.