From 9c65c994cc716ad73c8e84f526f962459d06ef43 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Sun, 25 Oct 2020 00:22:47 -0500 Subject: [PATCH] Add next/image default loader errors (#18152) * Add next/image default loader errors * Add domains check * Apply suggestions from PR * Update test --- packages/next/build/webpack-config.ts | 6 +++ packages/next/client/image.tsx | 44 ++++++++++++++++++- .../default/pages/invalid-src.js | 13 ++++++ .../default/pages/missing-src.js | 12 +++++ .../default/test/index.test.js | 24 +++++++++- 5 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 test/integration/image-component/default/pages/invalid-src.js create mode 100644 test/integration/image-component/default/pages/missing-src.js 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 80fb05cd9f34d01..a38b40e1cafa099 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -17,6 +17,7 @@ type ImageData = { sizes: number[] loader: LoaderKey path: string + domains?: string[] } type ImageProps = Omit< @@ -34,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] @@ -349,6 +355,42 @@ 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 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 } + )}` + ) + } + + 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 (!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\`` + ) + } + } + } + return `${root}?url=${encodeURIComponent(src)}&w=${width}&q=${ quality || '100' }` diff --git a/test/integration/image-component/default/pages/invalid-src.js b/test/integration/image-component/default/pages/invalid-src.js new file mode 100644 index 000000000000000..d9920d016b95cef --- /dev/null +++ b/test/integration/image-component/default/pages/invalid-src.js @@ -0,0 +1,13 @@ +import React from 'react' +import Image from 'next/image' + +const Page = () => { + return ( +
+

Hello World

+ +
+ ) +} + +export default Page 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..df490c1fc04ef1f --- /dev/null +++ b/test/integration/image-component/default/pages/missing-src.js @@ -0,0 +1,12 @@ +import React from 'react' +import Image from 'next/image' + +const Page = () => { + return ( +
+ +
+ ) +} + +export default Page diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index f6deaf57b064339..8af093b9240c4ec 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 { @@ -79,6 +81,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 Optimization 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', () => {