diff --git a/packages/next/build/webpack/loaders/next-image-loader.js b/packages/next/build/webpack/loaders/next-image-loader.js index a336090874db9..a2524d510f2fd 100644 --- a/packages/next/build/webpack/loaders/next-image-loader.js +++ b/packages/next/build/webpack/loaders/next-image-loader.js @@ -17,7 +17,6 @@ function nextImageLoader(content) { opts ) const outputPath = assetPrefix + '/_next' + interpolatedName - let extension = loaderUtils.interpolateName(this, '[ext]', opts) if (extension === 'jpg') { extension = 'jpeg' @@ -25,8 +24,15 @@ 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 = imageSize + 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..a33478e7d5de5 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,35 @@ export async function getNotFoundError( return input } } + +export async function getImageError( + compilation: any, + input: any, + 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. The image may be corrupted or an unsupported format.` + ) + ) +} 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..b8163ddf59ecc 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,11 @@ export async function getModuleBuildError( return notFoundError } + const imageError = await getImageError(compilation, input, 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..74f236c91571a --- /dev/null +++ b/test/integration/image-future/invalid-image-import/test/index.test.ts @@ -0,0 +1,70 @@ +/* eslint-env jest */ + +import { join } from 'path' +import { + findPort, + getRedboxHeader, + getRedboxSource, + hasRedbox, + killApp, + launchApp, + nextBuild, +} 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. The image may be corrupted or an unsupported format.' + +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 }) + }) +})