Skip to content

Commit a080c13

Browse files
authored
Merge branch 'canary' into img/add-limit-checks
2 parents 4908cd3 + 61fab92 commit a080c13

File tree

6 files changed

+120
-81
lines changed

6 files changed

+120
-81
lines changed

packages/next/client/image.tsx

Lines changed: 98 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ const {
4242
domains: configDomains,
4343
} = imageData
4444
configSizes.sort((a, b) => a - b) // smallest to largest
45-
const largestSize = configSizes[configSizes.length - 1]
4645

4746
let cachedObserver: IntersectionObserver
4847
const IntersectionObserver =
@@ -80,15 +79,32 @@ function getObserver(): IntersectionObserver | undefined {
8079
))
8180
}
8281

82+
function getWidthsFromConfig(width: number | undefined) {
83+
if (typeof width !== 'number') {
84+
return configSizes
85+
}
86+
const widths: number[] = []
87+
for (let size of configSizes) {
88+
widths.push(size)
89+
if (size >= width) {
90+
break
91+
}
92+
}
93+
return widths
94+
}
95+
8396
function computeSrc(
8497
src: string,
8598
unoptimized: boolean,
99+
width: number | undefined,
86100
quality?: string
87101
): string {
88102
if (unoptimized) {
89103
return src
90104
}
91-
return callLoader({ src, width: largestSize, quality })
105+
const widths = getWidthsFromConfig(width)
106+
const largest = widths[widths.length - 1]
107+
return callLoader({ src, width: largest, quality })
92108
}
93109

