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

Add codemods for next/image #41004

Merged
merged 21 commits into from Oct 11, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 38 additions & 0 deletions docs/advanced-features/codemods.md
Expand Up @@ -17,6 +17,44 @@ Codemods are transformations that run on your codebase programmatically. This al
- `--dry` Do a dry-run, no code will be edited
- `--print` Prints the changed output for comparison

## Next.js 13
balazsorban44 marked this conversation as resolved.
Show resolved Hide resolved

### `next-image-to-legacy-image`

Safely migrates existing Next.js 10, 11, 12 applications importing `next/image` to the renamed `next/legacy/image` import in Next.js 13.

For example:

```jsx
import Image1 from 'next/image'
import Image2 from 'next/future/image'

export default function Home() {
return (
<div>
<Image1 src="/test.jpg" width="200" height="300" />
<Image2 src="/test.png" width="500" height="400" />
</div>
)
}
```

Transforms into:

```jsx
import Image1 from 'next/legacy/image'
import Image2 from 'next/image'

export default function Home() {
return (
<div>
<Image1 src="/test.jpg" width="200" height="300" />
<Image2 src="/test.png" width="500" height="400" />
</div>
)
}
```

## Next.js 11

### `cra-to-next` (experimental)
Expand Down
5 changes: 5 additions & 0 deletions docs/upgrading.md
Expand Up @@ -4,6 +4,11 @@ description: Learn how to upgrade Next.js.

# Upgrade Guide

## Upgrading from 12 to 13

The `next/image` import was renamed to `next/legacy/image`. The `next/future/image` import was renamed to `next/image`.
A [codemod is available](/docs/advanced-features/codemods.md#next-image-to-legacy-image) to automatically rename your imports.

## Upgrading to 12.2

If you were using Middleware prior to `12.2`, please see the [upgrade guide](https://nextjs.org/docs/messages/middleware-upgrade-guide) for more information.
Expand Down
@@ -0,0 +1,14 @@
export async function Home() {
const Image = await import("next/image");
const Named = await import("next/image");
const Foo = await import("foo");
const Future1 = await import("next/future/image");
const Future2 = await import("next/future/image");
return (<div>
<h1>Both</h1>
<Image src="/test.jpg" width="200" height="300" />
<Named src="/test.png" width="500" height="400" />
<Future1 src="/test.webp" width="60" height="70" />
<Future2 src="/test.avif" width="80" height="90" />
</div>)
}
@@ -0,0 +1,14 @@
export async function Home() {
const Image = await import("next/legacy/image");
const Named = await import("next/legacy/image");
const Foo = await import("foo");
const Future1 = await import("next/image");
const Future2 = await import("next/image");
return (<div>
<h1>Both</h1>
<Image src="/test.jpg" width="200" height="300" />
<Named src="/test.png" width="500" height="400" />
<Future1 src="/test.webp" width="60" height="70" />
<Future2 src="/test.avif" width="80" height="90" />
</div>)
}
@@ -0,0 +1,15 @@
import Image from "next/image";
import Named from "next/image";
import Foo from "foo";
import Future1 from "next/future/image";
import Future2 from "next/future/image";

export default function Home() {
return (<div>
<h1>Both</h1>
<Image src="/test.jpg" width="200" height="300" />
<Named src="/test.png" width="500" height="400" />
<Future1 src="/test.webp" width="60" height="70" />
<Future2 src="/test.avif" width="80" height="90" />
</div>)
}
@@ -0,0 +1,15 @@
import Image from "next/legacy/image";
import Named from "next/legacy/image";
import Foo from "foo";
import Future1 from "next/image";
import Future2 from "next/image";

export default function Home() {
return (<div>
<h1>Both</h1>
<Image src="/test.jpg" width="200" height="300" />
<Named src="/test.png" width="500" height="400" />
<Future1 src="/test.webp" width="60" height="70" />
<Future2 src="/test.avif" width="80" height="90" />
</div>)
}
@@ -0,0 +1,16 @@
/* global jest */
jest.autoMockOff()
const defineTest = require('jscodeshift/dist/testUtils').defineTest
const { readdirSync } = require('fs')
const { join } = require('path')

const fixtureDir = 'next-image-to-legacy-image'
const fixtureDirPath = join(__dirname, '..', '__testfixtures__', fixtureDir)
const fixtures = readdirSync(fixtureDirPath)
.filter(file => file.endsWith('.input.js'))
.map(file => file.replace('.input.js', ''))

for (const fixture of fixtures) {
const prefix = `${fixtureDir}/${fixture}`;
defineTest(__dirname, fixtureDir, null, prefix)
}
68 changes: 68 additions & 0 deletions packages/next-codemod/transforms/next-image-to-legacy-image.ts
@@ -0,0 +1,68 @@
import type { API, FileInfo, Options } from 'jscodeshift'

export default function transformer(
file: FileInfo,
api: API,
options: Options
) {
const j = api.jscodeshift
const root = j(file.source)

// Before: import Image from "next/image"
// After: import Image from "next/legacy/image"
root
.find(j.ImportDeclaration, {
source: { value: 'next/image' },
})
.forEach((imageImport) => {
j(imageImport).replaceWith(
j.importDeclaration(
imageImport.node.specifiers,
j.stringLiteral('next/legacy/image')
)
)
})
// Before: const Image = await import("next/image")
// After: const Image = await import("next/legacy/image")
root
.find(j.ImportExpression, {
source: { value: 'next/image' },
})
.forEach((imageImport) => {
j(imageImport).replaceWith(
j.importExpression(j.stringLiteral('next/legacy/image'))
)
})

// Before: import Image from "next/future/image"
// After: import Image from "next/image"
root
.find(j.ImportDeclaration, {
source: { value: 'next/future/image' },
})
.forEach((imageFutureImport) => {
j(imageFutureImport).replaceWith(
j.importDeclaration(
imageFutureImport.node.specifiers,
j.stringLiteral('next/image')
)
)
})

// Before: const Image = await import("next/future/image")
// After: const Image = await import("next/image")
root
.find(j.ImportExpression, {
source: { value: 'next/future/image' },
})
.forEach((imageFutureImport) => {
j(imageFutureImport).replaceWith(
j.importExpression(j.stringLiteral('next/image'))
)
})

// Learn more about renaming an import declaration here:
// https://www.codeshiftcommunity.com/docs/import-manipulation/#replacerename-an-import-declaration

return root.toSource(options)
}