9e1d04dd93
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>
102 lines
3.3 KiB
JavaScript
102 lines
3.3 KiB
JavaScript
import loaderUtils from 'next/dist/compiled/loader-utils3'
|
|
import { resizeImage, getImageSize } from '../../../server/image-optimizer'
|
|
|
|
const BLUR_IMG_SIZE = 8
|
|
const BLUR_QUALITY = 70
|
|
const VALID_BLUR_EXT = ['jpeg', 'png', 'webp', 'avif'] // should match next/client/image.tsx
|
|
|
|
function nextImageLoader(content) {
|
|
const imageLoaderSpan = this.currentTraceSpan.traceChild('next-image-loader')
|
|
return imageLoaderSpan.traceAsyncFn(async () => {
|
|
const { isServer, isDev, assetPrefix, basePath } = this.getOptions()
|
|
const context = this.rootContext
|
|
const opts = { context, content }
|
|
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'
|
|
}
|
|
|
|
const imageSizeSpan = imageLoaderSpan.traceChild('image-size-calculation')
|
|
const imageSize = await imageSizeSpan.traceAsyncFn(() =>
|
|
getImageSize(content, extension).catch((err) => err)
|
|
)
|
|
|
|
if (imageSize instanceof Error) {
|
|
const err = imageSize
|
|
err.name = 'InvalidImageFormatError'
|
|
throw err
|
|
}
|
|
|
|
let blurDataURL
|
|
let blurWidth
|
|
let blurHeight
|
|
|
|
if (VALID_BLUR_EXT.includes(extension)) {
|
|
// Shrink the image's largest dimension
|
|
if (imageSize.width >= imageSize.height) {
|
|
blurWidth = BLUR_IMG_SIZE
|
|
blurHeight = Math.max(
|
|
Math.round((imageSize.height / imageSize.width) * BLUR_IMG_SIZE),
|
|
1
|
|
)
|
|
} else {
|
|
blurWidth = Math.max(
|
|
Math.round((imageSize.width / imageSize.height) * BLUR_IMG_SIZE),
|
|
1
|
|
)
|
|
blurHeight = BLUR_IMG_SIZE
|
|
}
|
|
|
|
if (isDev) {
|
|
// During `next dev`, we don't want to generate blur placeholders with webpack
|
|
// because it can delay starting the dev server. Instead, we inline a
|
|
// special url to lazily generate the blur placeholder at request time.
|
|
const prefix = 'http://localhost'
|
|
const url = new URL(`${basePath || ''}/_next/image`, prefix)
|
|
url.searchParams.set('url', outputPath)
|
|
url.searchParams.set('w', blurWidth)
|
|
url.searchParams.set('q', BLUR_QUALITY)
|
|
blurDataURL = url.href.slice(prefix.length)
|
|
} else {
|
|
const resizeImageSpan = imageLoaderSpan.traceChild('image-resize')
|
|
const resizedImage = await resizeImageSpan.traceAsyncFn(() =>
|
|
resizeImage(content, blurWidth, blurHeight, extension, BLUR_QUALITY)
|
|
)
|
|
const blurDataURLSpan = imageLoaderSpan.traceChild(
|
|
'image-base64-tostring'
|
|
)
|
|
blurDataURL = blurDataURLSpan.traceFn(
|
|
() =>
|
|
`data:image/${extension};base64,${resizedImage.toString('base64')}`
|
|
)
|
|
}
|
|
}
|
|
|
|
const stringifiedData = imageLoaderSpan
|
|
.traceChild('image-data-stringify')
|
|
.traceFn(() =>
|
|
JSON.stringify({
|
|
src: outputPath,
|
|
height: imageSize.height,
|
|
width: imageSize.width,
|
|
blurDataURL,
|
|
blurWidth,
|
|
blurHeight,
|
|
})
|
|
)
|
|
|
|
if (!isServer) {
|
|
this.emitFile(interpolatedName, content, null)
|
|
}
|
|
|
|
return `export default ${stringifiedData};`
|
|
})
|
|
}
|
|
export const raw = true
|
|
export default nextImageLoader
|