Skip to content

Commit

Permalink
Add pretty error when image import is invalid format (vercel#41267)
Browse files Browse the repository at this point in the history
This PR improves the error message when an invalid image is imported. It
also ensures the page that imported the image is displayed.

### Before

<img width="1062" alt="image"
src="https://user-images.githubusercontent.com/229881/194674177-70f4ae73-64c9-497f-8e20-098f949a4219.png">


### After

<img width="1002" alt="image"
src="https://user-images.githubusercontent.com/229881/194716285-27dd3455-60a9-440f-a50e-eff8d49e764b.png">

Co-authored-by: Balázs Orbán <info@balazsorban.com>
  • Loading branch information
2 people authored and Kikobeats committed Oct 24, 2022
1 parent 91a3b3a commit 88e42cf
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 3 deletions.
10 changes: 8 additions & 2 deletions packages/next/build/webpack/loaders/next-image-loader.js
Expand Up @@ -17,16 +17,22 @@ function nextImageLoader(content) {
opts
)
const outputPath = assetPrefix + '/_next' + interpolatedName

let extension = loaderUtils.interpolateName(this, '[ext]', opts)
if (extension === 'jpg') {
extension = 'jpeg'
}

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
Expand Down
Expand Up @@ -118,3 +118,35 @@ export async function getNotFoundError(
return input
}
}

export async function getImageError(
compilation: any,
input: any,
err: Error
): Promise<SimpleWebpackError | false> {
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.`
)
)
}
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions 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 (
<div>
<h1>Try to import and invalid image file</h1>
<Image id="invalid-img" src={test} width={400} height={400} />
</div>
)
}

export default Page
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -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 })
})
})

0 comments on commit 88e42cf

Please sign in to comment.