cc1f3b8a38
Add support for AVIF to `next/image` - Fixes #27882 - Closes #27432 ## Feature - [x] Implements an existing feature request - [x] Related issues linked - [x] Integration tests added - [x] Documentation added - [x] Update manifest output - [x] Warn when `sharp` is outdated - [x] Errors & Warnings have helpful link attached - [ ] Remove `image-size` in favor of `squoosh`/`sharp` (optional, need to benchmark)
85 lines
2.7 KiB
JavaScript
85 lines
2.7 KiB
JavaScript
import loaderUtils from 'next/dist/compiled/loader-utils'
|
|
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 } =
|
|
loaderUtils.getOptions(this)
|
|
const context = this.rootContext
|
|
const opts = { context, content }
|
|
const interpolatedName = loaderUtils.interpolateName(
|
|
this,
|
|
'/static/image/[path][name].[hash].[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
|
|
|
|
if (VALID_BLUR_EXT.includes(extension)) {
|
|
if (isDev) {
|
|
const prefix = 'http://localhost'
|
|
const url = new URL(`${basePath || ''}/_next/image`, prefix)
|
|
url.searchParams.set('url', outputPath)
|
|
url.searchParams.set('w', BLUR_IMG_SIZE)
|
|
url.searchParams.set('q', BLUR_QUALITY)
|
|
blurDataURL = url.href.slice(prefix.length)
|
|
} else {
|
|
// Shrink the image's largest dimension
|
|
const dimension =
|
|
imageSize.width >= imageSize.height ? 'width' : 'height'
|
|
|
|
const resizeImageSpan = imageLoaderSpan.traceChild('image-resize')
|
|
const resizedImage = await resizeImageSpan.traceAsyncFn(() =>
|
|
resizeImage(
|
|
content,
|
|
dimension,
|
|
BLUR_IMG_SIZE,
|
|
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,
|
|
})
|
|
)
|
|
|
|
if (!isServer) {
|
|
this.emitFile(interpolatedName, content, null)
|
|
}
|
|
|
|
return `export default ${stringifiedData};`
|
|
})
|
|
}
|
|
export const raw = true
|
|
export default nextImageLoader
|