@@ -42,7 +42,6 @@ const {
42
42
domains : configDomains ,
43
43
} = imageData
44
44
configSizes . sort ( ( a , b ) => a - b ) // smallest to largest
45
- const largestSize = configSizes [ configSizes . length - 1 ]
46
45
47
46
let cachedObserver : IntersectionObserver
48
47
const IntersectionObserver =
@@ -80,15 +79,32 @@ function getObserver(): IntersectionObserver | undefined {
80
79
) )
81
80
}
82
81
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
+
83
96
function computeSrc (
84
97
src : string ,
85
98
unoptimized : boolean ,
99
+ width : number | undefined ,
86
100
quality ?: string
87
101
) : string {
88
102
if ( unoptimized ) {
89
103
return src
90
104
}
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 } )
92
108
}
93
109
94
110
type CallLoaderProps = {
@@ -104,29 +120,38 @@ function callLoader(loaderProps: CallLoaderProps) {
104
120
105
121
type SrcSetData = {
106
122
src : string
107
- widths : number [ ]
108
- quality ?: string
123
+ unoptimized : boolean
124
+ width : number | undefined
125
+ quality : string | undefined
109
126
}
110
127
111
- function generateSrcSet ( { src, widths, quality } : SrcSetData ) : string {
128
+ function generateSrcSet ( {
129
+ src,
130
+ unoptimized,
131
+ width,
132
+ quality,
133
+ } : SrcSetData ) : string | undefined {
112
134
// At each breakpoint, generate an image url using the loader, such as:
113
135
// ' 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` )
116
141
. join ( ', ' )
117
142
}
118
143
119
144
type PreloadData = {
120
145
src : string
121
- widths : number [ ]
146
+ unoptimized : boolean
147
+ width : number | undefined
122
148
sizes ?: string
123
- unoptimized ?: boolean
124
149
quality ?: string
125
150
}
126
151
127
152
function generatePreload ( {
128
153
src,
129
- widths ,
154
+ width ,
130
155
unoptimized = false ,
131
156
sizes,
132
157
quality,
@@ -140,15 +165,25 @@ function generatePreload({
140
165
< link
141
166
rel = "preload"
142
167
as = "image"
143
- href = { computeSrc ( src , unoptimized , quality ) }
168
+ href = { computeSrc ( src , unoptimized , width , quality ) }
144
169
// @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 } ) }
146
171
imagesizes = { sizes }
147
172
/>
148
173
</ Head >
149
174
)
150
175
}
151
176
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
+
152
187
export default function Image ( {
153
188
src,
154
189
sizes,
@@ -165,6 +200,13 @@ export default function Image({
165
200
const thisEl = useRef < HTMLImageElement > ( null )
166
201
167
202
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
+ }
168
210
if ( ! VALID_LOADING_VALUES . includes ( loading ) ) {
169
211
throw new Error (
170
212
`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({
200
242
}
201
243
} , [ thisEl , lazy ] )
202
244
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 )
239
247
let divStyle : React . CSSProperties | undefined
240
248
let imgStyle : React . CSSProperties | undefined
241
249
let wrapperStyle : React . CSSProperties | undefined
242
250
if (
243
- typeof height !== 'undefined' &&
244
- typeof width !== 'undefined' &&
251
+ typeof widthInt !== 'undefined' &&
252
+ typeof heightInt !== 'undefined' &&
245
253
! unsized
246
254
) {
247
255
// <Image src="i.png" width={100} height={100} />
248
256
// <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
251
258
const ratio = isNaN ( quotient ) ? 1 : quotient * 100
252
259
wrapperStyle = {
253
260
maxWidth : '100%' ,
254
- width,
261
+ width : widthInt ,
255
262
}
256
263
divStyle = {
257
264
position : 'relative' ,
@@ -266,8 +273,8 @@ export default function Image({
266
273
width : '100%' ,
267
274
}
268
275
} else if (
269
- typeof height === 'undefined' &&
270
- typeof width === 'undefined' &&
276
+ typeof widthInt === 'undefined' &&
277
+ typeof heightInt === 'undefined' &&
271
278
unsized
272
279
) {
273
280
// <Image src="i.png" unsized />
@@ -288,6 +295,41 @@ export default function Image({
288
295
}
289
296
}
290
297
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
+
291
333
// No need to add preloads on the client side--by the time the application is hydrated,
292
334
// it's too late for preloads
293
335
const shouldPreload = priority && typeof window === 'undefined'
@@ -298,7 +340,7 @@ export default function Image({
298
340
{ shouldPreload
299
341
? generatePreload ( {
300
342
src,
301
- widths : configSizes ,
343
+ width : widthInt ,
302
344
unoptimized,
303
345
sizes,
304
346
quality,
0 commit comments