From 889a3c85de6f6b66f484076e2ecceceae7b3e4f3 Mon Sep 17 00:00:00 2001 From: atcastle Date: Wed, 9 Oct 2019 10:10:11 -0700 Subject: [PATCH 01/49] Update framework chunk test regex to not select nested dependencies --- packages/next/build/webpack-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 73bf5e756b96..84cab3abb8ce 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -234,7 +234,7 @@ export default async function getBaseWebpackConfig( // TODO(atcastle): Analyze if other cache groups should be set to 'all' as well chunks: 'all', name: 'framework', - test: /[\\/]node_modules[\\/](react|react-dom|scheduler|prop-types)[\\/]/, + test: /(? Date: Mon, 14 Oct 2019 13:52:05 -0400 Subject: [PATCH 02/49] Update webpack-config.ts --- packages/next/build/webpack-config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 84cab3abb8ce..c53682dee469 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -234,6 +234,9 @@ export default async function getBaseWebpackConfig( // TODO(atcastle): Analyze if other cache groups should be set to 'all' as well chunks: 'all', name: 'framework', + // This regex ignores nested copies of framework libraries so they're + // bundled with their issuer. + // https://github.com/zeit/next.js/pull/9012 test: /(? Date: Mon, 14 Feb 2022 09:57:41 -0800 Subject: [PATCH 03/49] Add support for raw layout mode --- packages/next/client/image.tsx | 87 +++++++++++++++++-- .../default/pages/layout-raw.js | 48 ++++++++++ .../default/pages/on-loading-complete.js | 10 +++ .../default/test/index.test.js | 38 ++++++++ 4 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 test/integration/image-component/default/pages/layout-raw.js diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index a634db7a22dd..773d8f8f4a7b 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -48,6 +48,7 @@ const VALID_LAYOUT_VALUES = [ 'fixed', 'intrinsic', 'responsive', + 'raw', undefined, ] as const type LayoutValue = typeof VALID_LAYOUT_VALUES[number] @@ -107,7 +108,15 @@ export type ImageProps = Omit< objectFit?: ImgElementStyle['objectFit'] objectPosition?: ImgElementStyle['objectPosition'] onLoadingComplete?: OnLoadingComplete -} +} & ( + | { + layout?: Omit + } + | { + layout: 'raw' + style: ImgElementStyle + } + ) function getWidths( { deviceSizes, allSizes }: ImageConfig, @@ -115,7 +124,10 @@ function getWidths( layout: LayoutValue, sizes: string | undefined ): { widths: number[]; kind: 'w' | 'x' } { - if (sizes && (layout === 'fill' || layout === 'responsive')) { + if ( + sizes && + (layout === 'fill' || layout === 'responsive' || layout === 'raw') + ) { // Find all the "vw" percent sizes used in the sizes prop const viewportWidthRe = /(^|\s)(1?\d?\d)vw/g const percentSizes = [] @@ -134,7 +146,8 @@ function getWidths( if ( typeof width !== 'number' || layout === 'fill' || - layout === 'responsive' + layout === 'responsive' || + layout === 'raw' ) { return { widths: deviceSizes, kind: 'w' } } @@ -425,9 +438,14 @@ export default function Image({ `Image with src "${src}" has both "priority" and "loading='lazy'" properties. Only one should be used.` ) } - if (sizes && layout !== 'fill' && layout !== 'responsive') { + if ( + sizes && + layout !== 'fill' && + layout !== 'responsive' && + layout !== 'raw' + ) { console.warn( - `Image with src "${src}" has "sizes" property but it will be ignored. Only use "sizes" with "layout='fill'" or "layout='responsive'".` + `Image with src "${src}" has "sizes" property but it will be ignored. Only use "sizes" with 'fill,' 'responsive,' or 'raw' layout modes.` ) } if (placeholder === 'blur') { @@ -456,7 +474,7 @@ export default function Image({ `Image with src "${src}" is using unsupported "ref" property. Consider using the "onLoadingComplete" property instead.` ) } - if ('style' in rest) { + if ('style' in rest && layout !== 'raw') { console.warn( `Image with src "${src}" is using unsupported "style" property. Please use the "className" property instead.` ) @@ -638,6 +656,22 @@ export default function Image({ }) } + let isRaw = false + const rawStyle: ImgElementStyle = {} + + if (layout === 'raw') { + isRaw = true + const styleProp = 'style' in rest && rest.style ? rest.style : {} + Object.assign( + rawStyle, + { + ...(objectFit ? { objectFit } : {}), + ...(objectPosition ? { objectPosition } : {}), + }, + styleProp + ) + } + let srcString: string = src if (process.env.NODE_ENV !== 'production') { @@ -680,7 +714,46 @@ export default function Image({ handleLoading(imgRef, srcString, layout, placeholder, onLoadingCompleteRef) }, [srcString, layout, placeholder, isVisible]) - return ( + return isRaw ? ( + <> + + {isLazy && ( + + )} + + ) : ( {hasSizer ? ( diff --git a/test/integration/image-component/default/pages/layout-raw.js b/test/integration/image-component/default/pages/layout-raw.js new file mode 100644 index 000000000000..c205b9d83224 --- /dev/null +++ b/test/integration/image-component/default/pages/layout-raw.js @@ -0,0 +1,48 @@ +import React from 'react' +import Image from 'next/image' + +const Page = () => { + return ( +
+

Layout Raw

+
+ +
+
+ +
+
+ +
+
+ ) +} + +export default Page diff --git a/test/integration/image-component/default/pages/on-loading-complete.js b/test/integration/image-component/default/pages/on-loading-complete.js index 88ad4cc2a54a..11418881fdf3 100644 --- a/test/integration/image-component/default/pages/on-loading-complete.js +++ b/test/integration/image-component/default/pages/on-loading-complete.js @@ -84,6 +84,16 @@ const Page = () => { idToCount={idToCount} setIdToCount={setIdToCount} /> + + + diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index 8267389e1c94..8382c20cc493 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -273,6 +273,10 @@ function runTests(mode) { () => browser.eval(`document.getElementById("img8").currentSrc`), /test-rect.jpg/ ) + await check( + () => browser.eval(`document.getElementById("msg9").textContent`), + 'loaded 1 img9 with dimensions 266x266' + ) } finally { if (browser) { await browser.close() @@ -584,6 +588,40 @@ function runTests(mode) { } }) + it('should render no wrappers or sizers and minimal styling with layout-raw', async () => { + let browser + try { + browser = await webdriver(appPort, '/layout-raw') + + const numberOfChildren = await browser.eval( + `document.getElementById('image-container1').children.length` + ) + expect(numberOfChildren).toBe(1) + const childElementType = await browser.eval( + `document.getElementById('image-container1').children[0].nodeName` + ) + expect(childElementType).toBe('IMG') + + expect(await browser.elementById('raw1').getAttribute('style')).toBe( + 'object-fit:cover' + ) + + expect(await browser.elementById('raw2').getAttribute('style')).toBe( + 'object-fit:cover;object-position:30% 30%;padding-left:4rem;width:100%' + ) + + expect(await browser.elementById('raw3').getAttribute('style')).toBeNull() + + const warnings = (await browser.log('browser')) + .map((log) => log.message) + .join('\n') + } finally { + if (browser) { + await browser.close() + } + } + }) + if (mode === 'dev') { it('should show missing src error', async () => { const browser = await webdriver(appPort, '/missing-src') From 4c769d3e62dc30a225fcbf5ed3cb63c16f39a6d7 Mon Sep 17 00:00:00 2001 From: atcastle Date: Mon, 14 Feb 2022 10:27:56 -0800 Subject: [PATCH 04/49] Update image documentation for raw layout --- docs/api-reference/next/image.md | 11 +++++++++-- docs/basic-features/image-optimization.md | 5 ++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 8ed846ff7403..5a1879d747eb 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -16,6 +16,7 @@ description: Enable Image Optimization with the built-in Image component. | Version | Changes | | --------- | ------------------------------------------------------------------------------------------------- | +| `v12.0.11`| `raw` layout added | | `v12.0.9` | `lazyRoot` prop added | | `v12.0.0` | `formats` configuration added.
AVIF support added.
Wrapper `
` changed to ``. | | `v11.1.0` | `onLoadingComplete` and `lazyBoundary` props added. | @@ -70,6 +71,8 @@ The layout behavior of the image as the viewport changes size. | `fixed` | Sized to `width` and `height` exactly | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | | `responsive` | Scale to fit width of container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | | `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | +| `raw` | Insert the image element with no responsive behavior | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | + - [Demo the `intrinsic` layout (default)](https://image-component.nextjs.gallery/layout-intrinsic) - When `intrinsic`, the image will scale the dimensions down for smaller viewports, but maintain the original dimensions for larger viewports. @@ -82,6 +85,10 @@ The layout behavior of the image as the viewport changes size. - When `fill`, the image will stretch both width and height to the dimensions of the parent element, provided the parent element is relative. - This is usually paired with the [`objectFit`](#objectFit) property. - Ensure the parent element has `position: relative` in their stylesheet. +- When `raw`, the image will be rendered as a single image element with no wrappers, sizers or other responsive behavior. + - Unlike other layout modes, a `raw` image will pass through the `style` property to the underlying image. + - If your image styling will change the size of a `raw` image, you should include the `sizes` property for proper image serving. + - The other layout modes are optimized for performance and should cover nearly all use cases. It is recommended to try to use those modes before using `raw`. - [Demo background image](https://image-component.nextjs.gallery/background) ### loader @@ -120,7 +127,7 @@ const MyImage = (props) => { A string that provides information about how wide the image will be at different breakpoints. Defaults to `100vw` (the full width of the screen) when using `layout="responsive"` or `layout="fill"`. -If you are using `layout="fill"` or `layout="responsive"`, it's important to assign `sizes` for any image that takes up less than the full viewport width. +If you are using `layout="fill"`, `layout="responsive"`, or `layout="raw"` it's important to assign `sizes` for any image that takes up less than the full viewport width. For example, when the parent element will constrain the image to always be less than half the viewport width, use `sizes="50vw"`. Without `sizes`, the image will be sent at twice the necessary resolution, decreasing performance. @@ -284,7 +291,7 @@ size, or format. Defaults to `false`. Other properties on the `` component will be passed to the underlying `img` element with the exception of the following: -- `style`. Use `className` instead. +- `style`. Only allowed on `layout="raw"` images. For others, use `className` instead. - `srcSet`. Use [Device Sizes](#device-sizes) instead. diff --git a/docs/basic-features/image-optimization.md b/docs/basic-features/image-optimization.md index 0c7cb5a6a292..e9b2dcbaac6f 100644 --- a/docs/basic-features/image-optimization.md +++ b/docs/basic-features/image-optimization.md @@ -181,7 +181,8 @@ The image component has several different [layout modes](/docs/api-reference/nex **Target the image with className, not based on DOM structure** -Regardless of the layout mode used, the Image component will have a consistent DOM structure of one `` tag wrapped by exactly one ``. For some modes, it may also have a sibling `` for spacing. These additional `` elements are critical to allow the component to prevent layout shifts. +For all of the standard layout modes, the Image component will have a consistent DOM structure of one `` tag wrapped by exactly one ``. For some modes, it may also have a sibling `` for spacing. These additional `` elements are critical to allow the component to prevent layout shifts. + The recommended way to style the inner `` is to set the `className` prop on the Image component to the value of an imported [CSS Module](/docs/basic-features/built-in-css-support.md#adding-component-level-css). The value of `className` will be automatically applied to the underlying `` element. @@ -191,6 +192,8 @@ You cannot use [styled-jsx](/docs/basic-features/built-in-css-support.md#css-in- You cannot use the `style` prop because the `` component does not pass it through to the underlying ``. +> An additional `raw` layout mode is provided which removes the wrapper element and allows the `style` prop. This mode still requires `height` and `width` and is recommended only for advanced use cases that aren't covered by the primary layout modes. + **When using `layout='fill'`, the parent element must have `position: relative`** This is necessary for the proper rendering of the image element in that layout mode. From 584382655095b887d6dde8cc48d6a27a507bb370 Mon Sep 17 00:00:00 2001 From: atcastle Date: Mon, 14 Feb 2022 14:04:36 -0800 Subject: [PATCH 05/49] documentation autoformatting --- docs/api-reference/next/image.md | 23 +++++++++++------------ docs/basic-features/image-optimization.md | 1 - 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 5a1879d747eb..42f890fb319b 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -14,16 +14,16 @@ description: Enable Image Optimization with the built-in Image component.
Version History -| Version | Changes | -| --------- | ------------------------------------------------------------------------------------------------- | -| `v12.0.11`| `raw` layout added | -| `v12.0.9` | `lazyRoot` prop added | -| `v12.0.0` | `formats` configuration added.
AVIF support added.
Wrapper `
` changed to ``. | -| `v11.1.0` | `onLoadingComplete` and `lazyBoundary` props added. | -| `v11.0.0` | `src` prop support for static import.
`placeholder` prop added.
`blurDataURL` prop added. | -| `v10.0.5` | `loader` prop added. | -| `v10.0.1` | `layout` prop added. | -| `v10.0.0` | `next/image` introduced. | +| Version | Changes | +| ---------- | ------------------------------------------------------------------------------------------------- | +| `v12.0.11` | `raw` layout added | +| `v12.0.9` | `lazyRoot` prop added | +| `v12.0.0` | `formats` configuration added.
AVIF support added.
Wrapper `
` changed to ``. | +| `v11.1.0` | `onLoadingComplete` and `lazyBoundary` props added. | +| `v11.0.0` | `src` prop support for static import.
`placeholder` prop added.
`blurDataURL` prop added. | +| `v10.0.5` | `loader` prop added. | +| `v10.0.1` | `layout` prop added. | +| `v10.0.0` | `next/image` introduced. |
@@ -73,7 +73,6 @@ The layout behavior of the image as the viewport changes size. | `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | | `raw` | Insert the image element with no responsive behavior | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | - - [Demo the `intrinsic` layout (default)](https://image-component.nextjs.gallery/layout-intrinsic) - When `intrinsic`, the image will scale the dimensions down for smaller viewports, but maintain the original dimensions for larger viewports. - [Demo the `fixed` layout](https://image-component.nextjs.gallery/layout-fixed) @@ -85,7 +84,7 @@ The layout behavior of the image as the viewport changes size. - When `fill`, the image will stretch both width and height to the dimensions of the parent element, provided the parent element is relative. - This is usually paired with the [`objectFit`](#objectFit) property. - Ensure the parent element has `position: relative` in their stylesheet. -- When `raw`, the image will be rendered as a single image element with no wrappers, sizers or other responsive behavior. +- When `raw`, the image will be rendered as a single image element with no wrappers, sizers or other responsive behavior. - Unlike other layout modes, a `raw` image will pass through the `style` property to the underlying image. - If your image styling will change the size of a `raw` image, you should include the `sizes` property for proper image serving. - The other layout modes are optimized for performance and should cover nearly all use cases. It is recommended to try to use those modes before using `raw`. diff --git a/docs/basic-features/image-optimization.md b/docs/basic-features/image-optimization.md index e9b2dcbaac6f..8fa9fd463837 100644 --- a/docs/basic-features/image-optimization.md +++ b/docs/basic-features/image-optimization.md @@ -183,7 +183,6 @@ The image component has several different [layout modes](/docs/api-reference/nex For all of the standard layout modes, the Image component will have a consistent DOM structure of one `` tag wrapped by exactly one ``. For some modes, it may also have a sibling `` for spacing. These additional `` elements are critical to allow the component to prevent layout shifts. - The recommended way to style the inner `` is to set the `className` prop on the Image component to the value of an imported [CSS Module](/docs/basic-features/built-in-css-support.md#adding-component-level-css). The value of `className` will be automatically applied to the underlying `` element. Alternatively, you can import a [global stylesheet](/docs/basic-features/built-in-css-support#adding-a-global-stylesheet) and manually set the `className` prop to the same name used in the global stylesheet. From 4252ffb4cbf73f380a1aeda677df809ef5f03bf9 Mon Sep 17 00:00:00 2001 From: atcastle Date: Mon, 14 Feb 2022 15:21:56 -0800 Subject: [PATCH 06/49] change typescript text fixture --- .../image-component/typescript-style/pages/invalid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/image-component/typescript-style/pages/invalid.tsx b/test/integration/image-component/typescript-style/pages/invalid.tsx index 06a6635269a0..1184b8304505 100644 --- a/test/integration/image-component/typescript-style/pages/invalid.tsx +++ b/test/integration/image-component/typescript-style/pages/invalid.tsx @@ -9,7 +9,7 @@ const Invalid = () => { width={500} height={500} src="https://via.placeholder.com/500" - style={{ objectFit: 'cover' }} + style={{ padding: '12px' }} >

This is the invalid usage

From 4006b369e6db2f4ec25e83d8c2a968c3e3ade342 Mon Sep 17 00:00:00 2001 From: atcastle Date: Thu, 17 Feb 2022 10:41:06 -0800 Subject: [PATCH 07/49] Refactor base image element in image component. Remove raw layout support for objectiFit and objecctPosition attributes --- packages/next/client/image.tsx | 88 ++++++------------- .../default/pages/layout-raw.js | 3 - .../default/test/index.test.js | 12 ++- .../typescript-style/pages/invalid.tsx | 1 + .../typescript-style/test/index.test.js | 2 +- 5 files changed, 34 insertions(+), 72 deletions(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 1ae39c2361ba..8e76723a88e2 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -111,6 +111,7 @@ export type ImageProps = Omit< } & ( | { layout?: Omit + style?: never } | { layout: 'raw' @@ -146,8 +147,7 @@ function getWidths( if ( typeof width !== 'number' || layout === 'fill' || - layout === 'responsive' || - layout === 'raw' + layout === 'responsive' ) { return { widths: deviceSizes, kind: 'w' } } @@ -442,6 +442,11 @@ export default function Image({ `Image with src "${src}" has both "priority" and "loading='lazy'" properties. Only one should be used.` ) } + if (layout === 'raw' && objectFit || objectPosition) { + throw new Error( + `Image with src "${src}" has "layout='raw'" and 'objectFit' or 'objectPosition'. For raw images, these and other styles should be specified using the 'style' attribute.` + ) + } if ( sizes && layout !== 'fill' && @@ -449,7 +454,7 @@ export default function Image({ layout !== 'raw' ) { console.warn( - `Image with src "${src}" has "sizes" property but it will be ignored. Only use "sizes" with 'fill,' 'responsive,' or 'raw' layout modes.` + `Image with src "${src}" has "sizes" property but it will be ignored. Only use "sizes" with "layout='fill'", "layout='responsive'", or "layout='raw'` ) } if (placeholder === 'blur') { @@ -660,22 +665,6 @@ export default function Image({ }) } - let isRaw = false - const rawStyle: ImgElementStyle = {} - - if (layout === 'raw') { - isRaw = true - const styleProp = 'style' in rest && rest.style ? rest.style : {} - Object.assign( - rawStyle, - { - ...(objectFit ? { objectFit } : {}), - ...(objectPosition ? { objectPosition } : {}), - }, - styleProp - ) - } - let srcString: string = src if (process.env.NODE_ENV !== 'production') { @@ -717,19 +706,26 @@ export default function Image({ useEffect(() => { handleLoading(imgRef, srcString, layout, placeholder, onLoadingCompleteRef) }, [srcString, layout, placeholder, isVisible]) + + let rawStyle = {} + if (layout === 'raw' && 'style' in rest && rest.style) { + rawStyle = rest.style + // rest gets spread as img attributes, but we add style manually + delete rest.style + } + const stringSrc = src - return isRaw ? ( - <> + const ImageElement = ({raw}: {raw: boolean}) => + <> {isLazy && ( )} + + return layout === 'raw' ? ( + ) : ( {hasSizer ? ( @@ -781,39 +779,7 @@ export default function Image({ ) : null} ) : null} - - {isLazy && ( - - )} - + {priority ? ( // Note how we omit the `href` attribute, as it would only be relevant // for browsers that do not support `imagesrcset`, and in those cases diff --git a/test/integration/image-component/default/pages/layout-raw.js b/test/integration/image-component/default/pages/layout-raw.js index c205b9d83224..21874e14b3f3 100644 --- a/test/integration/image-component/default/pages/layout-raw.js +++ b/test/integration/image-component/default/pages/layout-raw.js @@ -12,7 +12,6 @@ const Page = () => { width="1200" height="700" layout="raw" - objectFit="cover" loading="eager" > @@ -22,8 +21,6 @@ const Page = () => { src="/wide.png" width="1200" height="700" - objectFit="cover" - objectPosition="50% 50%" style={{ paddingLeft: '4rem', width: '100%', diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index 14bce0f93fcc..e4116f593108 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -588,7 +588,7 @@ function runTests(mode) { } }) - it('should render no wrappers or sizers and minimal styling with layout-raw', async () => { + it.only('should render no wrappers or sizers and minimal styling with layout-raw', async () => { let browser try { browser = await webdriver(appPort, '/layout-raw') @@ -602,12 +602,10 @@ function runTests(mode) { ) expect(childElementType).toBe('IMG') - expect(await browser.elementById('raw1').getAttribute('style')).toBe( - 'object-fit:cover' - ) + expect(await browser.elementById('raw1').getAttribute('style')).toBeNull() expect(await browser.elementById('raw2').getAttribute('style')).toBe( - 'object-fit:cover;object-position:30% 30%;padding-left:4rem;width:100%' + 'padding-left: 4rem; width: 100%; object-position: 30% 30%;' ) expect(await browser.elementById('raw3').getAttribute('style')).toBeNull() @@ -1153,7 +1151,7 @@ function runTests(mode) { } describe('Image Component Tests', () => { - describe('dev mode', () => { + describe.skip('dev mode', () => { beforeAll(async () => { appPort = await findPort() app = await launchApp(appDir, appPort) @@ -1178,7 +1176,7 @@ describe('Image Component Tests', () => { runTests('server') }) - describe('serverless mode', () => { + describe.skip('serverless mode', () => { beforeAll(async () => { await nextBuild(appDir) appPort = await findPort() diff --git a/test/integration/image-component/typescript-style/pages/invalid.tsx b/test/integration/image-component/typescript-style/pages/invalid.tsx index 1184b8304505..a7a4ad62b14d 100644 --- a/test/integration/image-component/typescript-style/pages/invalid.tsx +++ b/test/integration/image-component/typescript-style/pages/invalid.tsx @@ -10,6 +10,7 @@ const Invalid = () => { height={500} src="https://via.placeholder.com/500" style={{ padding: '12px' }} + layout="fixed" >

This is the invalid usage

diff --git a/test/integration/image-component/typescript-style/test/index.test.js b/test/integration/image-component/typescript-style/test/index.test.js index c89386a8743d..ab04ee12331d 100644 --- a/test/integration/image-component/typescript-style/test/index.test.js +++ b/test/integration/image-component/typescript-style/test/index.test.js @@ -10,7 +10,7 @@ describe('TypeScript Image Component with Styles', () => { it('should fail to build when the `style` prop is passed to ', async () => { const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) expect(stderr).toMatch(/Failed to compile/) - expect(stderr).toMatch(/Property 'style' does not exist on type/) + expect(stderr).toMatch(/Type '\"fixed\"' is not assignable to type '\"raw\"/) expect(code).toBe(1) }) }) From efad4b8b00d9357bf23531316697f72578e31309 Mon Sep 17 00:00:00 2001 From: atcastle Date: Thu, 17 Feb 2022 11:06:45 -0800 Subject: [PATCH 08/49] lint fixes --- packages/next/client/image.tsx | 17 +++++++++-------- .../image-component/default/test/index.test.js | 9 +++------ .../typescript-style/test/index.test.js | 4 +++- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 8e76723a88e2..f6e3469ddaf1 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -442,7 +442,7 @@ export default function Image({ `Image with src "${src}" has both "priority" and "loading='lazy'" properties. Only one should be used.` ) } - if (layout === 'raw' && objectFit || objectPosition) { + if ((layout === 'raw' && objectFit) || objectPosition) { throw new Error( `Image with src "${src}" has "layout='raw'" and 'objectFit' or 'objectPosition'. For raw images, these and other styles should be specified using the 'style' attribute.` ) @@ -706,7 +706,7 @@ export default function Image({ useEffect(() => { handleLoading(imgRef, srcString, layout, placeholder, onLoadingCompleteRef) }, [srcString, layout, placeholder, isVisible]) - + let rawStyle = {} if (layout === 'raw' && 'style' in rest && rest.style) { rawStyle = rest.style @@ -715,17 +715,17 @@ export default function Image({ } const stringSrc = src - const ImageElement = ({raw}: {raw: boolean}) => - <> + const ImageElement = ({ raw }: { raw: boolean }) => ( + <> {isLazy && ( )} + ) return layout === 'raw' ? ( diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index e4116f593108..c240c27bc983 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -588,7 +588,7 @@ function runTests(mode) { } }) - it.only('should render no wrappers or sizers and minimal styling with layout-raw', async () => { + it('should render no wrappers or sizers and minimal styling with layout-raw', async () => { let browser try { browser = await webdriver(appPort, '/layout-raw') @@ -610,9 +610,6 @@ function runTests(mode) { expect(await browser.elementById('raw3').getAttribute('style')).toBeNull() - const warnings = (await browser.log('browser')) - .map((log) => log.message) - .join('\n') } finally { if (browser) { await browser.close() @@ -1151,7 +1148,7 @@ function runTests(mode) { } describe('Image Component Tests', () => { - describe.skip('dev mode', () => { + describe('dev mode', () => { beforeAll(async () => { appPort = await findPort() app = await launchApp(appDir, appPort) @@ -1176,7 +1173,7 @@ describe('Image Component Tests', () => { runTests('server') }) - describe.skip('serverless mode', () => { + describe('serverless mode', () => { beforeAll(async () => { await nextBuild(appDir) appPort = await findPort() diff --git a/test/integration/image-component/typescript-style/test/index.test.js b/test/integration/image-component/typescript-style/test/index.test.js index ab04ee12331d..d0f5800a189d 100644 --- a/test/integration/image-component/typescript-style/test/index.test.js +++ b/test/integration/image-component/typescript-style/test/index.test.js @@ -10,7 +10,9 @@ describe('TypeScript Image Component with Styles', () => { it('should fail to build when the `style` prop is passed to ', async () => { const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) expect(stderr).toMatch(/Failed to compile/) - expect(stderr).toMatch(/Type '\"fixed\"' is not assignable to type '\"raw\"/) + expect(stderr).toMatch( + /Type '"fixed"' is not assignable to type '"raw"/ + ) expect(code).toBe(1) }) }) From 26a610325be0a96c8bbdb280e1e146fdb8bbde50 Mon Sep 17 00:00:00 2001 From: atcastle Date: Thu, 17 Feb 2022 11:21:02 -0800 Subject: [PATCH 09/49] lint fixes --- docs/api-reference/next/image.md | 2 +- test/integration/image-component/default/test/index.test.js | 1 - .../image-component/typescript-style/test/index.test.js | 4 +--- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 3f01a4e0909b..2f3db61fbe39 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -16,7 +16,7 @@ description: Enable Image Optimization with the built-in Image component. | Version | Changes | | --------- | ------------------------------------------------------------------------------------------------- | -| `v12.1.0` | `dangerouslyAllowSVG` and `contentSecurityPolicy` configuration added. `raw` layout added. | +| `v12.1.0` | `dangerouslyAllowSVG` and `contentSecurityPolicy` configuration added. `raw` layout added. | | `v12.0.9` | `lazyRoot` prop added. | | `v12.0.0` | `formats` configuration added.
AVIF support added.
Wrapper `
` changed to ``. | | `v11.1.0` | `onLoadingComplete` and `lazyBoundary` props added. | diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index c240c27bc983..78c9fdd18028 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -609,7 +609,6 @@ function runTests(mode) { ) expect(await browser.elementById('raw3').getAttribute('style')).toBeNull() - } finally { if (browser) { await browser.close() diff --git a/test/integration/image-component/typescript-style/test/index.test.js b/test/integration/image-component/typescript-style/test/index.test.js index d0f5800a189d..1edd1857efec 100644 --- a/test/integration/image-component/typescript-style/test/index.test.js +++ b/test/integration/image-component/typescript-style/test/index.test.js @@ -10,9 +10,7 @@ describe('TypeScript Image Component with Styles', () => { it('should fail to build when the `style` prop is passed to ', async () => { const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) expect(stderr).toMatch(/Failed to compile/) - expect(stderr).toMatch( - /Type '"fixed"' is not assignable to type '"raw"/ - ) + expect(stderr).toMatch(/Type '"fixed"' is not assignable to type '"raw"/) expect(code).toBe(1) }) }) From f32440da0b7758514da07f7b97169660022b5d1b Mon Sep 17 00:00:00 2001 From: atcastle Date: Thu, 17 Feb 2022 13:21:59 -0800 Subject: [PATCH 10/49] Fix typescript test --- packages/next/client/image.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index f6e3469ddaf1..85774b7fba4b 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -442,7 +442,7 @@ export default function Image({ `Image with src "${src}" has both "priority" and "loading='lazy'" properties. Only one should be used.` ) } - if ((layout === 'raw' && objectFit) || objectPosition) { + if (layout === 'raw' && (objectFit || objectPosition)) { throw new Error( `Image with src "${src}" has "layout='raw'" and 'objectFit' or 'objectPosition'. For raw images, these and other styles should be specified using the 'style' attribute.` ) From 78904f2a730c0246fd74a850bd7c1aed43756730 Mon Sep 17 00:00:00 2001 From: atcastle Date: Thu, 17 Feb 2022 14:09:30 -0800 Subject: [PATCH 11/49] fix tests --- packages/next/client/image.tsx | 2 +- test/integration/image-component/default/test/index.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 85774b7fba4b..c8ea37db7df7 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -725,7 +725,7 @@ export default function Image({ data-nimg={layout} className={className} ref={imgRef} - style={{ ...blurStyle, ...(raw ? rawStyle : imgStyle) }} + style={{...(raw ? rawStyle : imgStyle), ...blurStyle}} /> {isLazy && (
@@ -36,6 +37,7 @@ const Page = () => { width="400" height="400" layout="raw" + loading="eager" >
diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index 1e0bb7f92d0d..94a609a094af 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -593,7 +593,7 @@ function runTests(mode) { } }) - it('should render no wrappers or sizers and minimal styling with layout-raw', async () => { + it.only('should render no wrappers or sizers and minimal styling with layout-raw', async () => { let browser try { browser = await webdriver(appPort, '/layout-raw') @@ -608,12 +608,21 @@ function runTests(mode) { expect(childElementType).toBe('IMG') expect(await browser.elementById('raw1').getAttribute('style')).toBeNull() + expect(await browser.elementById('raw1').getAttribute('srcset')).toBe( + `/_next/image?url=%2Fwide.png&w=1200&q=75 1x, /_next/image?url=%2Fwide.png&w=3840&q=75 2x` + ) expect(await browser.elementById('raw2').getAttribute('style')).toBe( 'padding-left:4rem;width:100%;object-position:30% 30%' ) + expect(await browser.elementById('raw2').getAttribute('srcset')).toBe( + `/_next/image?url=%2Fwide.png&w=16&q=75 16w, /_next/image?url=%2Fwide.png&w=32&q=75 32w, /_next/image?url=%2Fwide.png&w=48&q=75 48w, /_next/image?url=%2Fwide.png&w=64&q=75 64w, /_next/image?url=%2Fwide.png&w=96&q=75 96w, /_next/image?url=%2Fwide.png&w=128&q=75 128w, /_next/image?url=%2Fwide.png&w=256&q=75 256w, /_next/image?url=%2Fwide.png&w=384&q=75 384w, /_next/image?url=%2Fwide.png&w=640&q=75 640w, /_next/image?url=%2Fwide.png&w=750&q=75 750w, /_next/image?url=%2Fwide.png&w=828&q=75 828w, /_next/image?url=%2Fwide.png&w=1080&q=75 1080w, /_next/image?url=%2Fwide.png&w=1200&q=75 1200w, /_next/image?url=%2Fwide.png&w=1920&q=75 1920w, /_next/image?url=%2Fwide.png&w=2048&q=75 2048w, /_next/image?url=%2Fwide.png&w=3840&q=75 3840w` + ) expect(await browser.elementById('raw3').getAttribute('style')).toBeNull() + expect(await browser.elementById('raw3').getAttribute('srcset')).toBe( + `/_next/image?url=%2Ftest.png&w=640&q=75 1x, /_next/image?url=%2Ftest.png&w=828&q=75 2x` + ) } finally { if (browser) { await browser.close() From 8c1201909d9a6a054c47f6d3790b9a981ff4c8e7 Mon Sep 17 00:00:00 2001 From: atcastle Date: Tue, 22 Feb 2022 12:45:35 -0800 Subject: [PATCH 23/49] Remove test focus --- test/integration/image-component/default/test/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index 94a609a094af..9d10aa191dd1 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -593,7 +593,7 @@ function runTests(mode) { } }) - it.only('should render no wrappers or sizers and minimal styling with layout-raw', async () => { + it('should render no wrappers or sizers and minimal styling with layout-raw', async () => { let browser try { browser = await webdriver(appPort, '/layout-raw') From 74ac553b91235b8047fe208831ca7c6db2d0cd57 Mon Sep 17 00:00:00 2001 From: Alex Castle Date: Tue, 22 Feb 2022 14:05:33 -0800 Subject: [PATCH 24/49] Update docs/api-reference/next/image.md Co-authored-by: Steven --- docs/api-reference/next/image.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 319ce6e82680..1c1e6f4f56d2 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -172,7 +172,7 @@ In some cases, you may need more advanced usage. The `` component optio Allows [passing CSS styles](https://reactjs.org/docs/dom-elements.html#style) to the underlying image element. -Note that all `layout` modes other than `"raw"` apply their own styles to the image element, and these automatic styles take precedence over any styles provided in the `styles` prop. +Note that all `layout` modes other than `"raw"` apply their own styles to the image element, and these automatic styles take precedence over the `style` prop. ### objectFit From 890478287fb7b8396d95173e394020f71f5998ca Mon Sep 17 00:00:00 2001 From: atcastle Date: Wed, 23 Feb 2022 14:48:17 -0800 Subject: [PATCH 25/49] Remove raw from ImageProps type and delete typescript-style test suite --- docs/api-reference/next/image.md | 1 - packages/next/client/image.tsx | 15 +++++--------- .../typescript-style/next.config.js | 5 ----- .../typescript-style/pages/invalid.tsx | 20 ------------------- .../typescript-style/test/index.test.js | 17 ---------------- .../typescript-style/tsconfig.json | 20 ------------------- 6 files changed, 5 insertions(+), 73 deletions(-) delete mode 100644 test/integration/image-component/typescript-style/next.config.js delete mode 100644 test/integration/image-component/typescript-style/pages/invalid.tsx delete mode 100644 test/integration/image-component/typescript-style/test/index.test.js delete mode 100644 test/integration/image-component/typescript-style/tsconfig.json diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 1c1e6f4f56d2..993be5c372a1 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -86,7 +86,6 @@ The layout behavior of the image as the viewport changes size. - This is usually paired with the [`objectFit`](#objectFit) property. - Ensure the parent element has `position: relative` in their stylesheet. - When `raw`, the image will be rendered as a single image element with no wrappers, sizers or other responsive behavior. - - Unlike other layout modes, a `raw` image will pass through the `style` property to the underlying image. - If your image styling will change the size of a `raw` image, you should include the `sizes` property for proper image serving. - The other layout modes are optimized for performance and should cover nearly all use cases. It is recommended to try to use those modes before using `raw`. - [Demo background image](https://image-component.nextjs.gallery/background) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 7e4d0f7ea018..20806655cb39 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -128,11 +128,7 @@ export type ImageProps = Omit< onLoadingComplete?: OnLoadingComplete } -type ImageElementProps = Omit< - JSX.IntrinsicElements['img'], - 'src' | 'srcSet' | 'ref' | 'width' | 'height' | 'loading' -> & { - raw: boolean +type ImageElementProps = Omit & { srcString: string imgAttributes: GenImgAttrsResult heightInt: number | undefined @@ -767,7 +763,7 @@ export default function Image({ return ( <> {layout === 'raw' ? ( - + ) : ( {hasSizer ? ( @@ -792,7 +788,7 @@ export default function Image({ ) : null} ) : null} - +
)} {priority ? ( @@ -821,7 +817,6 @@ export default function Image({ } const ImageElement = ({ - raw, imgAttributes, heightInt, widthInt, @@ -845,7 +840,7 @@ const ImageElement = ({ { - return ( -
-

Invalid TS

- -

This is the invalid usage

-
- ) -} - -export default Invalid diff --git a/test/integration/image-component/typescript-style/test/index.test.js b/test/integration/image-component/typescript-style/test/index.test.js deleted file mode 100644 index b8ba200a809b..000000000000 --- a/test/integration/image-component/typescript-style/test/index.test.js +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-env jest */ - -import { nextBuild } from 'next-test-utils' -import { join } from 'path' - -const appDir = join(__dirname, '..') - -describe('TypeScript Image Component with Styles', () => { - describe('next build', () => { - it.skip('should fail to build when the `style` prop is passed to ', async () => { - const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) - expect(stderr).toMatch(/Failed to compile/) - expect(stderr).toMatch(/Type '"fixed"' is not assignable to type '"raw"/) - expect(code).toBe(1) - }) - }) -}) diff --git a/test/integration/image-component/typescript-style/tsconfig.json b/test/integration/image-component/typescript-style/tsconfig.json deleted file mode 100644 index 9ada0f9fde61..000000000000 --- a/test/integration/image-component/typescript-style/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "esModuleInterop": true, - "module": "esnext", - "jsx": "preserve", - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "incremental": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true - }, - "exclude": ["node_modules"], - "include": ["next-env.d.ts", "components", "pages"] -} From 2f91512c5411e7dee7aa6d2a18e6d26aded23c8b Mon Sep 17 00:00:00 2001 From: atcastle Date: Wed, 23 Feb 2022 15:15:48 -0800 Subject: [PATCH 26/49] auto-lint --- packages/next/client/image.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 20806655cb39..03a1178f5133 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -840,7 +840,7 @@ const ImageElement = ({ Date: Wed, 23 Feb 2022 15:30:33 -0800 Subject: [PATCH 27/49] Documentation updates and move sizes overwrite warning --- docs/api-reference/next/image.md | 14 +-- docs/basic-features/image-optimization.md | 2 +- packages/next/client/image.tsx | 144 +++++++++++----------- 3 files changed, 82 insertions(+), 78 deletions(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 993be5c372a1..c52d89502099 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -66,13 +66,13 @@ The `` component accepts a number of additional properties beyond those The layout behavior of the image as the viewport changes size. -| `layout` | Behavior | `srcSet` | `sizes` | -| --------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------- | -| `intrinsic` (default) | Scale *down* to fit width of container, up to image size | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | -| `fixed` | Sized to `width` and `height` exactly | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | -| `responsive` | Scale to fit width of container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | -| `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | -| `raw` | Insert the image element with no automatic responsive behavior | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | +| `layout` | Behavior | `srcSet` | `sizes` | Has wrapper and sizer | +| --------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------- | --------------------- | +| `intrinsic` (default) | Scale *down* to fit width of container, up to image size | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | +| `fixed` | Sized to `width` and `height` exactly | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | +| `responsive` | Scale to fit width of container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | +| `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | +| `raw` | Insert the image element with no automatic layout behavior | Behaves like `responsive` if the images has the `sizes` prop, and like `fixed` if it does not | optional| no | - [Demo the `intrinsic` layout (default)](https://image-component.nextjs.gallery/layout-intrinsic) - When `intrinsic`, the image will scale the dimensions down for smaller viewports, but maintain the original dimensions for larger viewports. diff --git a/docs/basic-features/image-optimization.md b/docs/basic-features/image-optimization.md index d13693ce85a7..cc23eb71e337 100644 --- a/docs/basic-features/image-optimization.md +++ b/docs/basic-features/image-optimization.md @@ -181,7 +181,7 @@ The image component has several different [layout modes](/docs/api-reference/nex **Target the image with className, not based on DOM structure** -For all of the standard layout modes, the Image component will have a consistent DOM structure of one `` tag wrapped by exactly one ``. For some modes, it may also have a sibling `` for spacing. These additional `` elements are critical to allow the component to prevent layout shifts. +For most layout modes, the Image component will have a DOM structure of one `` tag wrapped by exactly one ``. For some modes, it may also have a sibling `` for spacing. These additional `` elements are critical to allow the component to prevent layout shifts. Images with `layout="raw"` will rendered without any wrapper elements or sizers. The recommended way to style the inner `` is to set the `className` prop on the Image component to the value of an imported [CSS Module](/docs/basic-features/built-in-css-support.md#adding-component-level-css). The value of `className` will be automatically applied to the underlying `` element. diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 03a1178f5133..5a7bda39a09c 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -424,6 +424,66 @@ export default function Image({ isLazy = false } + const [setIntersection, isIntersected] = useIntersection({ + rootRef: lazyRoot, + rootMargin: lazyBoundary, + disabled: !isLazy, + }) + const isVisible = !isLazy || isIntersected + + const wrapperStyle: JSX.IntrinsicElements['span']['style'] = { + boxSizing: 'border-box', + display: 'block', + overflow: 'hidden', + width: 'initial', + height: 'initial', + background: 'none', + opacity: 1, + border: 0, + margin: 0, + padding: 0, + } + const sizerStyle: JSX.IntrinsicElements['span']['style'] = { + boxSizing: 'border-box', + display: 'block', + width: 'initial', + height: 'initial', + background: 'none', + opacity: 1, + border: 0, + margin: 0, + padding: 0, + } + let hasSizer = false + let sizerSvgUrl: string | undefined + const layoutStyle: ImgElementStyle = { + position: 'absolute', + top: 0, + left: 0, + bottom: 0, + right: 0, + + boxSizing: 'border-box', + padding: 0, + border: 'none', + margin: 'auto', + + display: 'block', + width: 0, + height: 0, + minWidth: '100%', + maxWidth: '100%', + minHeight: '100%', + maxHeight: '100%', + + objectFit, + objectPosition, + } + + if (process.env.NODE_ENV !== 'production' && layout !== 'raw' && style) { + + } + if (process.env.NODE_ENV !== 'production') { if (!src) { throw new Error( @@ -524,7 +584,20 @@ export default function Image({ ) } } - + + if (style) { + let overwrittenStyles = Object.keys(style).filter( + (key) => key in layoutStyle + ) + if (overwrittenStyles.length) { + warnOnce( + `Image with src ${src} is assigned the following styles, which are overwritten by automtically-generated styles: ${overwrittenStyles.join( + ', ' + )}` + ) + } + } + if ( typeof window !== 'undefined' && !perfObserver && @@ -554,75 +627,6 @@ export default function Image({ } } - const [setIntersection, isIntersected] = useIntersection({ - rootRef: lazyRoot, - rootMargin: lazyBoundary, - disabled: !isLazy, - }) - const isVisible = !isLazy || isIntersected - - const wrapperStyle: JSX.IntrinsicElements['span']['style'] = { - boxSizing: 'border-box', - display: 'block', - overflow: 'hidden', - width: 'initial', - height: 'initial', - background: 'none', - opacity: 1, - border: 0, - margin: 0, - padding: 0, - } - const sizerStyle: JSX.IntrinsicElements['span']['style'] = { - boxSizing: 'border-box', - display: 'block', - width: 'initial', - height: 'initial', - background: 'none', - opacity: 1, - border: 0, - margin: 0, - padding: 0, - } - let hasSizer = false - let sizerSvgUrl: string | undefined - const layoutStyle: ImgElementStyle = { - position: 'absolute', - top: 0, - left: 0, - bottom: 0, - right: 0, - - boxSizing: 'border-box', - padding: 0, - border: 'none', - margin: 'auto', - - display: 'block', - width: 0, - height: 0, - minWidth: '100%', - maxWidth: '100%', - minHeight: '100%', - maxHeight: '100%', - - objectFit, - objectPosition, - } - - if (process.env.NODE_ENV !== 'production' && layout !== 'raw' && style) { - let overwrittenStyles = Object.keys(style).filter( - (key) => key in layoutStyle - ) - if (overwrittenStyles.length) { - warnOnce( - `Image with src ${src} is assigned the following styles, which are overwritten by automtically-generated styles: ${overwrittenStyles.join( - ', ' - )}` - ) - } - } - const imgStyle = Object.assign({}, style, layout === 'raw' ? {} : layoutStyle) const blurStyle = From 3be724383585b2326b8b6a3c15923daac369abdd Mon Sep 17 00:00:00 2001 From: atcastle Date: Wed, 9 Mar 2022 11:22:51 -0800 Subject: [PATCH 28/49] auto-lint --- docs/api-reference/next/image.md | 14 +++++++------- packages/next/client/image.tsx | 5 ++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index c52d89502099..3445d5d10df7 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -66,13 +66,13 @@ The `` component accepts a number of additional properties beyond those The layout behavior of the image as the viewport changes size. -| `layout` | Behavior | `srcSet` | `sizes` | Has wrapper and sizer | -| --------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------- | --------------------- | -| `intrinsic` (default) | Scale *down* to fit width of container, up to image size | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | -| `fixed` | Sized to `width` and `height` exactly | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | -| `responsive` | Scale to fit width of container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | -| `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | -| `raw` | Insert the image element with no automatic layout behavior | Behaves like `responsive` if the images has the `sizes` prop, and like `fixed` if it does not | optional| no | +| `layout` | Behavior | `srcSet` | `sizes` | Has wrapper and sizer | +| --------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | --------------------- | +| `intrinsic` (default) | Scale *down* to fit width of container, up to image size | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | +| `fixed` | Sized to `width` and `height` exactly | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | +| `responsive` | Scale to fit width of container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | +| `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | +| `raw` | Insert the image element with no automatic layout behavior | Behaves like `responsive` if the images has the `sizes` prop, and like `fixed` if it does not | optional | no | - [Demo the `intrinsic` layout (default)](https://image-component.nextjs.gallery/layout-intrinsic) - When `intrinsic`, the image will scale the dimensions down for smaller viewports, but maintain the original dimensions for larger viewports. diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 5a7bda39a09c..3f006e43b0f8 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -481,7 +481,6 @@ export default function Image({ } if (process.env.NODE_ENV !== 'production' && layout !== 'raw' && style) { - } if (process.env.NODE_ENV !== 'production') { @@ -584,7 +583,7 @@ export default function Image({ ) } } - + if (style) { let overwrittenStyles = Object.keys(style).filter( (key) => key in layoutStyle @@ -597,7 +596,7 @@ export default function Image({ ) } } - + if ( typeof window !== 'undefined' && !perfObserver && From 9c5b9b880519b3eb54223b4dee575ab620abd59e Mon Sep 17 00:00:00 2001 From: Alex Castle Date: Wed, 9 Mar 2022 11:24:13 -0800 Subject: [PATCH 29/49] Update packages/next/client/image.tsx Co-authored-by: Kara --- packages/next/client/image.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 5a7bda39a09c..7fd8b2edf21b 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -591,7 +591,7 @@ export default function Image({ ) if (overwrittenStyles.length) { warnOnce( - `Image with src ${src} is assigned the following styles, which are overwritten by automtically-generated styles: ${overwrittenStyles.join( + `Image with src ${src} is assigned the following styles, which are overwritten by automatically-generated styles: ${overwrittenStyles.join( ', ' )}` ) From adb3a574dddd10b4d6db7172e0ba9ba8ddee9292 Mon Sep 17 00:00:00 2001 From: Alex Castle Date: Wed, 9 Mar 2022 11:24:28 -0800 Subject: [PATCH 30/49] Update docs/api-reference/next/image.md Co-authored-by: Kara --- docs/api-reference/next/image.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index c52d89502099..779ec25d0663 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -72,7 +72,7 @@ The layout behavior of the image as the viewport changes size. | `fixed` | Sized to `width` and `height` exactly | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | | `responsive` | Scale to fit width of container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | | `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | -| `raw` | Insert the image element with no automatic layout behavior | Behaves like `responsive` if the images has the `sizes` prop, and like `fixed` if it does not | optional| no | +| `raw` | Insert the image element with no automatic layout behavior | Behaves like `responsive` if the image has the `sizes` prop, and like `fixed` if it does not | optional| no | - [Demo the `intrinsic` layout (default)](https://image-component.nextjs.gallery/layout-intrinsic) - When `intrinsic`, the image will scale the dimensions down for smaller viewports, but maintain the original dimensions for larger viewports. From 6baa8be4f628125a81d1dcae65672341bddc9f05 Mon Sep 17 00:00:00 2001 From: atcastle Date: Wed, 9 Mar 2022 11:28:23 -0800 Subject: [PATCH 31/49] auto-lint --- docs/api-reference/next/image.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 779ec25d0663..8a653e0d0992 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -66,13 +66,13 @@ The `` component accepts a number of additional properties beyond those The layout behavior of the image as the viewport changes size. -| `layout` | Behavior | `srcSet` | `sizes` | Has wrapper and sizer | -| --------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------- | --------------------- | -| `intrinsic` (default) | Scale *down* to fit width of container, up to image size | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | -| `fixed` | Sized to `width` and `height` exactly | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | -| `responsive` | Scale to fit width of container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | -| `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | -| `raw` | Insert the image element with no automatic layout behavior | Behaves like `responsive` if the image has the `sizes` prop, and like `fixed` if it does not | optional| no | +| `layout` | Behavior | `srcSet` | `sizes` | Has wrapper and sizer | +| --------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | --------------------- | +| `intrinsic` (default) | Scale *down* to fit width of container, up to image size | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | +| `fixed` | Sized to `width` and `height` exactly | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | +| `responsive` | Scale to fit width of container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | +| `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | +| `raw` | Insert the image element with no automatic layout behavior | Behaves like `responsive` if the image has the `sizes` prop, and like `fixed` if it does not | optional | no | - [Demo the `intrinsic` layout (default)](https://image-component.nextjs.gallery/layout-intrinsic) - When `intrinsic`, the image will scale the dimensions down for smaller viewports, but maintain the original dimensions for larger viewports. From e03c65b058252f617ecc21f933a99963f6e41067 Mon Sep 17 00:00:00 2001 From: atcastle Date: Wed, 9 Mar 2022 12:50:01 -0800 Subject: [PATCH 32/49] Add experimental flag for raw layout mode --- packages/next/build/webpack-config.ts | 1 + packages/next/client/image.tsx | 8 ++++++++ packages/next/server/config-shared.ts | 6 ++++++ test/integration/image-component/default/next.config.js | 7 +++++++ 4 files changed, 22 insertions(+) create mode 100644 test/integration/image-component/default/next.config.js diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index d54d3c9f8c2b..bc35436b587e 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1357,6 +1357,7 @@ export default async function getBaseWebpackConfig( imageSizes: config.images.imageSizes, path: config.images.path, loader: config.images.loader, + experimentalLayoutRaw: config.experimental?.images?.layoutRaw, ...(dev ? { // pass domains in development to allow validating on the client diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index ae1a12d8652e..7f5af97e8307 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -9,6 +9,8 @@ import { import { useIntersection } from './use-intersection' import { ImageConfigContext } from '../shared/lib/image-config-context' +const experimentalLayoutRaw = (process.env.__NEXT_IMAGE_OPTS as any) + .experimentalLayoutRaw const configEnv = process.env.__NEXT_IMAGE_OPTS as any as ImageConfigComplete const loadedImageURLs = new Set() const allImgs = new Map< @@ -382,6 +384,12 @@ export default function Image({ delete rest['layout'] } + if (layout === 'raw' && !experimentalLayoutRaw) { + throw new Error( + `The "raw" layout is currently experimental and may be subject to breaking change. To use layout="raw", include 'experimental: { images: layoutRaw} } in your next.config file.` + ) + } + let staticSrc = '' if (isStaticImport(src)) { const staticImageData = isStaticRequire(src) ? src.default : src diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts index 4c74d58775c5..169c1b36bd3b 100644 --- a/packages/next/server/config-shared.ts +++ b/packages/next/server/config-shared.ts @@ -106,6 +106,9 @@ export interface ExperimentalConfig { urlImports?: NonNullable['buildHttp'] outputFileTracingRoot?: string outputStandalone?: boolean + images?: { + layoutRaw: boolean + } } /** @@ -468,6 +471,9 @@ export const defaultConfig: NextConfig = { fullySpecified: false, outputFileTracingRoot: process.env.NEXT_PRIVATE_OUTPUT_TRACE_ROOT || '', outputStandalone: !!process.env.NEXT_PRIVATE_STANDALONE, + images: { + layoutRaw: false, + }, }, } diff --git a/test/integration/image-component/default/next.config.js b/test/integration/image-component/default/next.config.js new file mode 100644 index 000000000000..7f8d4e88d0f7 --- /dev/null +++ b/test/integration/image-component/default/next.config.js @@ -0,0 +1,7 @@ +module.exports = { + experimental: { + images: { + layoutRaw: true, + }, + }, +} From 20955eb22ff2144e002b13af7a41c18b3beb47b6 Mon Sep 17 00:00:00 2001 From: atcastle Date: Wed, 9 Mar 2022 13:02:25 -0800 Subject: [PATCH 33/49] Fix image style test --- packages/next/client/image.tsx | 2 +- test/integration/image-component/default/test/index.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 7f5af97e8307..fd2ff2d118ff 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -592,7 +592,7 @@ export default function Image({ } } - if (style) { + if (style && layout !== 'raw') { let overwrittenStyles = Object.keys(style).filter( (key) => key in layoutStyle ) diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index 9d10aa191dd1..8b4a7b3bf444 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -668,10 +668,10 @@ function runTests(mode) { .map((log) => log.message) .join('\n') expect(warnings).toMatch( - /Image with src \/test.png is assigned the following styles, which are overwritten by automtically-generated styles: padding/gm + /Image with src \/test.png is assigned the following styles, which are overwritten by automatically-generated styles: padding/gm ) expect(warnings).toMatch( - /Image with src \/test.jpg is assigned the following styles, which are overwritten by automtically-generated styles: width, margin/gm + /Image with src \/test.jpg is assigned the following styles, which are overwritten by automatically-generated styles: width, margin/gm ) expect(warnings).not.toMatch( /Image with src \/test.webp is assigned the following styles/gm From b64bda70a3b4c5be401c50b49ae1347802231f19 Mon Sep 17 00:00:00 2001 From: atcastle Date: Wed, 9 Mar 2022 13:29:29 -0800 Subject: [PATCH 34/49] Add aspect-ratio for raw images and only add height/width without sizes --- packages/next/client/image.tsx | 14 +++++++++++--- .../image-component/default/test/index.test.js | 14 +++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index fd2ff2d118ff..a99435f50593 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -634,7 +634,13 @@ export default function Image({ } } - const imgStyle = Object.assign({}, style, layout === 'raw' ? {} : layoutStyle) + const imgStyle = Object.assign( + {}, + style, + layout === 'raw' + ? { aspectRatio: `${widthInt} / ${heightInt}` } + : layoutStyle + ) const blurStyle = placeholder === 'blur' @@ -851,7 +857,9 @@ const ImageElement = ({ Date: Wed, 9 Mar 2022 13:40:51 -0800 Subject: [PATCH 35/49] Update packages/next/client/image.tsx Co-authored-by: Kara --- packages/next/client/image.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index a99435f50593..6f91aff6c136 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -386,7 +386,7 @@ export default function Image({ if (layout === 'raw' && !experimentalLayoutRaw) { throw new Error( - `The "raw" layout is currently experimental and may be subject to breaking change. To use layout="raw", include 'experimental: { images: layoutRaw} } in your next.config file.` + `The "raw" layout is currently experimental and may be subject to breaking changes. To use layout="raw", include 'experimental: { images: layoutRaw } in your next.config file.` ) } From 7338c3ace08f9f23a3984a575c80bde82ed886bb Mon Sep 17 00:00:00 2001 From: Alex Castle Date: Wed, 9 Mar 2022 14:40:06 -0800 Subject: [PATCH 36/49] Update docs/api-reference/next/image.md Co-authored-by: Steven --- docs/api-reference/next/image.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 8a653e0d0992..33dae98a00e9 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -16,7 +16,7 @@ description: Enable Image Optimization with the built-in Image component. | Version | Changes | | --------- | ------------------------------------------------------------------------------------------------- | -| `v12.1.1` | `raw` layout added. Support added for `style` prop. | +| `v12.1.1` | `style` prop added. Experimental support for `layout="raw"` added. | | `v12.1.0` | `dangerouslyAllowSVG` and `contentSecurityPolicy` configuration added. | | `v12.0.9` | `lazyRoot` prop added. | | `v12.0.0` | `formats` configuration added.
AVIF support added.
Wrapper `
` changed to ``. | From bc42437bf1684d91b0eaefa8d3c7c0fad0a955b1 Mon Sep 17 00:00:00 2001 From: atcastle Date: Wed, 9 Mar 2022 14:56:02 -0800 Subject: [PATCH 37/49] update docs for experimental raw layout --- docs/api-reference/next/image.md | 64 +++++++++++++++-------- docs/basic-features/image-optimization.md | 4 +- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 33dae98a00e9..fbd79c7ac137 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -14,17 +14,17 @@ description: Enable Image Optimization with the built-in Image component.
Version History -| Version | Changes | -| --------- | ------------------------------------------------------------------------------------------------- | -| `v12.1.1` | `style` prop added. Experimental support for `layout="raw"` added. | -| `v12.1.0` | `dangerouslyAllowSVG` and `contentSecurityPolicy` configuration added. | -| `v12.0.9` | `lazyRoot` prop added. | -| `v12.0.0` | `formats` configuration added.
AVIF support added.
Wrapper `
` changed to ``. | -| `v11.1.0` | `onLoadingComplete` and `lazyBoundary` props added. | -| `v11.0.0` | `src` prop support for static import.
`placeholder` prop added.
`blurDataURL` prop added. | -| `v10.0.5` | `loader` prop added. | -| `v10.0.1` | `layout` prop added. | -| `v10.0.0` | `next/image` introduced. | +| Version | Changes | +| --------- | ------------------------------------------------------------------------------------------------------- | +| `v12.1.1` | `style` prop added. Experimental[\*](#Experimental-"raw"-layout-mode) support for `layout="raw"` added. | +| `v12.1.0` | `dangerouslyAllowSVG` and `contentSecurityPolicy` configuration added. | +| `v12.0.9` | `lazyRoot` prop added. | +| `v12.0.0` | `formats` configuration added.
AVIF support added.
Wrapper `
` changed to ``. | +| `v11.1.0` | `onLoadingComplete` and `lazyBoundary` props added. | +| `v11.0.0` | `src` prop support for static import.
`placeholder` prop added.
`blurDataURL` prop added. | +| `v10.0.5` | `loader` prop added. | +| `v10.0.1` | `layout` prop added. | +| `v10.0.0` | `next/image` introduced. |
@@ -66,13 +66,13 @@ The `` component accepts a number of additional properties beyond those The layout behavior of the image as the viewport changes size. -| `layout` | Behavior | `srcSet` | `sizes` | Has wrapper and sizer | -| --------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | --------------------- | -| `intrinsic` (default) | Scale *down* to fit width of container, up to image size | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | -| `fixed` | Sized to `width` and `height` exactly | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | -| `responsive` | Scale to fit width of container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | -| `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | -| `raw` | Insert the image element with no automatic layout behavior | Behaves like `responsive` if the image has the `sizes` prop, and like `fixed` if it does not | optional | no | +| `layout` | Behavior | `srcSet` | `sizes` | Has wrapper and sizer | +| ------------------------------------------ | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | --------------------- | +| `intrinsic` (default) | Scale *down* to fit width of container, up to image size | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | +| `fixed` | Sized to `width` and `height` exactly | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | +| `responsive` | Scale to fit width of container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | +| `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | +| `raw`[\*](#Experimental-"raw"-layout-mode) | Insert the image element with no automatic layout behavior | Behaves like `responsive` if the image has the `sizes` prop, and like `fixed` if it does not | optional | no | - [Demo the `intrinsic` layout (default)](https://image-component.nextjs.gallery/layout-intrinsic) - When `intrinsic`, the image will scale the dimensions down for smaller viewports, but maintain the original dimensions for larger viewports. @@ -85,8 +85,8 @@ The layout behavior of the image as the viewport changes size. - When `fill`, the image will stretch both width and height to the dimensions of the parent element, provided the parent element is relative. - This is usually paired with the [`objectFit`](#objectFit) property. - Ensure the parent element has `position: relative` in their stylesheet. -- When `raw`, the image will be rendered as a single image element with no wrappers, sizers or other responsive behavior. - - If your image styling will change the size of a `raw` image, you should include the `sizes` property for proper image serving. +- When `raw`[\*](#Experimental-"raw"-layout-mode), the image will be rendered as a single image element with no wrappers, sizers or other responsive behavior. + - If your image styling will change the size of a `raw` image, you should include the `sizes` property for proper image serving. Otherwise your image will receive a fixed height and width. - The other layout modes are optimized for performance and should cover nearly all use cases. It is recommended to try to use those modes before using `raw`. - [Demo background image](https://image-component.nextjs.gallery/background) @@ -126,7 +126,7 @@ const MyImage = (props) => { A string that provides information about how wide the image will be at different breakpoints. Defaults to `100vw` (the full width of the screen) when using `layout="responsive"` or `layout="fill"`. -If you are using `layout="fill"`, `layout="responsive"`, or `layout="raw"` it's important to assign `sizes` for any image that takes up less than the full viewport width. +If you are using `layout="fill"`, `layout="responsive"`, or `layout="raw"`[\*](#Experimental-"raw"-layout-mode) it's important to assign `sizes` for any image that takes up less than the full viewport width. For example, when the parent element will constrain the image to always be less than half the viewport width, use `sizes="50vw"`. Without `sizes`, the image will be sent at twice the necessary resolution, decreasing performance. @@ -171,7 +171,7 @@ In some cases, you may need more advanced usage. The `` component optio Allows [passing CSS styles](https://reactjs.org/docs/dom-elements.html#style) to the underlying image element. -Note that all `layout` modes other than `"raw"` apply their own styles to the image element, and these automatic styles take precedence over the `style` prop. +Note that all `layout` modes other than `"raw"`[\*](#Experimental-"raw"-layout-mode) apply their own styles to the image element, and these automatic styles take precedence over the `style` prop. ### objectFit @@ -465,6 +465,26 @@ module.exports = { } ``` +### Experimental "raw" layout mode + +The image component currently supports an additional `layout="raw"` mode, which renders the image without wrappers or styling. This layout mode is currently an experimental feature, while user feedback is gathered. We expect the "raw" layout mode to graduate to full support in the near future. + +Currently, there is the possibility of breaking changes to the `layout="raw"` interface, so the feature is locked behind an experimental feature flag. If you would like to use the `raw` layout mode, you must add the following to your `next.config.js`: + +```js +module.exports = { + experimental: { + images: { + layoutRaw: true, + }, + }, +} +``` + +> Note on CLS with `layout="raw"`: +> It is possible to cause [layout shift](https://web.dev/cls/) with the image component in `raw` mode. If you include a `sizes` property, the image component will not pass `height` and `width` attributes to the image, to allow you to apply your own responsive sizing. +> An [aspect-ratio](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio) style property is automatically applied to prevent layout shift, but this won't apply on [older browsers](https://caniuse.com/mdn-css_properties_aspect-ratio). + ## Related For an overview of the Image component features and usage guidelines, see: diff --git a/docs/basic-features/image-optimization.md b/docs/basic-features/image-optimization.md index cc23eb71e337..9de528f1910b 100644 --- a/docs/basic-features/image-optimization.md +++ b/docs/basic-features/image-optimization.md @@ -181,7 +181,7 @@ The image component has several different [layout modes](/docs/api-reference/nex **Target the image with className, not based on DOM structure** -For most layout modes, the Image component will have a DOM structure of one `` tag wrapped by exactly one ``. For some modes, it may also have a sibling `` for spacing. These additional `` elements are critical to allow the component to prevent layout shifts. Images with `layout="raw"` will rendered without any wrapper elements or sizers. +For most layout modes, the Image component will have a DOM structure of one `` tag wrapped by exactly one ``. For some modes, it may also have a sibling `` for spacing. These additional `` elements are critical to allow the component to prevent layout shifts. The recommended way to style the inner `` is to set the `className` prop on the Image component to the value of an imported [CSS Module](/docs/basic-features/built-in-css-support.md#adding-component-level-css). The value of `className` will be automatically applied to the underlying `` element. @@ -189,8 +189,6 @@ Alternatively, you can import a [global stylesheet](/docs/basic-features/built-i You cannot use [styled-jsx](/docs/basic-features/built-in-css-support.md#css-in-js) because it's scoped to the current component. -> An additional `raw` layout mode is provided which removes the wrapper and sizer elements. This mode still requires `height` and `width` and is recommended only for advanced use cases that aren't covered by the primary layout modes. - **When using `layout='fill'`, the parent element must have `position: relative`** This is necessary for the proper rendering of the image element in that layout mode. From 5eea550ee271e2616d4ea6e9fc3b580b4706e8bc Mon Sep 17 00:00:00 2001 From: atcastle Date: Wed, 9 Mar 2022 15:00:16 -0800 Subject: [PATCH 38/49] Fix markdown links --- docs/api-reference/next/image.md | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index fbd79c7ac137..2e96580ecb2e 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -14,17 +14,17 @@ description: Enable Image Optimization with the built-in Image component.
Version History -| Version | Changes | -| --------- | ------------------------------------------------------------------------------------------------------- | -| `v12.1.1` | `style` prop added. Experimental[\*](#Experimental-"raw"-layout-mode) support for `layout="raw"` added. | -| `v12.1.0` | `dangerouslyAllowSVG` and `contentSecurityPolicy` configuration added. | -| `v12.0.9` | `lazyRoot` prop added. | -| `v12.0.0` | `formats` configuration added.
AVIF support added.
Wrapper `
` changed to ``. | -| `v11.1.0` | `onLoadingComplete` and `lazyBoundary` props added. | -| `v11.0.0` | `src` prop support for static import.
`placeholder` prop added.
`blurDataURL` prop added. | -| `v10.0.5` | `loader` prop added. | -| `v10.0.1` | `layout` prop added. | -| `v10.0.0` | `next/image` introduced. | +| Version | Changes | +| --------- | ----------------------------------------------------------------------------------------------------- | +| `v12.1.1` | `style` prop added. Experimental[\*](#Experimental-raw-layout-mode) support for `layout="raw"` added. | +| `v12.1.0` | `dangerouslyAllowSVG` and `contentSecurityPolicy` configuration added. | +| `v12.0.9` | `lazyRoot` prop added. | +| `v12.0.0` | `formats` configuration added.
AVIF support added.
Wrapper `
` changed to ``. | +| `v11.1.0` | `onLoadingComplete` and `lazyBoundary` props added. | +| `v11.0.0` | `src` prop support for static import.
`placeholder` prop added.
`blurDataURL` prop added. | +| `v10.0.5` | `loader` prop added. | +| `v10.0.1` | `layout` prop added. | +| `v10.0.0` | `next/image` introduced. |
@@ -66,13 +66,13 @@ The `` component accepts a number of additional properties beyond those The layout behavior of the image as the viewport changes size. -| `layout` | Behavior | `srcSet` | `sizes` | Has wrapper and sizer | -| ------------------------------------------ | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | --------------------- | -| `intrinsic` (default) | Scale *down* to fit width of container, up to image size | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | -| `fixed` | Sized to `width` and `height` exactly | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | -| `responsive` | Scale to fit width of container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | -| `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | -| `raw`[\*](#Experimental-"raw"-layout-mode) | Insert the image element with no automatic layout behavior | Behaves like `responsive` if the image has the `sizes` prop, and like `fixed` if it does not | optional | no | +| `layout` | Behavior | `srcSet` | `sizes` | Has wrapper and sizer | +| ---------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | --------------------- | +| `intrinsic` (default) | Scale *down* to fit width of container, up to image size | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | +| `fixed` | Sized to `width` and `height` exactly | `1x`, `2x` (based on [imageSizes](#image-sizes)) | N/A | yes | +| `responsive` | Scale to fit width of container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | +| `fill` | Grow in both X and Y axes to fill container | `640w`, `750w`, ... `2048w`, `3840w` (based on [imageSizes](#image-sizes) and [deviceSizes](#device-sizes)) | `100vw` | yes | +| `raw`[\*](#Experimental-raw-layout-mode) | Insert the image element with no automatic layout behavior | Behaves like `responsive` if the image has the `sizes` prop, and like `fixed` if it does not | optional | no | - [Demo the `intrinsic` layout (default)](https://image-component.nextjs.gallery/layout-intrinsic) - When `intrinsic`, the image will scale the dimensions down for smaller viewports, but maintain the original dimensions for larger viewports. @@ -85,7 +85,7 @@ The layout behavior of the image as the viewport changes size. - When `fill`, the image will stretch both width and height to the dimensions of the parent element, provided the parent element is relative. - This is usually paired with the [`objectFit`](#objectFit) property. - Ensure the parent element has `position: relative` in their stylesheet. -- When `raw`[\*](#Experimental-"raw"-layout-mode), the image will be rendered as a single image element with no wrappers, sizers or other responsive behavior. +- When `raw`[\*](#Experimental-raw-layout-mode), the image will be rendered as a single image element with no wrappers, sizers or other responsive behavior. - If your image styling will change the size of a `raw` image, you should include the `sizes` property for proper image serving. Otherwise your image will receive a fixed height and width. - The other layout modes are optimized for performance and should cover nearly all use cases. It is recommended to try to use those modes before using `raw`. - [Demo background image](https://image-component.nextjs.gallery/background) @@ -126,7 +126,7 @@ const MyImage = (props) => { A string that provides information about how wide the image will be at different breakpoints. Defaults to `100vw` (the full width of the screen) when using `layout="responsive"` or `layout="fill"`. -If you are using `layout="fill"`, `layout="responsive"`, or `layout="raw"`[\*](#Experimental-"raw"-layout-mode) it's important to assign `sizes` for any image that takes up less than the full viewport width. +If you are using `layout="fill"`, `layout="responsive"`, or `layout="raw"`[\*](#Experimental-raw-layout-mode) it's important to assign `sizes` for any image that takes up less than the full viewport width. For example, when the parent element will constrain the image to always be less than half the viewport width, use `sizes="50vw"`. Without `sizes`, the image will be sent at twice the necessary resolution, decreasing performance. @@ -171,7 +171,7 @@ In some cases, you may need more advanced usage. The `` component optio Allows [passing CSS styles](https://reactjs.org/docs/dom-elements.html#style) to the underlying image element. -Note that all `layout` modes other than `"raw"`[\*](#Experimental-"raw"-layout-mode) apply their own styles to the image element, and these automatic styles take precedence over the `style` prop. +Note that all `layout` modes other than `"raw"`[\*](#Experimental-raw-layout-mode) apply their own styles to the image element, and these automatic styles take precedence over the `style` prop. ### objectFit From d3d82b678643946c58c033ea02996aedf305b1e4 Mon Sep 17 00:00:00 2001 From: atcastle Date: Wed, 9 Mar 2022 15:12:10 -0800 Subject: [PATCH 39/49] Documentation wording change --- docs/api-reference/next/image.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 2e96580ecb2e..c12d5b041deb 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -467,9 +467,7 @@ module.exports = { ### Experimental "raw" layout mode -The image component currently supports an additional `layout="raw"` mode, which renders the image without wrappers or styling. This layout mode is currently an experimental feature, while user feedback is gathered. We expect the "raw" layout mode to graduate to full support in the near future. - -Currently, there is the possibility of breaking changes to the `layout="raw"` interface, so the feature is locked behind an experimental feature flag. If you would like to use the `raw` layout mode, you must add the following to your `next.config.js`: +The image component currently supports an additional `layout="raw"` mode, which renders the image without wrappers or styling. This layout mode is currently an experimental feature, while user feedback is gathered. As there is the possibility of breaking changes to the `layout="raw"` interface, the feature is locked behind an experimental feature flag. If you would like to use the `raw` layout mode, you must add the following to your `next.config.js`: ```js module.exports = { From 7525553377e45cf2ce3fd26fe4e4678937e2073d Mon Sep 17 00:00:00 2001 From: atcastle Date: Thu, 10 Mar 2022 10:22:30 -0800 Subject: [PATCH 40/49] Fix type from merge --- packages/next/client/image.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 3726ec230567..cac342f7a6e5 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -890,7 +890,7 @@ const ImageElement = ({ loading={loading || 'lazy'} /> - )} + } ) } From 9f41f20e80fe2300f55c8c19a17b86e3127e1b9f Mon Sep 17 00:00:00 2001 From: Alex Castle Date: Thu, 10 Mar 2022 10:24:00 -0800 Subject: [PATCH 41/49] Update packages/next/client/image.tsx Co-authored-by: Steven --- packages/next/client/image.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index cac342f7a6e5..d8dc62b83686 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -377,7 +377,7 @@ export default function Image({ if (layout === 'raw' && !experimentalLayoutRaw) { throw new Error( - `The "raw" layout is currently experimental and may be subject to breaking changes. To use layout="raw", include 'experimental: { images: layoutRaw } in your next.config file.` + `The "raw" layout is currently experimental and may be subject to breaking changes. To use layout="raw", include \`experimental: { images: { layoutRaw: true } }\` in your next.config.js file.` ) } From c4654fabc73d30e1791f36f062ff20ed9686cfb9 Mon Sep 17 00:00:00 2001 From: Alex Castle Date: Thu, 10 Mar 2022 10:24:08 -0800 Subject: [PATCH 42/49] Update docs/api-reference/next/image.md Co-authored-by: Steven --- docs/api-reference/next/image.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index d3cf48e74add..71e70763e39c 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -85,7 +85,7 @@ The layout behavior of the image as the viewport changes size. - When `fill`, the image will stretch both width and height to the dimensions of the parent element, provided the parent element is relative. - This is usually paired with the [`objectFit`](#objectFit) property. - Ensure the parent element has `position: relative` in their stylesheet. -- When `raw`[\*](#Experimental-raw-layout-mode), the image will be rendered as a single image element with no wrappers, sizers or other responsive behavior. +- When `raw`[\*](#experimental-raw-layout-mode), the image will be rendered as a single image element with no wrappers, sizers or other responsive behavior. - If your image styling will change the size of a `raw` image, you should include the `sizes` property for proper image serving. Otherwise your image will receive a fixed height and width. - The other layout modes are optimized for performance and should cover nearly all use cases. It is recommended to try to use those modes before using `raw`. - [Demo background image](https://image-component.nextjs.gallery/background) From 820b1528a3eb409b81428c141aac23a99d554015 Mon Sep 17 00:00:00 2001 From: Alex Castle Date: Thu, 10 Mar 2022 10:36:20 -0800 Subject: [PATCH 43/49] Update packages/next/client/image.tsx Co-authored-by: Steven --- packages/next/client/image.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index d8dc62b83686..9451af864ece 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -865,7 +865,7 @@ const ImageElement = ({ ref={imgRef} style={{ ...imgStyle, ...blurStyle }} /> - {lazy && + {(lazy || placeholder === 'blur') &&