Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codemod for Async Request API #69572

Merged
merged 46 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
4b521da
Transform cookies for sync and async migratable cases
huozhi Sep 2, 2024
a49711b
rm if cond check
huozhi Sep 4, 2024
6722ee1
rm cond
huozhi Sep 5, 2024
ca294c4
skip client
huozhi Sep 10, 2024
6570c2e
wip params and search params
huozhi Sep 10, 2024
830d1ec
rename properties
huozhi Sep 11, 2024
99e74ac
transform search params
huozhi Sep 11, 2024
8c95ab6
reogrnaize, merge two transform
huozhi Sep 11, 2024
230ec3f
support typescript param and searchParam
huozhi Sep 12, 2024
948230d
rename to props
huozhi Sep 12, 2024
b929bea
insert imports
huozhi Sep 12, 2024
3785aa1
format
huozhi Sep 13, 2024
79ec53d
skip local api calls
huozhi Sep 13, 2024
3aa7855
convert recent export default to async if possible
huozhi Sep 13, 2024
5cea1fd
add hook test
huozhi Sep 13, 2024
66d3ec2
handle generateMetadata
huozhi Sep 17, 2024
53a36c1
handle props types defined somewhere else
huozhi Sep 18, 2024
cf48031
handle cases are not able to transform
huozhi Sep 18, 2024
ec7b2ac
do not apply if possible
huozhi Sep 18, 2024
c62981f
secure transform func
huozhi Sep 18, 2024
1295221
handle route.js transform
huozhi Sep 19, 2024
3402568
require entry file
huozhi Sep 19, 2024
f964b11
reorg files
huozhi Sep 19, 2024
21ae0bf
skip types
huozhi Sep 19, 2024
7c84196
avoid duplicates
huozhi Sep 19, 2024
a8ad7ba
fix cookies type name and fix missing await case
huozhi Sep 19, 2024
f2c4992
handle destruction
huozhi Sep 19, 2024
5c68f58
convert return type
huozhi Sep 19, 2024
b2a1bff
rename UnsafeUnwrapped
huozhi Sep 19, 2024
c4d446b
insert helper per function
huozhi Sep 19, 2024
e293623
add return type case
huozhi Sep 19, 2024
1b89229
transform the decl.func return type instead of path
huozhi Sep 19, 2024
3c61a1b
fix transform multi arg types
huozhi Sep 19, 2024
b97c1fb
avoid inserting duplicated types
huozhi Sep 19, 2024
e6d50c6
format
huozhi Sep 20, 2024
60c89bd
prettier input
huozhi Sep 20, 2024
089017f
update comment
huozhi Sep 20, 2024
db8f04b
prettier more
huozhi Sep 20, 2024
9a54831
move folder to include the assets in files
huozhi Sep 20, 2024
c9b1b60
reorganize tests
huozhi Sep 20, 2024
a06013d
fix await calls under arrow function
huozhi Sep 20, 2024
2676c4f
unique identifier
huozhi Sep 23, 2024
7a75a8e
rename tests
huozhi Sep 23, 2024
53f999f
handle import specifiers
huozhi Sep 23, 2024
daf9d4f
Merge branch 'canary' into 09-02-codemod_exotic_cookie
huozhi Sep 23, 2024
be4f882
add docs
huozhi Sep 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions docs/02-app/01-building-your-application/11-upgrading/01-codemods.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,108 @@ Replacing `<transform>` and `<path>` with appropriate values.

### 15.0

#### Migrate to async Dynamic APIs

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

```bash filename="Terminal"
npx @next/codemod@latest next-async-request-api .
```

This codemod will transform APIs `cookies()`, `headers()` and `draftMode()` from `next/headers` to be properly awaited or wrapped with `React.use()` if needed.
If it's not able to be migrated, will either add a type cast or a TODO comment based on the file is a TypeScript or JavaScript file.

For example:

```tsx
import { cookies, headers } from 'next/headers'
const token = cookies().get('token')

function useToken() {
const token = cookies().get('token')
return token
}

export default function Page() {
const name = cookies().get('name')
}

function getHeader() {
return headers().get('x-foo')
}
```

Transforms into:

```tsx
import { use } from 'react'
import { cookies, headers, type UnsafeUnwrappedCookies } from 'next/headers'

const token = (await cookies()).get('token')

function useToken() {
const token = use(cookies()).get('token')
return token
}

export default function Page() {
const name = (await cookies()).get('name')
}

function getHeader() {
return (headers() as UnsafeUnwrappedCookies).get('x-foo')
}
```

If there's access to the entry handler `params` and `searchParams` in the route entry default handler (`page.js`, `layout.js`, `route.js` or Metadata API routes) or `generateMetadata` export API,
it will transform the access to the property from sync to async, and await or wrap these properties with `React.use()` if needed.

For example:

```tsx
// page.tsx
export default function Page({
params,
searchParams,
}: {
params: { slug: string }
searchParams: { [key: string]: string | undefined }
}) {
const { value } = searchParams
if (value === 'foo') {
// ...
}
}

export function generateMetadata({ params }: { params: { slug: string } }) {
return {
title: `My Page - ${slug}`,
}
}
```

Transforms into:

```tsx
// page.tsx
export default function Page(props: {
params: { slug: string }
searchParams: { [key: string]: string | undefined }
}) {
const { value } = await props.searchParams
if (value === 'foo') {
// ...
}
}

export function generateMetadata(props: { params: { slug: string } }) {
const { slug } = await props.params
return {
title: `My Page - ${slug}`,
}
}
```

