未验证 提交 0fb39e23 编写于 作者: S Steven 提交者: GitHub

Add `width` and `height` props to Image component (#18031)

Co-authored-by: NTim Neutkens <timneutkens@me.com>
上级 01e6bd16
......@@ -229,13 +229,6 @@ export default async function getBaseWebpackConfig(
}
}
// Normalize defined image host to end in slash
if (config.images?.path) {
if (config.images.path[config.images.path.length - 1] !== '/') {
config.images.path += '/'
}
}
const reactVersion = await getPackageVersion({ cwd: dir, name: 'react' })
const hasReactRefresh: boolean = dev && !isServer
const hasJsxRuntime: boolean =
......@@ -997,7 +990,12 @@ export default async function getBaseWebpackConfig(
'process.env.__NEXT_SCROLL_RESTORATION': JSON.stringify(
config.experimental.scrollRestoration
),
'process.env.__NEXT_IMAGE_OPTS': JSON.stringify(config.images),
'process.env.__NEXT_IMAGE_OPTS': JSON.stringify({
sizes: config.images.sizes,
path: config.images.path,
loader: config.images.loader,
autoOptimize: config.images.autoOptimize,
}),
'process.env.__NEXT_ROUTER_BASEPATH': JSON.stringify(config.basePath),
'process.env.__NEXT_HAS_REWRITES': JSON.stringify(hasRewrites),
'process.env.__NEXT_I18N_SUPPORT': JSON.stringify(
......
......@@ -9,6 +9,9 @@ const loaders: { [key: string]: (props: LoaderProps) => string } = {
type ImageData = {
sizes?: number[]
loader?: string
path?: string
autoOptimize?: boolean
}
type ImageProps = Omit<
......@@ -16,13 +19,15 @@ type ImageProps = Omit<
'src' | 'srcSet' | 'ref'
> & {
src: string
width: number
height: number
quality?: string
priority?: boolean
lazy?: boolean
unoptimized?: boolean
}
let imageData: any = process.env.__NEXT_IMAGE_OPTS
const imageData: ImageData = process.env.__NEXT_IMAGE_OPTS as any
const breakpoints = imageData.sizes || [640, 1024, 1600]
// Auto optimize defaults to on if not specified
if (imageData.autoOptimize === undefined) {
......@@ -83,7 +88,7 @@ type CallLoaderProps = {
function callLoader(loaderProps: CallLoaderProps) {
let loader = loaders[imageData.loader || 'default']
return loader({ root: imageData.path, ...loaderProps })
return loader({ root: imageData.path || '/_next/image', ...loaderProps })
}
type SrcSetData = {
......@@ -136,6 +141,8 @@ function generatePreload({
export default function Image({
src,
sizes,
width,
height,
unoptimized = false,
priority = false,
lazy = false,
......@@ -156,11 +163,6 @@ export default function Image({
lazy = false
}
// Normalize provided src
if (src[0] === '/') {
src = src.slice(1)
}
useEffect(() => {
const target = thisEl.current
......@@ -217,8 +219,11 @@ export default function Image({
// it's too late for preloads
const shouldPreload = priority && typeof window === 'undefined'
const ratio = (height / width) * 100
const paddingBottom = `${isNaN(ratio) ? 1 : ratio}%`
return (
<div>
<div style={{ position: 'relative', paddingBottom }}>
{shouldPreload
? generatePreload({
src,
......@@ -233,6 +238,13 @@ export default function Image({
className={className}
sizes={sizes}
ref={thisEl}
style={{
height: '100%',
left: '0',
position: 'absolute',
top: '0',
width: '100%',
}}
/>
</div>
)
......@@ -242,6 +254,10 @@ export default function Image({
type LoaderProps = CallLoaderProps & { root: string }
function normalizeSrc(src: string) {
return src[0] === '/' ? src.slice(1) : src
}
function imgixLoader({ root, src, width, quality }: LoaderProps): string {
const params = []
let paramsString = ''
......@@ -257,7 +273,7 @@ function imgixLoader({ root, src, width, quality }: LoaderProps): string {
if (params.length) {
paramsString = '?' + params.join('&')
}
return `${root}${src}${paramsString}`
return `${root}${normalizeSrc(src)}${paramsString}`
}
function cloudinaryLoader({ root, src, width, quality }: LoaderProps): string {
......@@ -275,7 +291,7 @@ function cloudinaryLoader({ root, src, width, quality }: LoaderProps): string {
if (params.length) {
paramsString = params.join(',') + '/'
}
return `${root}${paramsString}${src}`
return `${root}${paramsString}${normalizeSrc(src)}`
}
function defaultLoader({ root, src, width, quality }: LoaderProps): string {
......
......@@ -215,6 +215,14 @@ function assignDefaults(userConfig: { [key: string]: any }) {
if (result?.images) {
const { images } = result
// Normalize defined image host to end in slash
if (images?.path) {
if (images.path[images.path.length - 1] !== '/') {
images.path += '/'
}
}
if (typeof images !== 'object') {
throw new Error(
`Specified images should be an object received ${typeof images}`
......
......@@ -6,21 +6,49 @@ const ClientSide = () => {
return (
<div>
<p id="stubtext">This is a client side page</p>
<Image id="basic-image" src="foo.jpg" quality="60"></Image>
<Image id="attribute-test" data-demo="demo-value" src="bar.jpg" />
<Image
id="basic-image"
src="foo.jpg"
width={300}
height={400}
quality={60}
></Image>
<Image
id="attribute-test"
data-demo="demo-value"
src="bar.jpg"
width={300}
height={400}
/>
<Image
id="secondary-image"
data-demo="demo-value"
host="secondary"
src="foo2.jpg"
width={300}
height={400}
/>
<Image
id="unoptimized-image"
unoptimized
src="https://arbitraryurl.com/foo.jpg"
width={300}
height={400}
/>
<Image
id="priority-image-client"
priority
src="withpriorityclient.png"
width={300}
height={400}
/>
<Image
id="preceding-slash-image"
src="/fooslash.jpg"
priority
width={300}
height={400}
/>
<Image id="priority-image-client" priority src="withpriorityclient.png" />
<Image id="preceding-slash-image" src="/fooslash.jpg" priority />
<Link href="/errors">
<a id="errorslink">Errors</a>
</Link>
......
......@@ -5,7 +5,13 @@ const Errors = () => {
return (
<div>
<p id="stubtext">This is a page with errors</p>
<Image id="nonexistant-host" host="nope" src="wronghost.jpg"></Image>
<Image
id="nonexistant-host"
host="nope"
src="wronghost.jpg"
width={300}
height={400}
></Image>
</div>
)
}
......
......@@ -6,18 +6,34 @@ const Page = () => {
return (
<div>
<p>Hello World</p>
<Image id="basic-image" src="foo.jpg" quality="60"></Image>
<Image id="attribute-test" data-demo="demo-value" src="bar.jpg" />
<Image
id="basic-image"
src="foo.jpg"
width={300}
height={400}
quality={60}
></Image>
<Image
id="attribute-test"
data-demo="demo-value"
src="bar.jpg"
width={300}
height={400}
/>
<Image
id="secondary-image"
data-demo="demo-value"
host="secondary"
src="foo2.jpg"
width={300}
height={400}
/>
<Image
id="unoptimized-image"
unoptimized
src="https://arbitraryurl.com/foo.jpg"
width={300}
height={400}
/>
<Image id="priority-image" priority src="withpriority.png" />
<Image
......@@ -25,14 +41,24 @@ const Page = () => {
priority
host="secondary"
src="withpriority2.png"
width={300}
height={400}
/>
<Image
id="priority-image"
priority
unoptimized
src="https://arbitraryurl.com/withpriority3.png"
width={300}
height={400}
/>
<Image
id="preceding-slash-image"
src="/fooslash.jpg"
priority
width={300}
height={400}
/>
<Image id="preceding-slash-image" src="/fooslash.jpg" priority />
<Link href="/client-side">
<a id="clientlink">Client Side</a>
</Link>
......
......@@ -5,28 +5,22 @@ const Lazy = () => {
return (
<div>
<p id="stubtext">This is a page with lazy-loaded images</p>
<Image
id="lazy-top"
src="foo1.jpg"
height="400px"
width="300px"
lazy
></Image>
<Image id="lazy-top" src="foo1.jpg" height={400} width={300} lazy></Image>
<div style={{ height: '2000px' }}></div>
<Image
id="lazy-mid"
src="foo2.jpg"
lazy
height="400px"
width="300px"
height={400}
width={300}
className="exampleclass"
></Image>
<div style={{ height: '2000px' }}></div>
<Image
id="lazy-bottom"
src="https://www.otherhost.com/foo3.jpg"
height="400px"
width="300px"
height={400}
width={300}
unoptimized
lazy
></Image>
......
......@@ -7,6 +7,7 @@ import {
nextStart,
nextBuild,
waitFor,
check,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
......@@ -91,19 +92,20 @@ function lazyLoadingTests() {
it('should load the second image after scrolling down', async () => {
let viewportHeight = await browser.eval(`window.innerHeight`)
let topOfMidImage = await browser.eval(
`document.getElementById('lazy-mid').offsetTop`
`document.getElementById('lazy-mid').parentElement.offsetTop`
)
let buffer = 150
await browser.eval(
`window.scrollTo(0, ${topOfMidImage - (viewportHeight + buffer)})`
)
await waitFor(200)
expect(await browser.elementById('lazy-mid').getAttribute('src')).toBe(
'https://example.com/myaccount/foo2.jpg'
)
expect(await browser.elementById('lazy-mid').getAttribute('srcset')).toBe(
'https://example.com/myaccount/foo2.jpg?w=480 480w, https://example.com/myaccount/foo2.jpg?w=1024 1024w, https://example.com/myaccount/foo2.jpg?w=1600 1600w'
)
await check(() => {
return browser.elementById('lazy-mid').getAttribute('src')
}, 'https://example.com/myaccount/foo2.jpg')
await check(() => {
return browser.elementById('lazy-mid').getAttribute('srcset')
}, 'https://example.com/myaccount/foo2.jpg?w=480 480w, https://example.com/myaccount/foo2.jpg?w=1024 1024w, https://example.com/myaccount/foo2.jpg?w=1600 1600w')
})
it('should not have loaded the third image after scrolling down', async () => {
expect(
......@@ -116,7 +118,7 @@ function lazyLoadingTests() {
it('should load the third image, which is unoptimized, after scrolling further down', async () => {
let viewportHeight = await browser.eval(`window.innerHeight`)
let topOfBottomImage = await browser.eval(
`document.getElementById('lazy-bottom').offsetTop`
`document.getElementById('lazy-bottom').parentElement.offsetTop`
)
let buffer = 150
await browser.eval(
......
import React from 'react'
import Image from 'next/image'
const Page = () => {
return (
<div>
<p>Hello World</p>
<Image id="basic-image" src="/test.jpg" width={400} height={400}></Image>
<p id="stubtext">This is the index page</p>
</div>
)
}
export default Page
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="400.000000pt" height="400.000000pt" viewBox="0 0 400.000000 400.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,400.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M0 2000 l0 -2000 2000 0 2000 0 0 2000 0 2000 -2000 0 -2000 0 0
-2000z m2401 118 l396 -693 -398 -3 c-220 -1 -578 -1 -798 0 l-398 3 396 693
c217 380 398 692 401 692 3 0 184 -312 401 -692z"/>
</g>
</svg>
/* eslint-env jest */
import { join } from 'path'
import {
killApp,
findPort,
launchApp,
nextStart,
nextBuild,
check,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import fs from 'fs-extra'
jest.setTimeout(1000 * 30)
const appDir = join(__dirname, '../')
const nextConfig = join(appDir, 'next.config.js')
let appPort
let app
function runTests() {
it('should load the image', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
await check(async () => {
const result = await browser.eval(
`document.getElementById('basic-image').naturalWidth`
)
if (result === 0) {
throw new Error('Incorrectly loaded image')
}
return 'result-correct'
}, /result-correct/)
} finally {
if (browser) {
await browser.close()
}
}
})
}
describe('Image Component Tests', () => {
describe('dev mode', () => {
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(() => killApp(app))
runTests('dev')
})
describe('server mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
runTests('server')
})
describe('serverless mode', () => {
beforeAll(async () => {
await fs.writeFile(
nextConfig,
`
module.exports = {
target: 'serverless'
}
`
)
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await fs.unlink(nextConfig)
await killApp(app)
})
runTests('serverless')
})
})
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册