mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
feat: add experiment for sharpjs cpu flags (#71733)
This PR adds two experimental flags to configure sharpjs settings that may improve cpu for image optimization.
This commit is contained in:
@@ -314,6 +314,9 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
|
||||
forceSwcTransforms: z.boolean().optional(),
|
||||
fullySpecified: z.boolean().optional(),
|
||||
gzipSize: z.boolean().optional(),
|
||||
imgOptConcurrency: z.number().int().optional().nullable(),
|
||||
imgOptTimeoutInSeconds: z.number().int().optional(),
|
||||
imgOptMaxInputPixels: z.number().int().optional(),
|
||||
internal_disableSyncDynamicAPIWarnings: z.boolean().optional(),
|
||||
isrFlushToDisk: z.boolean().optional(),
|
||||
largePageDataBytes: z.number().optional(),
|
||||
|
||||
@@ -286,6 +286,9 @@ export interface ExperimentalConfig {
|
||||
extensionAlias?: Record<string, any>
|
||||
allowedRevalidateHeaderKeys?: string[]
|
||||
fetchCacheKeyPrefix?: string
|
||||
imgOptConcurrency?: number | null
|
||||
imgOptTimeoutInSeconds?: number
|
||||
imgOptMaxInputPixels?: number
|
||||
optimisticClientCache?: boolean
|
||||
/**
|
||||
* @deprecated use config.expireTime instead
|
||||
@@ -1112,6 +1115,9 @@ export const defaultConfig: NextConfig = {
|
||||
(os.cpus() || { length: 1 }).length) - 1
|
||||
),
|
||||
memoryBasedWorkersCount: false,
|
||||
imgOptConcurrency: null,
|
||||
imgOptTimeoutInSeconds: 7,
|
||||
imgOptMaxInputPixels: 268_402_689, // https://sharp.pixelplumbing.com/api-constructor#:~:text=%5Boptions.limitInputPixels%5D
|
||||
isrFlushToDisk: true,
|
||||
workerThreads: false,
|
||||
proxyTimeout: undefined,
|
||||
|
||||
@@ -47,7 +47,7 @@ const BLUR_QUALITY = 70 // should match `next-image-loader`
|
||||
|
||||
let _sharp: typeof import('sharp')
|
||||
|
||||
function getSharp() {
|
||||
function getSharp(concurrency: number | null | undefined) {
|
||||
if (_sharp) {
|
||||
return _sharp
|
||||
}
|
||||
@@ -59,7 +59,7 @@ function getSharp() {
|
||||
// https://sharp.pixelplumbing.com/api-utility#concurrency
|
||||
const divisor = process.env.NODE_ENV === 'development' ? 4 : 2
|
||||
_sharp.concurrency(
|
||||
Math.floor(Math.max(_sharp.concurrency() / divisor, 1))
|
||||
concurrency ?? Math.floor(Math.max(_sharp.concurrency() / divisor, 1))
|
||||
)
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
@@ -512,15 +512,27 @@ export async function optimizeImage({
|
||||
quality,
|
||||
width,
|
||||
height,
|
||||
concurrency,
|
||||
limitInputPixels,
|
||||
timeoutInSeconds,
|
||||
}: {
|
||||
buffer: Buffer
|
||||
contentType: string
|
||||
quality: number
|
||||
width: number
|
||||
height?: number
|
||||
concurrency?: number | null
|
||||
limitInputPixels?: number
|
||||
timeoutInSeconds?: number
|
||||
}): Promise<Buffer> {
|
||||
const sharp = getSharp()
|
||||
const transformer = sharp(buffer).timeout({ seconds: 7 }).rotate()
|
||||
const sharp = getSharp(concurrency)
|
||||
const transformer = sharp(buffer, {
|
||||
limitInputPixels,
|
||||
})
|
||||
.timeout({
|
||||
seconds: timeoutInSeconds ?? 7,
|
||||
})
|
||||
.rotate()
|
||||
|
||||
if (height) {
|
||||
transformer.resize(width, height)
|
||||
@@ -631,6 +643,10 @@ export async function imageOptimizer(
|
||||
'href' | 'width' | 'quality' | 'mimeType'
|
||||
>,
|
||||
nextConfig: {
|
||||
experimental: Pick<
|
||||
NextConfigComplete['experimental'],
|
||||
'imgOptConcurrency' | 'imgOptMaxInputPixels' | 'imgOptTimeoutInSeconds'
|
||||
>
|
||||
images: Pick<
|
||||
NextConfigComplete['images'],
|
||||
'dangerouslyAllowSVG' | 'minimumCacheTTL'
|
||||
@@ -735,6 +751,9 @@ export async function imageOptimizer(
|
||||
contentType,
|
||||
quality,
|
||||
width,
|
||||
concurrency: nextConfig.experimental.imgOptConcurrency,
|
||||
limitInputPixels: nextConfig.experimental.imgOptMaxInputPixels,
|
||||
timeoutInSeconds: nextConfig.experimental.imgOptTimeoutInSeconds,
|
||||
})
|
||||
if (optimizedBuffer) {
|
||||
if (isDev && width <= BLUR_IMG_SIZE && quality === BLUR_QUALITY) {
|
||||
|
||||
@@ -734,6 +734,37 @@ describe('Image Optimizer', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('experimental.imgOptMaxInputPixels in next.config.js', () => {
|
||||
let app
|
||||
let appPort
|
||||
|
||||
beforeAll(async () => {
|
||||
nextConfig.replace(
|
||||
'{ /* replaceme */ }',
|
||||
JSON.stringify({
|
||||
experimental: {
|
||||
imgOptMaxInputPixels: 100,
|
||||
},
|
||||
})
|
||||
)
|
||||
await cleanImagesDir({ imagesDir })
|
||||
appPort = await findPort()
|
||||
app = await launchApp(appDir, appPort)
|
||||
})
|
||||
afterAll(async () => {
|
||||
await killApp(app)
|
||||
nextConfig.restore()
|
||||
})
|
||||
it('should fallback to source image when input exceeds imgOptMaxInputPixels', async () => {
|
||||
const size = 256 // defaults defined in lib/image-config.ts
|
||||
const query = { w: size, q: 75, url: '/test.jpg' }
|
||||
const opts = { headers: { accept: 'image/webp' } }
|
||||
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
|
||||
})
|
||||
})
|
||||
|
||||
describe('External rewrite support with for serving static content in images', () => {
|
||||
;(process.env.TURBOPACK_DEV ? describe.skip : describe)(
|
||||
'production mode',
|
||||
|
||||
Reference in New Issue
Block a user