#### Replace `geo` and `ip` properties of `NextRequest` with `@vercel/functions`

##### `next-request-geo-ip`
Expand Down
4 changes: 4 additions & 0 deletions packages/next-codemod/bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ const TRANSFORMER_INQUIRER_CHOICES = [
name: 'built-in-next-font: Uninstall `@next/font` and transform imports to `next/font`',
value: 'built-in-next-font',
},
{
name: 'next-async-request-api: Transforms usage of Next.js async Request APIs',
value: 'next-async-request-api',
},
{
name: 'next-request-geo-ip: Install `@vercel/functions` to replace `geo` and `ip` properties on `NextRequest`',
value: 'next-request-geo-ip',
Expand Down
1 change: 1 addition & 0 deletions packages/next-codemod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"files": [
"transforms/*.js",
"transforms/lib/**/*.js",
"bin/*.js",
"lib/**/*.js",
"lib/cra-to-next/gitignore"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { cookies } from 'next/headers'

export async function MyComponent() {
const name = cookies().get('name')
callback(name)
}

function callback(name: any) {
console.log(name)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { cookies } from 'next/headers'

export async function MyComponent() {
const name = (await cookies()).get('name')
callback(name)
}

function callback(name: any) {
console.log(name)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { draftMode } from 'next/headers'

export async function MyComponent() {
draftMode().enable()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { draftMode } from 'next/headers'

export async function MyComponent() {
(await draftMode()).enable()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// If it's sync default export, convert to async and await the function call
import { draftMode } from 'next/headers'

export default function MyComponent() {
draftMode().enable()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// If it's sync default export, convert to async and await the function call
import { draftMode } from 'next/headers'

export default async function MyComponent() {
(await draftMode()).enable()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { draftMode } from 'next/headers'

export default async function MyComponent() {
draftMode().enable()
}

export async function MyComponent2() {
draftMode().enable()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { draftMode } from 'next/headers'

export default async function MyComponent() {
(await draftMode()).enable()
}

export async function MyComponent2() {
(await draftMode()).enable()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { draftMode } from 'next/headers'

export function MyComponent2() {
draftMode().enable()
}

export function useDraftModeEnabled() {
draftMode().enable()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { use } from "react";
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers';

export function MyComponent2() {
(draftMode() as unknown as UnsafeUnwrappedDraftMode).enable()
}

export function useDraftModeEnabled() {
use(draftMode()).enable()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react'
import { draftMode } from 'next/headers'

export default function Page() {
return <button disabled={draftMode().isEnabled}>Enable Draft Mode</button>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react'
import { draftMode } from 'next/headers'

export default async function Page() {
return <button disabled={(await draftMode()).isEnabled}>Enable Draft Mode</button>;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Already imported the type
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'

export function MyComponent2() {
draftMode().enable()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Already imported the type
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'

export function MyComponent2() {
(draftMode() as unknown as UnsafeUnwrappedDraftMode).enable()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { headers } from 'next/headers'

export function GET(): Response {
headers()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { headers } from 'next/headers'

export async function GET(): Promise<Response> {
await headers()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default function Page({
params: { slug },
searchParams: { search },
}: {
params: { slug: string }
searchParams: any
}): JSX.Element {
// Access to the destructed properties
slug
search

return <div>Page</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export default async function Page(
props: {
params: Promise<{ slug: string }>
searchParams: Promise<any>
}
): Promise<JSX.Element> {
const searchParams = await props.searchParams;

const {
search
} = searchParams;

const params = await props.params;

const {
slug
} = params;

// Access to the destructed properties
slug
search

return <div>Page</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { headers } from 'next/headers'

export function MyComp() {
return headers()
}

export function MyComp2() {
return headers()
}

export function MyComp3() {
return headers()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers';

export function MyComp() {
return (headers() as unknown as UnsafeUnwrappedHeaders);
}

export function MyComp2() {
return (headers() as unknown as UnsafeUnwrappedHeaders);
}

export function MyComp3() {
return (headers() as unknown as UnsafeUnwrappedHeaders);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { headers } from 'next/headers'

export function MyComp() {
headers()
}

export function generateContentfulMetadata() {
headers()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers';

export function MyComp() {
(headers() as unknown as UnsafeUnwrappedHeaders)
}

export function generateContentfulMetadata() {
(headers() as unknown as UnsafeUnwrappedHeaders)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { cookies } from "next/headers";

async function MyComponent() {
callSomething(cookies());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { cookies } from "next/headers";

async function MyComponent() {
callSomething(await cookies());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { headers } from "next/headers";

async function MyComponent() {
callSomething(headers());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { headers } from "next/headers";

async function MyComponent() {
callSomething(await headers());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { cookies } from "next/headers";

export default async function Page() {
callSomething(cookies());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { cookies } from "next/headers";

export default async function Page() {
callSomething(await cookies());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { cookies } from "next/headers";

async function MyComponent() {
function asyncFunction() {
callSomething(cookies());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { cookies } from "next/headers";

async function MyComponent() {
function asyncFunction() {
callSomething(/* TODO: please manually await this call, codemod cannot transform due to undetermined async scope */
cookies());
}
}
Loading
Loading