c3ceeb03ab
This PR updates the `next/future/image` component so that CSS `filter` is never needed. Previously, we used SVG to blur for prod and CSS to blur for dev, but now we use SVG for both. This required a shared function `getImageBlurSvg()` used between both client and server because `next dev` doesn't create Data URIs and instead defers blur generation until request time. So we also need to defer svg generation to request time (on the server) during next dev. This is the first step to removing `<noscript>` completely (see #39736).
94 lines
3.1 KiB
JavaScript
94 lines
3.1 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)
|
|
)
|
|
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.round(
|
|
(imageSize.height / imageSize.width) * BLUR_IMG_SIZE
|
|
)
|
|
} else {
|
|
blurWidth = Math.round(
|
|
(imageSize.width / imageSize.height) * BLUR_IMG_SIZE
|
|
)
|
|
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
|