369d6b76c4
In #41548, I show that I would like to provide an object with images in getStaticProps. The StaticImageData is parsed correctly and provided as a prop to the page. Nonetheless, the image is not available in the static directory. Therefore the image is not shown. This is also addressed in issue #29571. The underlying cause is that the import of the image is removed from the client bundle and only present in the server bundle. Evaluating the next-image-loader shows that the file is only placed in the static directory if emitted from the client bundle by firing this.emitFile. By changing this to only emitting the file from the serverside bundle in the webpackloader, static images loaded in the getStaticProps are made available properly as well as images directly used in componts (so present in server and client bundle). This would PR would prevent the circumventing solution which enforces that the StaticImageData should be present in the client side bundle while it will also be present in the staticprops. <!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change that you're making: --> ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` Closes #42783, Fixes #42443 Co-authored-by: Diederik <diederik@digitalpatrol.nl>
106 lines
3.4 KiB
JavaScript
106 lines
3.4 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(
|
|
`../${isDev ? '' : '../'}${interpolatedName}`,
|
|
content,
|
|
null
|
|
)
|
|
}
|
|
|
|
return `export default ${stringifiedData};`
|
|
})
|
|
}
|
|
export const raw = true
|
|
export default nextImageLoader
|