From b1c9b2f104d0ab7ce3df92744392285e18de3ce8 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 22 Oct 2020 18:51:38 -0500 Subject: [PATCH 1/4] Add next/image default loader errors --- packages/next/client/image.tsx | 18 ++++++++++++++++++ .../default/pages/missing-src.js | 14 ++++++++++++++ .../default/pages/missing-width.js | 14 ++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 test/integration/image-component/default/pages/missing-src.js create mode 100644 test/integration/image-component/default/pages/missing-width.js diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 7ca82668b8700de..1deff9c27f1d2d0 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -344,6 +344,24 @@ function cloudinaryLoader({ root, src, width, quality }: LoaderProps): string { } function defaultLoader({ root, src, width, quality }: LoaderProps): string { + if (process.env.NODE_ENV !== 'production') { + const missingValues = [] + + // these should always be provided but make sure they are + if (!src) missingValues.push('src') + if (!width) missingValues.push('width') + + if (missingValues.length > 0) { + throw new Error( + `Next Image optimizer requires ${missingValues.join( + ', ' + )} to be provided. Make sure you pass them as props to the \`next/image\` component. Received: ${JSON.stringify( + { src, width, quality } + )}` + ) + } + } + return `${root}?url=${encodeURIComponent(src)}&${ width ? `w=${width}&` : '' }q=${quality || '100'}` diff --git a/test/integration/image-component/default/pages/missing-src.js b/test/integration/image-component/default/pages/missing-src.js new file mode 100644 index 000000000000000..69ae5a62d0ca28d --- /dev/null +++ b/test/integration/image-component/default/pages/missing-src.js @@ -0,0 +1,14 @@ +import React from 'react' +import Image from 'next/image' + +const Page = () => { + return ( +
+

Hello World

+ +

This is the index page

+
+ ) +} + +export default Page diff --git a/test/integration/image-component/default/pages/missing-width.js b/test/integration/image-component/default/pages/missing-width.js new file mode 100644 index 000000000000000..6463d5b59b0410c --- /dev/null +++ b/test/integration/image-component/default/pages/missing-width.js @@ -0,0 +1,14 @@ +import React from 'react' +import Image from 'next/image' + +const Page = () => { + return ( +
+

Hello World

+ +

This is the index page

+
+ ) +} + +export default Page From fdebf7795d68853469ba2b512f4550e9c4099925 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 23 Oct 2020 16:10:18 -0500 Subject: [PATCH 2/4] Add domains check --- packages/next/build/webpack-config.ts | 6 +++++ packages/next/client/image.tsx | 10 ++++++++ .../{missing-width.js => invalid-src.js} | 6 ++++- .../default/test/index.test.js | 24 ++++++++++++++++++- 4 files changed, 44 insertions(+), 2 deletions(-) rename test/integration/image-component/default/pages/{missing-width.js => invalid-src.js} (65%) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 272ff411f253875..d174521b7ad9ad5 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -994,6 +994,12 @@ export default async function getBaseWebpackConfig( sizes: config.images.sizes, path: config.images.path, loader: config.images.loader, + ...(dev + ? { + // pass domains in development to allow validating on the client + domains: config.images.domains, + } + : {}), }), 'process.env.__NEXT_ROUTER_BASEPATH': JSON.stringify(config.basePath), 'process.env.__NEXT_HAS_REWRITES': JSON.stringify(hasRewrites), diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 7ccdda73167b00d..3143d6a32ba92e4 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -358,6 +358,16 @@ function defaultLoader({ root, src, width, quality }: LoaderProps): string { )}` ) } + + if (src && !src.startsWith('/') && imageData.domains) { + const parsedSrc = new URL(src) + + if (!imageData.domains.includes(parsedSrc.hostname)) { + throw new Error( + `Invalid src prop (${src}) on \`next/image\`, hostname is not configured under images in your \`next.config.js\`` + ) + } + } } return `${root}?url=${encodeURIComponent(src)}&w=${width}&q=${ diff --git a/test/integration/image-component/default/pages/missing-width.js b/test/integration/image-component/default/pages/invalid-src.js similarity index 65% rename from test/integration/image-component/default/pages/missing-width.js rename to test/integration/image-component/default/pages/invalid-src.js index 6463d5b59b0410c..d77b883b6abd7b1 100644 --- a/test/integration/image-component/default/pages/missing-width.js +++ b/test/integration/image-component/default/pages/invalid-src.js @@ -5,7 +5,11 @@ const Page = () => { return (

Hello World

- +

This is the index page

) diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index f698189e3b4f558..66f0ed9c70f6005 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -8,6 +8,8 @@ import { nextStart, nextBuild, check, + hasRedbox, + getRedboxHeader, } from 'next-test-utils' import webdriver from 'next-webdriver' import fs from 'fs-extra' @@ -20,7 +22,7 @@ const nextConfig = join(appDir, 'next.config.js') let appPort let app -function runTests() { +function runTests(mode) { it('should load the images', async () => { let browser try { @@ -73,6 +75,26 @@ function runTests() { } } }) + + if (mode === 'dev') { + it('should show missing src error', async () => { + const browser = await webdriver(appPort, '/missing-src') + + await hasRedbox(browser) + expect(await getRedboxHeader(browser)).toContain( + 'Next Image optimizer requires src to be provided. Make sure you pass them as props to the `next/image` component. Received: {"width":1200}' + ) + }) + + it('should show invalid src error', async () => { + const browser = await webdriver(appPort, '/invalid-src') + + await hasRedbox(browser) + expect(await getRedboxHeader(browser)).toContain( + 'Invalid src prop (https://google.com/test.png) on `next/image`, hostname is not configured under images in your `next.config.js`' + ) + }) + } } describe('Image Component Tests', () => { From 4a9ccaa5022ecf5cdfb9d911536e65d00a0f7351 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 23 Oct 2020 20:45:10 -0500 Subject: [PATCH 3/4] Apply suggestions from PR --- packages/next/client/image.tsx | 23 +++++++++++++++---- .../default/pages/invalid-src.js | 7 +----- .../default/pages/missing-src.js | 4 +--- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 3143d6a32ba92e4..758e196c75f3407 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -35,7 +35,12 @@ type ImageProps = Omit< ) const imageData: ImageData = process.env.__NEXT_IMAGE_OPTS as any -const { sizes: configSizes, loader: configLoader, path: configPath } = imageData +const { + sizes: configSizes, + loader: configLoader, + path: configPath, + domains: configDomains, +} = imageData configSizes.sort((a, b) => a - b) // smallest to largest const largestSize = configSizes[configSizes.length - 1] @@ -351,7 +356,7 @@ function defaultLoader({ root, src, width, quality }: LoaderProps): string { if (missingValues.length > 0) { throw new Error( - `Next Image optimizer requires ${missingValues.join( + `Next Image Optimization requires ${missingValues.join( ', ' )} to be provided. Make sure you pass them as props to the \`next/image\` component. Received: ${JSON.stringify( { src, width, quality } @@ -359,10 +364,18 @@ function defaultLoader({ root, src, width, quality }: LoaderProps): string { ) } - if (src && !src.startsWith('/') && imageData.domains) { - const parsedSrc = new URL(src) + if (src && !src.startsWith('/') && configDomains) { + let parsedSrc: URL + try { + parsedSrc = new URL(src) + } catch (err) { + console.error(err) + throw new Error( + `Failed to parse "${src}" if using relative image it must start with a leading slash "/" or be an absolute URL` + ) + } - if (!imageData.domains.includes(parsedSrc.hostname)) { + if (!configDomains.includes(parsedSrc.hostname)) { throw new Error( `Invalid src prop (${src}) on \`next/image\`, hostname is not configured under images in your \`next.config.js\`` ) diff --git a/test/integration/image-component/default/pages/invalid-src.js b/test/integration/image-component/default/pages/invalid-src.js index d77b883b6abd7b1..d9920d016b95cef 100644 --- a/test/integration/image-component/default/pages/invalid-src.js +++ b/test/integration/image-component/default/pages/invalid-src.js @@ -5,12 +5,7 @@ const Page = () => { return (

Hello World

- -

This is the index page

+
) } diff --git a/test/integration/image-component/default/pages/missing-src.js b/test/integration/image-component/default/pages/missing-src.js index 69ae5a62d0ca28d..df490c1fc04ef1f 100644 --- a/test/integration/image-component/default/pages/missing-src.js +++ b/test/integration/image-component/default/pages/missing-src.js @@ -4,9 +4,7 @@ import Image from 'next/image' const Page = () => { return (
-

Hello World

- -

This is the index page

+
) } From 88731071aa7cb5af8920e16793a09c10a1d025ad Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Sat, 24 Oct 2020 12:41:14 -0500 Subject: [PATCH 4/4] Update test --- .../integration/image-component/default/test/index.test.js | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index 1c519d5d3860316..8af093b9240c4ec 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -88,7 +88,7 @@ function runTests(mode) { await hasRedbox(browser) expect(await getRedboxHeader(browser)).toContain( - 'Next Image optimizer requires src to be provided. Make sure you pass them as props to the `next/image` component. Received: {"width":1200}' + 'Next Image Optimization requires src to be provided. Make sure you pass them as props to the `next/image` component. Received: {"width":1200}' ) }) diff --git a/yarn.lock b/yarn.lock index 829b25a7f784843..3e22fcac28b1971 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15826,9 +15826,10 @@ styled-jsx-plugin-postcss@2.0.1: postcss "^7.0.2" postcss-load-plugins "^2.3.0" -styled-jsx@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-3.3.0.tgz#32335c1a3ecfc923ba4f9c056eeb3d4699006b09" +styled-jsx@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-3.3.1.tgz#d79f306c42c99cefbe8e76f35dad8100dc5c9ecc" + integrity sha512-RhW71t3k95E3g7Zq3lEBk+kmf+p4ZME7c5tfsYf9M5mq6CgIvFXkbvhawL2gWriXLRlMyKAYACE89Qa2JnTqUw== dependencies: "@babel/types" "7.8.3" babel-plugin-syntax-jsx "6.18.0"