Skip to content

Commit

Permalink
Add codemods for next/image (#41004)
Browse files Browse the repository at this point in the history
This adds two codemods for `next/image`.

The first codemod a safe solution to upgrade from 12 to 13 by swapping
to `next/legacy/image`.

The second codemod is an experimental solution that attempts to automate
the [migration
guide](https://nextjs.org/docs/api-reference/next/future/image#migration).
  • Loading branch information
styfle committed Oct 11, 2022
1 parent 09d642a commit c1de0c0
Show file tree
Hide file tree
Showing 12 changed files with 520 additions and 0 deletions.
49 changes: 49 additions & 0 deletions docs/advanced-features/codemods.md
Expand Up @@ -17,6 +17,55 @@ 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

### `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-image-experimental` (experimental)

Dangerously migrates from `next/legacy/image` to the new `next/image` by adding inline styles and removing unused props.

- Removes `layout` prop and adds `style`
- Removes `objectFit` prop and adds `style`
- Removes `objectPosition` prop and adds `style`
- Removes `lazyBoundary` prop
- Removes `lazyRoot` prop
- TODO: handle `loader`

## 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 safely and 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,33 @@
import Image from "next/legacy/image";
import Named, { Blarg } from "next/legacy/image";
import Foo from "foo";
import img from "../public/img.jpg";

export default function Home() {
const myStyle = { color: 'black' };
return (
<div>
<h1>Upgrade</h1>
<Image src="/test.jpg" width="200" height="300" />
<Image src="/test.jpg" width="200" height="300" layout="intrinsic" />
<Image src="/test.jpg" width="200" height="300" layout="responsive" />
<Image src="/test.jpg" width="200" height="300" layout="fixed" />
<div style={{ position: 'relative', width: '300px', height: '500px' }}>
<Image
alt="example alt text"
src={img}
layout="fill"
objectFit="contain"
/>
</div>
<Named src="/test.png" width="500" height="400" layout="fixed" />
<Foo bar="1" />
<Image src="/test.jpg" width="200" height="300" layout="responsive" style={{color: "green"}} />
<Image src="/test.jpg" width="200" height="300" layout="responsive" style={{color: myStyle.color}} />
<Image src="/test.jpg" width="200" height="300" layout="responsive" sizes="50vw" style={{color: "#fff"}} />
<Image src="/test.jpg" width="200" height="300" layout="fixed" lazyBoundary="1500px" lazyRoot={img} />
<Image src="/test.lit" width="200" height="300" layout="responsive" style={myStyle} />
<Image src="/test.dot" width="200" height="300" layout="responsive" style={{...myStyle}} />
</div>
);
}
@@ -0,0 +1,95 @@
import Image from "next/image";
import Named, { Blarg } from "next/image";
import Foo from "foo";
import img from "../public/img.jpg";

export default function Home() {
const myStyle = { color: 'black' };
return (
<div>
<h1>Upgrade</h1>
<Image src="/test.jpg" width="200" height="300" />
<Image
src="/test.jpg"
width="200"
height="300"
style={{
maxWidth: "100%",
height: "auto"
}} />
<Image
src="/test.jpg"
width="200"
height="300"
sizes="100vw"
style={{
width: "100%",
height: "auto"
}} />
<Image src="/test.jpg" width="200" height="300" />
<div style={{ position: 'relative', width: '300px', height: '500px' }}>
<Image
alt="example alt text"
src={img}
fill
sizes="100vw"
style={{
objectFit: "contain"
}} />
</div>
<Named src="/test.png" width="500" height="400" />
<Foo bar="1" />
<Image
src="/test.jpg"
width="200"
height="300"
sizes="100vw"
style={{
color: "green",
width: "100%",
height: "auto"
}} />
<Image
src="/test.jpg"
width="200"
height="300"
sizes="100vw"
style={{
color: myStyle.color,
width: "100%",
height: "auto"
}} />
<Image
src="/test.jpg"
width="200"
height="300"
sizes="50vw"
style={{
color: "#fff",
width: "100%",
height: "auto"
}} />
<Image src="/test.jpg" width="200" height="300" />
<Image
src="/test.lit"
width="200"
height="300"
sizes="100vw"
style={{
...myStyle,
width: "100%",
height: "auto"
}} />
<Image
src="/test.dot"
width="200"
height="300"
sizes="100vw"
style={{
...myStyle,
width: "100%",
height: "auto"
}} />
</div>
);
}
@@ -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-experimental'
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)
}
@@ -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)
}

0 comments on commit c1de0c0

Please sign in to comment.