Skip to content

Commit

Permalink
Remove experimental image optimization feature (#34349)
Browse files Browse the repository at this point in the history
This PR removes the experimental `optimizeImages` flag. This feature was designed to automatically add preload tags for images, but I was never able to get it to do a very good job of selecting the images that actually need preloading.

This feature never graduated from experimental and in fact we never even publicized it as an experimental feature for people to try.

Additionally, even if someone was using this feature, it wouldn't have a functional effect, only a performance effect (removal of some preloads).

For those reasons, I believe it is safe to remove this functionality and that it is not a breaking change.
  • Loading branch information
atcastle committed Feb 15, 2022
1 parent c20e829 commit f516304
Show file tree
Hide file tree
Showing 17 changed files with 2 additions and 342 deletions.
4 changes: 0 additions & 4 deletions packages/next/build/webpack-config.ts
Expand Up @@ -1346,9 +1346,6 @@ export default async function getBaseWebpackConfig(
'process.env.__NEXT_OPTIMIZE_FONTS': JSON.stringify(
config.optimizeFonts && !dev
),
'process.env.__NEXT_OPTIMIZE_IMAGES': JSON.stringify(
config.experimental.optimizeImages
),
'process.env.__NEXT_OPTIMIZE_CSS': JSON.stringify(
config.experimental.optimizeCss && !dev
),
Expand Down Expand Up @@ -1611,7 +1608,6 @@ export default async function getBaseWebpackConfig(
reactStrictMode: config.reactStrictMode,
reactMode: config.experimental.reactMode,
optimizeFonts: config.optimizeFonts,
optimizeImages: config.experimental.optimizeImages,
optimizeCss: config.experimental.optimizeCss,
scrollRestoration: config.experimental.scrollRestoration,
basePath: config.basePath,
Expand Down
Expand Up @@ -191,7 +191,6 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
locale: detectedLocale,
defaultLocale,
domainLocales: i18n?.domains,
optimizeImages: process.env.__NEXT_OPTIMIZE_IMAGES,
optimizeCss: process.env.__NEXT_OPTIMIZE_CSS,
crossOrigin: process.env.__NEXT_CROSS_ORIGIN,
},
Expand Down
2 changes: 0 additions & 2 deletions packages/next/export/index.ts
Expand Up @@ -385,7 +385,6 @@ export default async function exportApp(
crossOrigin: nextConfig.crossOrigin,
optimizeCss: nextConfig.experimental.optimizeCss,
optimizeFonts: nextConfig.optimizeFonts,
optimizeImages: nextConfig.experimental.optimizeImages,
reactRoot: nextConfig.experimental.reactRoot || false,
}

Expand Down Expand Up @@ -583,7 +582,6 @@ export default async function exportApp(
buildExport: options.buildExport,
serverless: isTargetLikeServerless(nextConfig.target),
optimizeFonts: nextConfig.optimizeFonts,
optimizeImages: nextConfig.experimental.optimizeImages,
optimizeCss: nextConfig.experimental.optimizeCss,
disableOptimizedLoading:
nextConfig.experimental.disableOptimizedLoading,
Expand Down
9 changes: 0 additions & 9 deletions packages/next/export/worker.ts
Expand Up @@ -55,7 +55,6 @@ interface ExportPageInput {
subFolders?: boolean
serverless: boolean
optimizeFonts: boolean
optimizeImages?: boolean
optimizeCss: any
disableOptimizedLoading: any
parentSpanId: any
Expand All @@ -77,7 +76,6 @@ interface RenderOpts {
ampValidatorPath?: string
ampSkipValidation?: boolean
optimizeFonts?: boolean
optimizeImages?: boolean
disableOptimizedLoading?: boolean
optimizeCss?: any
fontManifest?: FontManifest
Expand Down Expand Up @@ -105,7 +103,6 @@ export default async function exportPage({
subFolders,
serverless,
optimizeFonts,
optimizeImages,
optimizeCss,
disableOptimizedLoading,
httpAgentOptions,
Expand Down Expand Up @@ -304,8 +301,6 @@ export default async function exportPage({
/// @ts-ignore
optimizeFonts,
/// @ts-ignore
optimizeImages,
/// @ts-ignore
optimizeCss,
disableOptimizedLoading,
distDir,
Expand Down Expand Up @@ -367,9 +362,6 @@ export default async function exportPage({
if (optimizeFonts) {
process.env.__NEXT_OPTIMIZE_FONTS = JSON.stringify(true)
}
if (optimizeImages) {
process.env.__NEXT_OPTIMIZE_IMAGES = JSON.stringify(true)
}
if (optimizeCss) {
process.env.__NEXT_OPTIMIZE_CSS = JSON.stringify(true)
}
Expand All @@ -379,7 +371,6 @@ export default async function exportPage({
ampPath: renderAmpPath,
params,
optimizeFonts,
optimizeImages,
optimizeCss,
disableOptimizedLoading,
fontManifest: optimizeFonts
Expand Down
2 changes: 0 additions & 2 deletions packages/next/pages/_document.tsx
Expand Up @@ -494,7 +494,6 @@ export class Head extends Component<
useMaybeDeferContent,
optimizeCss,
optimizeFonts,
optimizeImages,
runtime,
} = this.context

Expand Down Expand Up @@ -737,7 +736,6 @@ export class Head extends Component<
)}
{!optimizeCss && this.getCssLinks(files)}
{!optimizeCss && <noscript data-n-css={this.props.nonce ?? ''} />}
{optimizeImages && <meta name="next-image-preload" />}

{!isDeferred && getDynamicScriptPreloads()}

Expand Down
2 changes: 0 additions & 2 deletions packages/next/server/base-server.ts
Expand Up @@ -152,7 +152,6 @@ export default abstract class Server {
optimizeFonts: boolean
images: ImageConfigComplete
fontManifest?: FontManifest
optimizeImages: boolean
disableOptimizedLoading?: boolean
optimizeCss: any
locale?: string
Expand Down Expand Up @@ -314,7 +313,6 @@ export default abstract class Server {
this.nextConfig.optimizeFonts && !dev
? this.getFontManifest()
: undefined,
optimizeImages: !!this.nextConfig.experimental.optimizeImages,
optimizeCss: this.nextConfig.experimental.optimizeCss,
disableOptimizedLoading: this.nextConfig.experimental.runtime
? true
Expand Down
2 changes: 0 additions & 2 deletions packages/next/server/config-shared.ts
Expand Up @@ -85,7 +85,6 @@ export interface ExperimentalConfig {
reactMode?: 'legacy' | 'concurrent' | 'blocking'
workerThreads?: boolean
pageEnv?: boolean
optimizeImages?: boolean
optimizeCss?: boolean
scrollRestoration?: boolean
externalDir?: boolean
Expand Down Expand Up @@ -454,7 +453,6 @@ export const defaultConfig: NextConfig = {
isrFlushToDisk: true,
workerThreads: false,
pageEnv: false,
optimizeImages: false,
optimizeCss: false,
scrollRestoration: false,
externalDir: false,
Expand Down
6 changes: 1 addition & 5 deletions packages/next/server/next-server.ts
Expand Up @@ -100,15 +100,11 @@ export default class NextNodeServer extends BaseServer {
/**
* This sets environment variable to be used at the time of SSR by head.tsx.
* Using this from process.env allows targeting both serverless and SSR by calling
* `process.env.__NEXT_OPTIMIZE_IMAGES`.
* TODO(atcastle@): Remove this when experimental.optimizeImages are being cleaned up.
* `process.env.__NEXT_OPTIMIZE_CSS`.
*/
if (this.renderOpts.optimizeFonts) {
process.env.__NEXT_OPTIMIZE_FONTS = JSON.stringify(true)
}
if (this.renderOpts.optimizeImages) {
process.env.__NEXT_OPTIMIZE_IMAGES = JSON.stringify(true)
}
if (this.renderOpts.optimizeCss) {
process.env.__NEXT_OPTIMIZE_CSS = JSON.stringify(true)
}
Expand Down
7 changes: 1 addition & 6 deletions packages/next/server/render.tsx
Expand Up @@ -215,7 +215,6 @@ export type RenderOptsPartial = {
unstable_JsPreload?: false
optimizeFonts: boolean
fontManifest?: FontManifest
optimizeImages: boolean
optimizeCss: any
devOnlyCacheBusterQueryString?: string
resolvedUrl?: string
Expand Down Expand Up @@ -1406,7 +1405,6 @@ export async function renderToHTML(
crossOrigin: renderOpts.crossOrigin,
optimizeCss: renderOpts.optimizeCss,
optimizeFonts: renderOpts.optimizeFonts,
optimizeImages: renderOpts.optimizeImages,
runtime,
}

Expand Down Expand Up @@ -1485,16 +1483,13 @@ export async function renderToHTML(
return html
}
: null,
!process.browser &&
(process.env.__NEXT_OPTIMIZE_FONTS ||
process.env.__NEXT_OPTIMIZE_IMAGES)
!process.browser && process.env.__NEXT_OPTIMIZE_FONTS
? async (html: string) => {
return await postProcess(
html,
{ getFontDefinition },
{
optimizeFonts: renderOpts.optimizeFonts,
optimizeImages: renderOpts.optimizeImages,
}
)
}
Expand Down
108 changes: 0 additions & 108 deletions packages/next/shared/lib/post-process.ts
@@ -1,14 +1,10 @@
import { escapeStringRegexp } from './escape-regexp'
import { parse, HTMLElement } from 'next/dist/compiled/node-html-parser'
import { OPTIMIZED_FONT_PROVIDERS } from './constants'

// const MIDDLEWARE_TIME_BUDGET = parseInt(process.env.__POST_PROCESS_MIDDLEWARE_TIME_BUDGET || '', 10) || 10
const MAXIMUM_IMAGE_PRELOADS = 2
const IMAGE_PRELOAD_SIZE_THRESHOLD = 2500

type postProcessOptions = {
optimizeFonts: boolean
optimizeImages: boolean
}

type renderOptions = {
Expand Down Expand Up @@ -169,103 +165,6 @@ class FontOptimizerMiddleware implements PostProcessMiddleware {
}
}

class ImageOptimizerMiddleware implements PostProcessMiddleware {
inspect(originalDom: HTMLElement) {
const imgPreloads = []
const imgElements = originalDom.querySelectorAll('img')
let eligibleImages: Array<HTMLElement> = []
for (let i = 0; i < imgElements.length; i++) {
if (isImgEligible(imgElements[i])) {
eligibleImages.push(imgElements[i])
}
if (eligibleImages.length >= MAXIMUM_IMAGE_PRELOADS) {
break
}
}

for (const imgEl of eligibleImages) {
const src = imgEl.getAttribute('src')
if (src) {
imgPreloads.push(src)
}
}

return imgPreloads
}
mutate = async (markup: string, imgPreloads: string[]) => {
let result = markup
let imagePreloadTags = imgPreloads
.filter((imgHref) => !preloadTagAlreadyExists(markup, imgHref))
.reduce(
(acc, imgHref) =>
acc + `<link rel="preload" href="${imgHref}" as="image"/>`,
''
)
return result.replace('<meta name="next-image-preload"/>', imagePreloadTags)
}
}

function isImgEligible(imgElement: HTMLElement): boolean {
let imgSrc = imgElement.getAttribute('src')
return (
!!imgSrc &&
sourceIsSupportedType(imgSrc) &&
imageIsNotTooSmall(imgElement) &&
imageIsNotHidden(imgElement)
)
}

function preloadTagAlreadyExists(html: string, href: string) {
const escapedHref = escapeStringRegexp(href)
const regex = new RegExp(`<link[^>]*href[^>]*${escapedHref}`)
return html.match(regex)
}

function imageIsNotTooSmall(imgElement: HTMLElement): boolean {
// Skip images without both height and width--we don't know enough to say if
// they are too small
if (
!(imgElement.hasAttribute('height') && imgElement.hasAttribute('width'))
) {
return true
}
try {
const heightAttr = imgElement.getAttribute('height')
const widthAttr = imgElement.getAttribute('width')
if (!heightAttr || !widthAttr) {
return true
}

if (
parseInt(heightAttr) * parseInt(widthAttr) <=
IMAGE_PRELOAD_SIZE_THRESHOLD
) {
return false
}
} catch (err) {
return true
}
return true
}

// Traverse up the dom from each image to see if it or any of it's
// ancestors have the hidden attribute.
function imageIsNotHidden(imgElement: HTMLElement): boolean {
let activeElement = imgElement
while (activeElement.parentNode) {
if (activeElement.hasAttribute('hidden')) {
return false
}
activeElement = activeElement.parentNode as HTMLElement
}
return true
}

// Currently only filters out svg images--could be made more specific in the future.
function sourceIsSupportedType(imgSrc: string): boolean {
return !imgSrc.includes('.svg')
}

// Initialization
registerPostProcessor(
'Inline-Fonts',
Expand All @@ -275,11 +174,4 @@ registerPostProcessor(
(options) => options.optimizeFonts || process.env.__NEXT_OPTIMIZE_FONTS
)

registerPostProcessor(
'Preload Images',
new ImageOptimizerMiddleware(),
// @ts-ignore
(options) => options.optimizeImages || process.env.__NEXT_OPTIMIZE_IMAGES
)

export default processHTML
1 change: 0 additions & 1 deletion packages/next/shared/lib/utils.ts
Expand Up @@ -224,7 +224,6 @@ export type HtmlProps = {
crossOrigin?: string
optimizeCss?: boolean
optimizeFonts?: boolean
optimizeImages?: boolean
runtime?: 'edge' | 'nodejs'
}

Expand Down
4 changes: 0 additions & 4 deletions test/integration/image-optimization/next.config.js

This file was deleted.

25 changes: 0 additions & 25 deletions test/integration/image-optimization/pages/index.js

This file was deleted.

30 changes: 0 additions & 30 deletions test/integration/image-optimization/pages/stars.js

This file was deleted.

0 comments on commit f516304

Please sign in to comment.