94110
type CallLoaderProps = {
@@ -104,29 +120,38 @@ function callLoader(loaderProps: CallLoaderProps) {
104120

105121
type SrcSetData = {
106122
src: string
107-
widths: number[]
108-
quality?: string
123+
unoptimized: boolean
124+
width: number | undefined
125+
quality: string | undefined
109126
}
110127

111-
function generateSrcSet({ src, widths, quality }: SrcSetData): string {
128+
function generateSrcSet({
129+
src,
130+
unoptimized,
131+
width,
132+
quality,
133+
}: SrcSetData): string | undefined {
112134
// At each breakpoint, generate an image url using the loader, such as:
113135
// ' www.example.com/foo.jpg?w=480 480w, '
114-
return widths
115-
.map((width: number) => `${callLoader({ src, width, quality })} ${width}w`)
136+
if (unoptimized) {
137+
return undefined
138+
}
139+
return getWidthsFromConfig(width)
140+
.map((w) => `${callLoader({ src, width: w, quality })} ${w}w`)
116141
.join(', ')
117142
}
118143

119144
type PreloadData = {
120145
src: string
121-
widths: number[]
146+
unoptimized: boolean
147+
width: number | undefined
122148
sizes?: string
123-
unoptimized?: boolean
124149
quality?: string
125150
}
126151

127152
function generatePreload({
128153
src,
129-
widths,
154+
width,
130155
unoptimized = false,
131156
sizes,
132157
quality,
@@ -140,15 +165,25 @@ function generatePreload({
140165
<link
141166
rel="preload"
142167
as="image"
143-
href={computeSrc(src, unoptimized, quality)}
168+
href={computeSrc(src, unoptimized, width, quality)}
144169
// @ts-ignore: imagesrcset and imagesizes not yet in the link element type
145-
imagesrcset={generateSrcSet({ src, widths, quality })}
170+
imagesrcset={generateSrcSet({ src, unoptimized, width, quality })}
146171
imagesizes={sizes}
147172
/>
148173
</Head>
149174
)
150175
}
151176

177+
function getInt(x: unknown): number | undefined {
178+
if (typeof x === 'number') {
179+
return x
180+
}
181+
if (typeof x === 'string') {
182+
return parseInt(x, 10)
183+
}
184+
return undefined
185+
}
186+
152187
export default function Image({
153188
src,
154189
sizes,
@@ -165,6 +200,13 @@ export default function Image({
165200
const thisEl = useRef<HTMLImageElement>(null)
166201

167202
if (process.env.NODE_ENV !== 'production') {
203+
if (!src) {
204+
throw new Error(
205+
`Image is missing required "src" property. Make sure you pass "src" in props to the \`next/image\` component. Received: ${JSON.stringify(
206+
{ width, height, quality, unsized }
207+
)}`
208+
)
209+
}
168210
if (!VALID_LOADING_VALUES.includes(loading)) {
169211
throw new Error(
170212
`Image with src "${src}" has invalid "loading" property. Provided "${loading}" should be one of ${VALID_LOADING_VALUES.map(
@@ -200,58 +242,23 @@ export default function Image({
200242
}
201243
}, [thisEl, lazy])
202244

203-
// Generate attribute values
204-
const imgSrc = computeSrc(src, unoptimized, quality)
205-
const imgSrcSet = !unoptimized
206-
? generateSrcSet({
207-
src,
208-
widths: configSizes,
209-
quality,
210-
})
211-
: undefined
212-
213-
let imgAttributes:
214-
| {
215-
src: string
216-
srcSet?: string
217-
}
218-
| {
219-
'data-src': string
220-
'data-srcset'?: string
221-
}
222-
if (!lazy) {
223-
imgAttributes = {
224-
src: imgSrc,
225-
}
226-
if (imgSrcSet) {
227-
imgAttributes.srcSet = imgSrcSet
228-
}
229-
} else {
230-
imgAttributes = {
231-
'data-src': imgSrc,
232-
}
233-
if (imgSrcSet) {
234-
imgAttributes['data-srcset'] = imgSrcSet
235-
}
236-
className = className ? className + ' __lazy' : '__lazy'
237-
}
238-
245+
let widthInt = getInt(width)
246+
let heightInt = getInt(height)
239247
let divStyle: React.CSSProperties | undefined
240248
let imgStyle: React.CSSProperties | undefined
241249
let wrapperStyle: React.CSSProperties | undefined
242250
if (
243-
typeof height !== 'undefined' &&
244-
typeof width !== 'undefined' &&
251+
typeof widthInt !== 'undefined' &&
252+
typeof heightInt !== 'undefined' &&
245253
!unsized
246254
) {
247255
// <Image src="i.png" width={100} height={100} />
248256
// <Image src="i.png" width="100" height="100" />
249-
const quotient =
250-
parseInt(height as string, 10) / parseInt(width as string, 10)
257+
const quotient = heightInt / widthInt
251258
const ratio = isNaN(quotient) ? 1 : quotient * 100
252259
wrapperStyle = {
253260
maxWidth: '100%',
254-
width,
261+
width: widthInt,
255262
}
256263
divStyle = {
257264
position: 'relative',
@@ -266,8 +273,8 @@ export default function Image({
266273
width: '100%',
267274
}
268275
} else if (
269-
typeof height === 'undefined' &&
270-
typeof width === 'undefined' &&
276+
typeof widthInt === 'undefined' &&
277+
typeof heightInt === 'undefined' &&
271278
unsized
272279
) {
273280
// <Image src="i.png" unsized />
@@ -288,6 +295,41 @@ export default function Image({
288295
}
289296
}
290297

298+
// Generate attribute values
299+
const imgSrc = computeSrc(src, unoptimized, widthInt, quality)
300+
const imgSrcSet = generateSrcSet({
301+
src,
302+
width: widthInt,
303+
unoptimized,
304+
quality,
305+
})
306+
307+
let imgAttributes:
308+
| {
309+
src: string
310+
srcSet?: string
311+
}
312+
| {
313+
'data-src': string
314+
'data-srcset'?: string
315+
}
316+
if (!lazy) {
317+
imgAttributes = {
318+
src: imgSrc,
319+
}
320+
if (imgSrcSet) {
321+
imgAttributes.srcSet = imgSrcSet
322+
}
323+
} else {
324+
imgAttributes = {
325+
'data-src': imgSrc,
326+
}
327+
if (imgSrcSet) {
328+
imgAttributes['data-srcset'] = imgSrcSet
329+
}
330+
className = className ? className + ' __lazy' : '__lazy'
331+
}
332+
291333
// No need to add preloads on the client side--by the time the application is hydrated,
292334
// it's too late for preloads
293335
const shouldPreload = priority && typeof window === 'undefined'
@@ -298,7 +340,7 @@ export default function Image({
298340
{shouldPreload
299341
? generatePreload({
300342
src,
301-
widths: configSizes,
343+
width: widthInt,
302344
unoptimized,
303345
sizes,
304346
quality,

test/integration/image-component/basic/next.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = {
22
images: {
3-
sizes: [480, 1024, 1600],
3+
sizes: [480, 1024, 1600, 2000],
44
path: 'https://example.com/myaccount/',
55
loader: 'imgix',
66
},

test/integration/image-component/basic/pages/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const Page = () => {
1919
data-demo="demo-value"
2020
src="bar.jpg"
2121
loading="eager"
22-
width={300}
22+
width={1024}
2323
height={400}
2424
/>
2525
<Image
@@ -39,7 +39,6 @@ const Page = () => {
3939
width={300}
4040
height={400}
4141
/>
42-
<Image id="priority-image" priority src="withpriority.png" />
4342
<Image
4443
id="priority-image"
4544
priority

test/integration/image-component/basic/pages/lazy.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const Lazy = () => {
99
id="lazy-top"
1010
src="foo1.jpg"
1111
height={400}
12-
width={300}
12+
width={1024}
1313
loading="lazy"
1414
></Image>
1515
<div style={{ height: '2000px' }}></div>
@@ -35,15 +35,15 @@ const Lazy = () => {
3535
id="lazy-without-attribute"
3636
src="foo4.jpg"
3737
height={400}
38-
width={300}
38+
width={800}
3939
></Image>
4040
<div style={{ height: '2000px' }}></div>
4141
<Image
4242
id="eager-loading"
4343
src="foo5.jpg"
4444
loading="eager"
4545
height={400}
46-
width={300}
46+
width={1900}
4747
></Image>
4848
</div>
4949
)

0 commit comments

Comments
 (0)