From a0749a5103ecdc3b93f296e3eae5e64f54368c6e Mon Sep 17 00:00:00 2001 From: David Goguen Date: Fri, 18 Aug 2023 16:05:17 -0400 Subject: [PATCH] feat(image): add support for custom `loaderFile` when `loader: default` (#53417) Image Optimization API currently does not work if a custom loaderFile is specified in next.config.js, even if loader is explicitly set to 'default', because it is currently being overridden to 'custom' simply because a loaderFile is specified. This is unnecessary and causing the Image Optimization API routes not to be initialized since the change to the config happens before the routes are initialized. [Sandbox Reproduction](https://codesandbox.io/p/sandbox/purple-pine-t7hhgl?file=%2Fimage-loader.js%3A8%2C1) - Fixes #53415 --- packages/next/src/server/config.ts | 1 - .../dummy-loader.js | 3 + .../next.config.js | 6 ++ .../pages/get-img-props.js | 34 +++++++ .../pages/index.js | 35 +++++++ .../public/logo.png | Bin 0 -> 1545 bytes .../test/index.test.ts | 92 ++++++++++++++++++ 7 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 test/integration/next-image-new/loader-config-default-loader-with-file/dummy-loader.js create mode 100644 test/integration/next-image-new/loader-config-default-loader-with-file/next.config.js create mode 100644 test/integration/next-image-new/loader-config-default-loader-with-file/pages/get-img-props.js create mode 100644 test/integration/next-image-new/loader-config-default-loader-with-file/pages/index.js create mode 100644 test/integration/next-image-new/loader-config-default-loader-with-file/public/logo.png create mode 100644 test/integration/next-image-new/loader-config-default-loader-with-file/test/index.test.ts diff --git a/packages/next/src/server/config.ts b/packages/next/src/server/config.ts index 8504a52a0f084..45dbc0b227d6e 100644 --- a/packages/next/src/server/config.ts +++ b/packages/next/src/server/config.ts @@ -335,7 +335,6 @@ function assignDefaults( `Specified images.loaderFile does not exist at "${absolutePath}".` ) } - images.loader = 'custom' images.loaderFile = absolutePath } } diff --git a/test/integration/next-image-new/loader-config-default-loader-with-file/dummy-loader.js b/test/integration/next-image-new/loader-config-default-loader-with-file/dummy-loader.js new file mode 100644 index 0000000000000..b33aaf436e592 --- /dev/null +++ b/test/integration/next-image-new/loader-config-default-loader-with-file/dummy-loader.js @@ -0,0 +1,3 @@ +export default function dummyLoader({ src, width, quality }) { + return `/_next/image/?url=${src}&w=${width}&q=${quality || 50}` +} diff --git a/test/integration/next-image-new/loader-config-default-loader-with-file/next.config.js b/test/integration/next-image-new/loader-config-default-loader-with-file/next.config.js new file mode 100644 index 0000000000000..a5ce2b7098ecb --- /dev/null +++ b/test/integration/next-image-new/loader-config-default-loader-with-file/next.config.js @@ -0,0 +1,6 @@ +module.exports = { + images: { + loader: 'default', + loaderFile: './dummy-loader.js', + }, +} diff --git a/test/integration/next-image-new/loader-config-default-loader-with-file/pages/get-img-props.js b/test/integration/next-image-new/loader-config-default-loader-with-file/pages/get-img-props.js new file mode 100644 index 0000000000000..db36ae0c908b7 --- /dev/null +++ b/test/integration/next-image-new/loader-config-default-loader-with-file/pages/get-img-props.js @@ -0,0 +1,34 @@ +import { unstable_getImgProps as getImgProps } from 'next/image' + +function loader({ src, width, quality }) { + return `${src}?wid=${width}&qual=${quality || 35}` +} + +export default function Page() { + const { props: img1 } = getImgProps({ + id: 'img1', + alt: 'img1', + src: '/logo.png', + width: '400', + height: '400', + priority: true, + }) + const { props: img2 } = getImgProps({ + id: 'img2', + alt: 'img2', + src: '/logo.png', + width: '200', + height: '200', + loader, + }) + return ( +
+

Loader Config

+ +

Scroll down...

+
+

Loader Prop

+ +
+ ) +} diff --git a/test/integration/next-image-new/loader-config-default-loader-with-file/pages/index.js b/test/integration/next-image-new/loader-config-default-loader-with-file/pages/index.js new file mode 100644 index 0000000000000..e246516278ae4 --- /dev/null +++ b/test/integration/next-image-new/loader-config-default-loader-with-file/pages/index.js @@ -0,0 +1,35 @@ +import React from 'react' +import Image from 'next/image' + +function loader({ src, width, quality }) { + return `${src}?wid=${width}&qual=${quality || 35}` +} + +const Page = () => { + return ( +
+

Loader Config

+ img1 +

Scroll down...

+
+

Loader Prop

+ img2 +
+ ) +} + +export default Page diff --git a/test/integration/next-image-new/loader-config-default-loader-with-file/public/logo.png b/test/integration/next-image-new/loader-config-default-loader-with-file/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e14fafc5cf3bc63b70914ad20467f40f7fecd572 GIT binary patch literal 1545 zcmbVM{Xf$Q9A6%~&O#i9S`$MamWNGTo{nv7c{rq_?QHVUW~E6OQi@{ZJcmZ|?7l@Q zzFgO>LgSLHn=t)6BZh%t7EPMfk1SL z1Y86JvZ4In(b7~asTB_EYLbzJ#fBxtCqp2a7u(A{gEak&&i%OE5K&=dA02(f0EgVb zDQO?EwAgXhbPx#1STW3~N_6+*i-&gO&5gIVD)qtd)=yh(VkE{hpxOq=E?Uo-)5z*x z!Au!iA$YiLAm+*0qggP>?VsKD-2i&HQxQ3+OqX*8S}wK5H8(1QM_f{Jya%lp;-fFQ z-RxdA9ea)1aI;`EXvn#9J~1_}n?bl%WsA3~x1yF~ZJY?F%5TY1f>Os{GDi>X>C?IS zC87Oo3ZX}KJ*U`mZ%63leZQDa&ij+|L2Ig&kv$8+G!kJ)!A>IpI0!SpvZ=R*dmxwE z_A02!zif^Xi?D&?&%f0Tzbc>bI(#PkQsao89{0s~R(I*hM>py`YIH=n8s(l<+!VhFb)fj#H;uE`npo7 zY;0_#QmGRY6Algzb}0{05Qr9vi1UjyHCq}CIyy~&Xo)lk4660;XBm=IbzH;Vwux!6 z@U`%Q<6`U_r^#vHXzMH%_g}z&^bvih;Naksl&3F)p7Kn#$+goa*xhsUD|t?H%CawT z>JQ8!^fPzDF6c8waZPU1$^P~{X*y_EN`KC=6nc}~iEX#>ud*u)-GT=qZK~K!#eMKri|K2@v zeX7|gqiZ-a27vkY(m>jlb*A45J^WhNqUd5svx=i!WlyGoDxyIkDCJw8 zl1RKs=y0j+xtSIh@AZ-SU-~z%d7|iJXK0I}nj!QZ_;_V0t%N>WpH)B+RT91Kkuhzx zSp{CL@O&X!puOb5enarY#IKV0$GfaZ<5QCF#q6Ih66Bl1Pk?cT!sCl5^YK4KUf8=r z`aO#WUfA<6@Z|tBgFYm!h8b-eKV4c&$3bTW&<9YGGZ&`xG#9~EHI4;**~o$2bOc^F z)xqxjhTZjF)wtZ04Ns<6mIBW?61;SKUp&Ix#QrYF;SY_@rCeH2X2*tJ$*pAIHb zh#ej+0ZbcVCs7JzV7TsL6Jyyhc?vBAKW|d~E=#`(Epz?bhZI(;xeQ`sbe2CXvFp-!)9gAPmnDWWTsf>26XSP@ zv&2i`WrNZNf%ZoawxTiv7?Jj|6+NW@o>r`=449DMidcqyfhe1CUhQqXbvCSyC1#>! z&TQ9Zpp%MX zY5qJSn%bSF+=@PAVhp9?wWsW-al19&OZPE literal 0 HcmV?d00001 diff --git a/test/integration/next-image-new/loader-config-default-loader-with-file/test/index.test.ts b/test/integration/next-image-new/loader-config-default-loader-with-file/test/index.test.ts new file mode 100644 index 0000000000000..987e9a7bc08eb --- /dev/null +++ b/test/integration/next-image-new/loader-config-default-loader-with-file/test/index.test.ts @@ -0,0 +1,92 @@ +/* eslint-env jest */ + +import { + findPort, + killApp, + launchApp, + nextBuild, + nextStart, +} from 'next-test-utils' +import webdriver from 'next-webdriver' +import { join } from 'path' + +const appDir = join(__dirname, '../') + +let appPort +let app + +function runTests(url: string) { + it('should work with loaderFile config, leaving default image optimization enabled', async () => { + const browser = await webdriver(appPort, url) + expect(await browser.elementById('img1').getAttribute('src')).toBe( + '/_next/image/?url=/logo.png&w=828&q=50' + ) + expect(await browser.elementById('img1').getAttribute('srcset')).toBe( + '/_next/image/?url=/logo.png&w=640&q=50 1x, /_next/image/?url=/logo.png&w=828&q=50 2x' + ) + + // make sure the image was indeed successfully loaded from /_next/image + expect( + await browser.eval( + `document.getElementById('img1').complete && document.getElementById('img1').naturalWidth !== 0` + ) + ).toBe(true) + }) + + it('should work with loader prop', async () => { + const browser = await webdriver(appPort, url) + expect(await browser.elementById('img2').getAttribute('src')).toBe( + '/logo.png?wid=640&qual=35' + ) + expect(await browser.elementById('img2').getAttribute('srcset')).toBe( + '/logo.png?wid=256&qual=35 1x, /logo.png?wid=640&qual=35 2x' + ) + }) +} + +describe('Image Loader Config', () => { + describe('dev mode - component', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) + }) + afterAll(() => { + killApp(app) + }) + runTests('/') + }) + + describe('server mode - component', () => { + beforeAll(async () => { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(() => { + killApp(app) + }) + runTests('/') + }) + describe('dev mode - getImgProps', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) + }) + afterAll(() => { + killApp(app) + }) + runTests('/get-img-props') + }) + + describe('server mode - getImgProps', () => { + beforeAll(async () => { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(() => { + killApp(app) + }) + runTests('/get-img-props') + }) +})