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

Support for custom image loaders via image component prop #20216

Merged
merged 24 commits into from
Jan 5, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c78462b
support custom image component url resolvers
atcastle Nov 18, 2020
86e51ed
Merge remote-tracking branch 'origin/canary' into image-component-cus…
atcastle Nov 18, 2020
d464057
Fix inccorect conditional
atcastle Nov 19, 2020
a68391b
Add tests for custom resolver errors
atcastle Nov 19, 2020
83d531f
Add support for resolver alternative to loader property
atcastle Nov 20, 2020
6c3234a
Merge remote-tracking branch 'origin/canary' into image-component-cus…
atcastle Nov 20, 2020
f0e7dc0
Rename registerCustomResolver to registerCustomImageLoader
atcastle Dec 2, 2020
4034c6e
Change to use loader prop instead of _app.js
atcastle Dec 15, 2020
8728227
Merge remote-tracking branch 'origin/canary' into image-component-loa…
atcastle Dec 15, 2020
b98d2d6
Revert obsolete changes
atcastle Dec 15, 2020
e2ade38
One more image-config reversion
atcastle Dec 15, 2020
750e0dc
Update packages/next/client/image.tsx
atcastle Dec 15, 2020
ed8de54
remove redundant 'default' setting
atcastle Dec 15, 2020
cce179e
Merge branch 'image-component-loader-prop' of github.com:azukaru/next…
atcastle Dec 15, 2020
531efe8
Adopt ricokahler's suggestions for refactoring custom loader logic
atcastle Dec 16, 2020
f9cabd2
Merge remote-tracking branch 'origin/canary' into image-component-loa…
atcastle Jan 4, 2021
5301504
Remove support for string identification of built-in loaders
atcastle Jan 4, 2021
7bce34c
Clean up obsolete config destructuring change in image.tsx
atcastle Jan 4, 2021
dc2b6ac
Remove redundant check for invalid default loader
atcastle Jan 4, 2021
60a0415
Export type definition for image loader props
atcastle Jan 4, 2021
d126aad
Fixed mistakenly removed loading prop validation for image component
atcastle Jan 5, 2021
ebb20b8
Update packages/next/client/image.tsx
atcastle Jan 5, 2021
4afaeb5
Add default loaderr validation back into image.tsx
atcastle Jan 5, 2021
5c1563b
Merge branch 'canary' into image-component-loader-prop
Timer Jan 5, 2021
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
22 changes: 16 additions & 6 deletions packages/next/client/image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type ImageProps = Omit<
'src' | 'srcSet' | 'ref' | 'width' | 'height' | 'loading' | 'style'
> & {
src: string
loader?: URLResolver
quality?: number | string
priority?: boolean
loading?: LoadingValue
Expand All @@ -59,14 +60,15 @@ export type ImageProps = Omit<
}
)

const config =
((process.env.__NEXT_IMAGE_OPTS as any) as ImageConfig) || imageConfigDefault
const configLoader = config.loader || 'default'
atcastle marked this conversation as resolved.
Show resolved Hide resolved
const {
deviceSizes: configDeviceSizes,
imageSizes: configImageSizes,
loader: configLoader,
path: configPath,
domains: configDomains,
} =
((process.env.__NEXT_IMAGE_OPTS as any) as ImageConfig) || imageConfigDefault
} = config
// sort smallest to largest
const allSizes = [...configDeviceSizes, ...configImageSizes]
configDeviceSizes.sort((a, b) => a - b)
Expand Down Expand Up @@ -99,8 +101,12 @@ type CallLoaderProps = {
width: number
quality?: number
}
type URLResolver = (resolverProps: CallLoaderProps) => string
atcastle marked this conversation as resolved.
Show resolved Hide resolved

