From dbbe2e3dd26017574e4d61726c89954ccf1cb711 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 7 Oct 2022 19:09:29 -0400 Subject: [PATCH 1/4] Add pretty error when image import is invalid format --- .../webpack/loaders/next-image-loader.js | 17 ++++- .../parseNotFoundError.ts | 31 ++++++++ .../webpackModuleError.ts | 12 +++- .../invalid-image-import/pages/index.js | 14 ++++ .../invalid-image-import/public/invalid.svg | 3 + .../invalid-image-import/test/index.test.ts | 71 +++++++++++++++++++ 6 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 test/integration/image-future/invalid-image-import/pages/index.js create mode 100644 test/integration/image-future/invalid-image-import/public/invalid.svg create mode 100644 test/integration/image-future/invalid-image-import/test/index.test.ts diff --git a/packages/next/build/webpack/loaders/next-image-loader.js b/packages/next/build/webpack/loaders/next-image-loader.js index a336090874db9..c70234990dd82 100644 --- a/packages/next/build/webpack/loaders/next-image-loader.js +++ b/packages/next/build/webpack/loaders/next-image-loader.js @@ -11,13 +11,17 @@ function nextImageLoader(content) { const { isServer, isDev, assetPrefix, basePath } = this.getOptions() const context = this.rootContext const opts = { context, content } + const importName = loaderUtils.interpolateName( + this, + '[path][name].[ext]', + opts + ) const interpolatedName = loaderUtils.interpolateName( this, '/static/media/[name].[hash:8].[ext]', opts ) const outputPath = assetPrefix + '/_next' + interpolatedName - let extension = loaderUtils.interpolateName(this, '[ext]', opts) if (extension === 'jpg') { extension = 'jpeg' @@ -25,8 +29,17 @@ function nextImageLoader(content) { const imageSizeSpan = imageLoaderSpan.traceChild('image-size-calculation') const imageSize = await imageSizeSpan.traceAsyncFn(() => - getImageSize(content, extension) + getImageSize(content, extension).catch((err) => err) ) + + if (imageSize instanceof Error) { + const err = new Error( + `Image import "./${importName}" is not a valid image format` + ) + err.name = 'InvalidImageFormatError' + throw err + } + let blurDataURL let blurWidth let blurHeight diff --git a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts index 5591dfa93a54d..b0b0bda5e8e67 100644 --- a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts +++ b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts @@ -118,3 +118,34 @@ export async function getNotFoundError( return input } } + +export async function getImageError( + compilation: any, + input: any, + fileName: string, + err: Error +): Promise { + if (err.name !== 'InvalidImageFormatError') { + return false + } + + const moduleTrace = getModuleTrace(input, compilation) + const { origin, module } = moduleTrace[0] || {} + if (!origin || !module) { + return false + } + const page = origin.rawRequest.replace(/^private-next-pages/, './pages') + const importedFile = module.rawRequest + const source = origin.originalSource().buffer().toString('utf8') as string + let lineNumber = -1 + source.split('\n').some((line) => { + lineNumber++ + return line.includes(importedFile) + }) + return new SimpleWebpackError( + `${chalk.cyan(page)}:${chalk.yellow(lineNumber.toString())}`, + chalk.red + .bold('Error') + .concat(`: Image import "${importedFile}" is not a valid image file`) + ) +} diff --git a/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts b/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts index 45697e9eca6d2..71f468b1d2d56 100644 --- a/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts +++ b/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts @@ -5,7 +5,7 @@ import type { webpack } from 'next/dist/compiled/webpack/webpack' import { getBabelError } from './parseBabel' import { getCssError } from './parseCss' import { getScssError } from './parseScss' -import { getNotFoundError } from './parseNotFoundError' +import { getNotFoundError, getImageError } from './parseNotFoundError' import { SimpleWebpackError } from './simpleWebpackError' import isError from '../../../../lib/is-error' import { getRscError } from './parseRSC' @@ -71,6 +71,16 @@ export async function getModuleBuildError( return notFoundError } + const imageError = await getImageError( + compilation, + input, + sourceFilename, + err + ) + if (imageError !== false) { + return imageError + } + const babel = getBabelError(sourceFilename, err) if (babel !== false) { return babel diff --git a/test/integration/image-future/invalid-image-import/pages/index.js b/test/integration/image-future/invalid-image-import/pages/index.js new file mode 100644 index 0000000000000..9689108c0a8fc --- /dev/null +++ b/test/integration/image-future/invalid-image-import/pages/index.js @@ -0,0 +1,14 @@ +import React from 'react' +import Image from 'next/future/image' +import test from '../public/invalid.svg' + +const Page = () => { + return ( +
+

Try to import and invalid image file

+ +
+ ) +} + +export default Page diff --git a/test/integration/image-future/invalid-image-import/public/invalid.svg b/test/integration/image-future/invalid-image-import/public/invalid.svg new file mode 100644 index 0000000000000..46e5e69a1d4b4 --- /dev/null +++ b/test/integration/image-future/invalid-image-import/public/invalid.svg @@ -0,0 +1,3 @@ +{ + "is-image": "nope" +} \ No newline at end of file diff --git a/test/integration/image-future/invalid-image-import/test/index.test.ts b/test/integration/image-future/invalid-image-import/test/index.test.ts new file mode 100644 index 0000000000000..8dd4982df63d3 --- /dev/null +++ b/test/integration/image-future/invalid-image-import/test/index.test.ts @@ -0,0 +1,71 @@ +/* eslint-env jest */ + +import { join } from 'path' +import { + findPort, + getRedboxHeader, + getRedboxSource, + hasRedbox, + killApp, + launchApp, + nextBuild, + nextStart, +} from 'next-test-utils' +import webdriver from 'next-webdriver' + +const appDir = join(__dirname, '../') +let appPort: number +let app +let stderr = '' +const msg = + 'Error: Image import "../public/invalid.svg" is not a valid image file' + +function runTests({ isDev }) { + it('should show error', async () => { + if (isDev) { + const browser = await webdriver(appPort, '/') + expect(await hasRedbox(browser)).toBe(true) + expect(await getRedboxHeader(browser)).toBe('Failed to compile') + expect(await getRedboxSource(browser)).toBe(`./pages/index.js:3\n${msg}`) + expect(stderr).toContain(msg) + } else { + expect(stderr).toContain(msg) + } + }) +} + +describe('Missing Import Image Tests', () => { + describe('dev mode', () => { + beforeAll(async () => { + stderr = '' + appPort = await findPort() + app = await launchApp(appDir, appPort, { + onStderr(msg) { + stderr += msg || '' + }, + }) + }) + afterAll(async () => { + if (app) { + await killApp(app) + } + }) + + runTests({ isDev: true }) + }) + + describe('server mode', () => { + beforeAll(async () => { + stderr = '' + const result = await nextBuild(appDir, [], { stderr: true }) + stderr = result.stderr + }) + afterAll(async () => { + if (app) { + await killApp(app) + } + }) + + runTests({ isDev: false }) + }) +}) From 3396d5d95631c190ce39340a4a77816bd64a1211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sat, 8 Oct 2022 01:31:16 +0200 Subject: [PATCH 2/4] Apply suggestions from code review --- .../plugins/wellknown-errors-plugin/parseNotFoundError.ts | 1 - .../image-future/invalid-image-import/test/index.test.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts index b0b0bda5e8e67..6893fda76c35a 100644 --- a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts +++ b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts @@ -122,7 +122,6 @@ export async function getNotFoundError( export async function getImageError( compilation: any, input: any, - fileName: string, err: Error ): Promise { if (err.name !== 'InvalidImageFormatError') { diff --git a/test/integration/image-future/invalid-image-import/test/index.test.ts b/test/integration/image-future/invalid-image-import/test/index.test.ts index 8dd4982df63d3..0f4d39928c638 100644 --- a/test/integration/image-future/invalid-image-import/test/index.test.ts +++ b/test/integration/image-future/invalid-image-import/test/index.test.ts @@ -9,7 +9,6 @@ import { killApp, launchApp, nextBuild, - nextStart, } from 'next-test-utils' import webdriver from 'next-webdriver' From d3042caee8657d269ad636674440071c798f0568 Mon Sep 17 00:00:00 2001 From: Steven Date: Sat, 8 Oct 2022 11:56:13 -0400 Subject: [PATCH 3/4] Improve error message --- packages/next/build/webpack/loaders/next-image-loader.js | 9 +-------- .../wellknown-errors-plugin/parseNotFoundError.ts | 4 +++- .../image-future/invalid-image-import/test/index.test.ts | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/next/build/webpack/loaders/next-image-loader.js b/packages/next/build/webpack/loaders/next-image-loader.js index c70234990dd82..a2524d510f2fd 100644 --- a/packages/next/build/webpack/loaders/next-image-loader.js +++ b/packages/next/build/webpack/loaders/next-image-loader.js @@ -11,11 +11,6 @@ function nextImageLoader(content) { const { isServer, isDev, assetPrefix, basePath } = this.getOptions() const context = this.rootContext const opts = { context, content } - const importName = loaderUtils.interpolateName( - this, - '[path][name].[ext]', - opts - ) const interpolatedName = loaderUtils.interpolateName( this, '/static/media/[name].[hash:8].[ext]', @@ -33,9 +28,7 @@ function nextImageLoader(content) { ) if (imageSize instanceof Error) { - const err = new Error( - `Image import "./${importName}" is not a valid image format` - ) + const err = imageSize err.name = 'InvalidImageFormatError' throw err } diff --git a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts index 6893fda76c35a..a33478e7d5de5 100644 --- a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts +++ b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts @@ -145,6 +145,8 @@ export async function getImageError( `${chalk.cyan(page)}:${chalk.yellow(lineNumber.toString())}`, chalk.red .bold('Error') - .concat(`: Image import "${importedFile}" is not a valid image file`) + .concat( + `: Image import "${importedFile}" is not a valid image file. The image may be corrupted or an unsupported format.` + ) ) } diff --git a/test/integration/image-future/invalid-image-import/test/index.test.ts b/test/integration/image-future/invalid-image-import/test/index.test.ts index 0f4d39928c638..74f236c91571a 100644 --- a/test/integration/image-future/invalid-image-import/test/index.test.ts +++ b/test/integration/image-future/invalid-image-import/test/index.test.ts @@ -17,7 +17,7 @@ let appPort: number let app let stderr = '' const msg = - 'Error: Image import "../public/invalid.svg" is not a valid image file' + 'Error: Image import "../public/invalid.svg" is not a valid image file. The image may be corrupted or an unsupported format.' function runTests({ isDev }) { it('should show error', async () => { From bb4947dc6d875606a43ec353282b3b379861c537 Mon Sep 17 00:00:00 2001 From: Steven Date: Sat, 8 Oct 2022 11:57:52 -0400 Subject: [PATCH 4/4] Remove unused vars --- .../plugins/wellknown-errors-plugin/webpackModuleError.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts b/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts index 71f468b1d2d56..b8163ddf59ecc 100644 --- a/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts +++ b/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts @@ -71,12 +71,7 @@ export async function getModuleBuildError( return notFoundError } - const imageError = await getImageError( - compilation, - input, - sourceFilename, - err - ) + const imageError = await getImageError(compilation, input, err) if (imageError !== false) { return imageError }