@@ -42,7 +42,6 @@ const {
4242 domains : configDomains ,
4343} = imageData
4444configSizes . sort ( ( a , b ) => a - b ) // smallest to largest
45- const largestSize = configSizes [ configSizes . length - 1 ]
4645
4746let cachedObserver : IntersectionObserver
4847const 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+
8396function 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
94110type CallLoaderProps = {
@@ -104,29 +120,38 @@ function callLoader(loaderProps: CallLoaderProps) {
104120
105121type 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
119144type 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
127152function 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+
152187export 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,
0 commit comments