@@ -16,7 +16,20 @@ if (typeof window === 'undefined') {
16
16
const VALID_LOADING_VALUES = [ 'lazy' , 'eager' , undefined ] as const
17
17
type LoadingValue = typeof VALID_LOADING_VALUES [ number ]
18
18
19
- const loaders = new Map < LoaderValue , ( props : LoaderProps ) => string > ( [
19
+ export type ImageLoader = ( resolverProps : ImageLoaderProps ) => string
20
+
21
+ export type ImageLoaderProps = {
22
+ src : string
23
+ width : number
24
+ quality ?: number
25
+ }
26
+
27
+ type DefaultImageLoaderProps = ImageLoaderProps & { root : string }
28
+
29
+ const loaders = new Map <
30
+ LoaderValue ,
31
+ ( props : DefaultImageLoaderProps ) => string
32
+ > ( [
20
33
[ 'imgix' , imgixLoader ] ,
21
34
[ 'cloudinary' , cloudinaryLoader ] ,
22
35
[ 'akamai' , akamaiLoader ] ,
@@ -39,6 +52,7 @@ export type ImageProps = Omit<
39
52
'src' | 'srcSet' | 'ref' | 'width' | 'height' | 'loading' | 'style'
40
53
> & {
41
54
src : string
55
+ loader ?: ImageLoader
42
56
quality ?: number | string
43
57
priority ?: boolean
44
58
loading ?: LoadingValue
@@ -103,28 +117,11 @@ function getWidths(
103
117
return { widths, kind : 'x' }
104
118
}
105
119
106
- type CallLoaderProps = {
107
- src : string
108
- width : number
109
- quality ?: number
110
- }
111
-
112
- function callLoader ( loaderProps : CallLoaderProps ) : string {
113
- const load = loaders . get ( configLoader )
114
- if ( load ) {
115
- return load ( { root : configPath , ...loaderProps } )
116
- }
117
- throw new Error (
118
- `Unknown "loader" found in "next.config.js". Expected: ${ VALID_LOADERS . join (
119
- ', '
120
- ) } . Received: ${ configLoader } `
121
- )
122
- }
123
-
124
120
type GenImgAttrsData = {
125
121
src : string
126
122
unoptimized : boolean
127
123
layout : LayoutValue
124
+ loader : ImageLoader
128
125
width ?: number
129
126
quality ?: number
130
127
sizes ?: string
@@ -143,6 +140,7 @@ function generateImgAttrs({
143
140
width,
144
141
quality,
145
142
sizes,
143
+ loader,
146
144
} : GenImgAttrsData ) : GenImgAttrsResult {
147
145
if ( unoptimized ) {
148
146
return { src, srcSet : undefined , sizes : undefined }
@@ -151,22 +149,18 @@ function generateImgAttrs({
151
149
const { widths, kind } = getWidths ( width , layout )
152
150
const last = widths . length - 1
153
151
154
- const srcSet = widths
155
- . map (
156
- ( w , i ) =>
157
- ` ${ callLoader ( { src , quality , width : w } ) } ${
158
- kind === 'w' ? w : i + 1
159
- } ${ kind } `
160
- )
161
- . join ( ', ' )
162
-
163
- if ( ! sizes && kind === 'w' ) {
164
- sizes = '100vw'
152
+ return {
153
+ src : loader ( { src , quality , width : widths [ last ] } ) ,
154
+ sizes : ! sizes && kind === 'w' ? '100vw' : sizes ,
155
+ srcSet : widths
156
+ . map (
157
+ ( w , i ) =>
158
+ ` ${ loader ( { src , quality , width : w } ) } ${
159
+ kind === 'w' ? w : i + 1
160
+ } ${ kind } `
161
+ )
162
+ . join ( ', ' ) ,
165
163
}
166
-
167
- src = callLoader ( { src, quality, width : widths [ last ] } )
168
-
169
- return { src, sizes, srcSet }
170
164
}
171
165
172
166
function getInt ( x : unknown ) : number | undefined {
@@ -179,6 +173,18 @@ function getInt(x: unknown): number | undefined {
179
173
return undefined
180
174
}
181
175
176
+ function defaultImageLoader ( loaderProps : ImageLoaderProps ) {
177
+ const load = loaders . get ( configLoader )
178
+ if ( load ) {
179
+ return load ( { root : configPath , ...loaderProps } )
180
+ }
181
+ throw new Error (
182
+ `Unknown "loader" found in "next.config.js". Expected: ${ VALID_LOADERS . join (
183
+ ', '
184
+ ) } . Received: ${ configLoader } `
185
+ )
186
+ }
187
+
182
188
export default function Image ( {
183
189
src,
184
190
sizes,
@@ -191,6 +197,7 @@ export default function Image({
191
197
height,
192
198
objectFit,
193
199
objectPosition,
200
+ loader = defaultImageLoader ,
194
201
...all
195
202
} : ImageProps ) {
196
203
let rest : Partial < ImageProps > = all
@@ -377,6 +384,7 @@ export default function Image({
377
384
width : widthInt ,
378
385
quality : qualityInt ,
379
386
sizes,
387
+ loader,
380
388
} )
381
389
}
382
390
@@ -444,13 +452,16 @@ export default function Image({
444
452
445
453
//BUILT IN LOADERS
446
454
447
- type LoaderProps = CallLoaderProps & { root : string }
448
-
449
455
function normalizeSrc ( src : string ) : string {
450
456
return src [ 0 ] === '/' ? src . slice ( 1 ) : src
451
457
}
452
458
453
- function imgixLoader ( { root, src, width, quality } : LoaderProps ) : string {
459
+ function imgixLoader ( {
460
+ root,
461
+ src,
462
+ width,
463
+ quality,
464
+ } : DefaultImageLoaderProps ) : string {
454
465
// Demo: https://static.imgix.net/daisy.png?format=auto&fit=max&w=300
455
466
const params = [ 'auto=format' , 'fit=max' , 'w=' + width ]
456
467
let paramsString = ''
@@ -464,18 +475,28 @@ function imgixLoader({ root, src, width, quality }: LoaderProps): string {
464
475
return `${ root } ${ normalizeSrc ( src ) } ${ paramsString } `
465
476
}
466
477
467
- function akamaiLoader ( { root, src, width } : LoaderProps ) : string {
478
+ function akamaiLoader ( { root, src, width } : DefaultImageLoaderProps ) : string {
468
479
return `${ root } ${ normalizeSrc ( src ) } ?imwidth=${ width } `
469
480
}
470
481
471
- function cloudinaryLoader ( { root, src, width, quality } : LoaderProps ) : string {
482
+ function cloudinaryLoader ( {
483
+ root,
484
+ src,
485
+ width,
486
+ quality,
487
+ } : DefaultImageLoaderProps ) : string {
472
488
// Demo: https://res.cloudinary.com/demo/image/upload/w_300,c_limit,q_auto/turtles.jpg
473
489
const params = [ 'f_auto' , 'c_limit' , 'w_' + width , 'q_' + ( quality || 'auto' ) ]
474
490
let paramsString = params . join ( ',' ) + '/'
475
491
return `${ root } ${ paramsString } ${ normalizeSrc ( src ) } `
476
492
}
477
493
478
- function defaultLoader ( { root, src, width, quality } : LoaderProps ) : string {
494
+ function defaultLoader ( {
495
+ root,
496
+ src,
497
+ width,
498
+ quality,
499
+ } : DefaultImageLoaderProps ) : string {
479
500
if ( process . env . NODE_ENV !== 'production' ) {
480
501
const missingValues = [ ]
481
502
0 commit comments