function callLoader(loaderProps: CallLoaderProps) {
function callLoader(loaderProps: CallLoaderProps, loader?: URLResolver) {
atcastle marked this conversation as resolved.
Show resolved Hide resolved
if (loader) {
return loader(loaderProps)
}
const load = loaders.get(configLoader)
if (load) {
return load({ root: configPath, ...loaderProps })
Expand All @@ -119,6 +125,7 @@ type GenImgAttrsData = {
width?: number
quality?: number
sizes?: string
loader?: URLResolver
styfle marked this conversation as resolved.
Show resolved Hide resolved
}

type GenImgAttrsResult = Pick<
Expand All @@ -133,6 +140,7 @@ function generateImgAttrs({
width,
quality,
sizes,
loader,
styfle marked this conversation as resolved.
Show resolved Hide resolved
}: GenImgAttrsData): GenImgAttrsResult {
if (unoptimized) {
return { src }
Expand All @@ -144,7 +152,7 @@ function generateImgAttrs({
const srcSet = widths
.map(
(w, i) =>
`${callLoader({ src, quality, width: w })} ${
`${callLoader({ src, quality, width: w }, loader)} ${
atcastle marked this conversation as resolved.
Show resolved Hide resolved
kind === 'w' ? w : i + 1
}${kind}`
)
Expand All @@ -154,7 +162,7 @@ function generateImgAttrs({
sizes = '100vw'
}

src = callLoader({ src, quality, width: widths[last] })
src = callLoader({ src, quality, width: widths[last] }, loader)

return { src, sizes, srcSet }
atcastle marked this conversation as resolved.
Show resolved Hide resolved
}
Expand All @@ -181,6 +189,7 @@ export default function Image({
height,
objectFit,
objectPosition,
loader,
...all
}: ImageProps) {
let rest: Partial<ImageProps> = all
Expand Down Expand Up @@ -365,6 +374,7 @@ export default function Image({
width: widthInt,
quality: qualityInt,
sizes,
loader,
})
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
images: {
deviceSizes: [480, 1024, 1600, 2000],
imageSizes: [16, 32, 48, 64],
path: 'https://globalresolver.com/myaccount/',
loader: 'imgix',
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react'
import Image from 'next/image'

const myLoader = ({ src, width, quality }) => {
return `https://customresolver.com/${src}?w~~${width},q~~${quality}`
}

const MyImage = (props) => {
return <Image loader={myLoader} {...props}></Image>
}

const Page = () => {
return (
<div>
<p>Image Client Side Test</p>
<MyImage
id="basic-image"
src="foo.jpg"
loading="eager"
width={300}
height={400}
quality={60}
/>
<Image
id="unoptimized-image"
unoptimized
src="https://arbitraryurl.com/foo.jpg"
loading="eager"
width={300}
height={400}
/>
</div>
)
}

export default Page
40 changes: 40 additions & 0 deletions test/integration/image-component/custom-resolver/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react'
import Image from 'next/image'
import Link from 'next/link'

const myLoader = ({ src, width, quality }) => {
return `https://customresolver.com/${src}?w~~${width},q~~${quality}`
}

const MyImage = (props) => {
return <Image loader={myLoader} {...props}></Image>
}

const Page = () => {
return (
<div>
<p>Image SSR Test</p>
<MyImage
id="basic-image"
src="foo.jpg"
loading="eager"
width={300}
height={400}
quality={60}
/>
<Image
id="unoptimized-image"
unoptimized
src="https://arbitraryurl.com/foo.jpg"
loading="eager"
width={300}
height={400}
/>
<Link href="/client-side">
<a id="clientlink">Client Side</a>
</Link>
</div>
)
}

export default Page
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* eslint-env jest */

import { join } from 'path'
import { killApp, findPort, nextStart, nextBuild } from 'next-test-utils'
import webdriver from 'next-webdriver'

jest.setTimeout(1000 * 30)

const appDir = join(__dirname, '../')
let appPort
let app
let browser

function runTests() {
it('Should use a custom resolver for image URL', async () => {
expect(await browser.elementById('basic-image').getAttribute('src')).toBe(
'https://customresolver.com/foo.jpg?w~~1024,q~~60'
)
})
it('should add a srcset based on the custom resolver', async () => {
expect(
await browser.elementById('basic-image').getAttribute('srcset')
).toBe(
'https://customresolver.com/foo.jpg?w~~480,q~~60 1x, https://customresolver.com/foo.jpg?w~~1024,q~~60 2x'
)
})
it('should support the unoptimized attribute', async () => {
expect(
await browser.elementById('unoptimized-image').getAttribute('src')
).toBe('https://arbitraryurl.com/foo.jpg')
})
}

describe('Custom Resolver Tests', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
describe('SSR Custom Loader Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
})
afterAll(async () => {
browser = null
})
runTests()
})
describe('Client-side Custom Loader Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#clientlink').click()
})
afterAll(async () => {
browser = null
})
runTests()
})
})