Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(next/image): render valid html according to W3C #33825

Merged
merged 11 commits into from Feb 1, 2022
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -56,6 +56,7 @@
"@testing-library/react": "11.2.5",
"@types/cheerio": "0.22.16",
"@types/fs-extra": "8.1.0",
"@types/html-validator": "5.0.2",
"@types/http-proxy": "1.17.3",
"@types/jest": "24.0.13",
"@types/node": "13.11.0",
Expand Down Expand Up @@ -112,6 +113,7 @@
"get-port": "5.1.1",
"glob": "7.1.6",
"gzip-size": "5.1.1",
"html-validator": "5.1.18",
"image-size": "0.9.3",
"is-animated": "2.0.0",
"isomorphic-unfetch": "3.0.0",
Expand Down
10 changes: 5 additions & 5 deletions packages/next/client/image.tsx
@@ -1,5 +1,6 @@
import React, { useRef, useEffect } from 'react'
import Head from '../shared/lib/head'
import { toBase64 } from '../shared/lib/to-base-64'
import {
ImageConfigComplete,
imageConfigDefault,
Expand Down Expand Up @@ -544,7 +545,7 @@ export default function Image({
padding: 0,
}
let hasSizer = false
let sizerSvgUrl: string | undefined
let sizerSvg: string | undefined
const imgStyle: ImgElementStyle = {
position: 'absolute',
top: 0,
Expand Down Expand Up @@ -605,8 +606,7 @@ export default function Image({
wrapperStyle.maxWidth = '100%'
hasSizer = true
sizerStyle.maxWidth = '100%'
// url encoded svg is a little bit shorten than base64 encoding
sizerSvgUrl = `data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 version=%271.1%27 width=%27${widthInt}%27 height=%27${heightInt}%27/%3e`
sizerSvg = `<svg width="${widthInt}" height="${heightInt}" xmlns="http://www.w3.org/2000/svg" version="1.1"/>`
} else if (layout === 'fixed') {
// <Image src="i.png" width="100" height="100" layout="fixed" />
wrapperStyle.display = 'inline-block'
Expand Down Expand Up @@ -687,7 +687,7 @@ export default function Image({
<span style={wrapperStyle}>
{hasSizer ? (
<span style={sizerStyle}>
{sizerSvgUrl ? (
{sizerSvg ? (
<img
style={{
display: 'block',
Expand All @@ -702,7 +702,7 @@ export default function Image({
}}
alt=""
aria-hidden={true}
src={sizerSvgUrl}
src={`data:image/svg+xml;base64,${toBase64(sizerSvg)}`}
/>
) : null}
</span>
Expand Down
10 changes: 10 additions & 0 deletions packages/next/shared/lib/to-base-64.ts
@@ -0,0 +1,10 @@
/**
* Isomorphic base64 that works on the server and client
*/
export function toBase64(str: string) {
if (typeof window === 'undefined') {
return Buffer.from(str).toString('base64')
} else {
return window.btoa(str)
}
}
13 changes: 13 additions & 0 deletions test/integration/image-component/default/pages/_document.js
@@ -0,0 +1,13 @@
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
@@ -0,0 +1,20 @@
import Head from 'next/head'
import Image from 'next/image'

const Page = () => {
return (
<div>
<Head>
<title>Title</title>
<meta name="description" content="Description..." />
<link rel="icon" type="image/jpeg" href="/test.jpg" />
</Head>
styfle marked this conversation as resolved.
Show resolved Hide resolved

<main>
<Image src="/test.jpg" width="400" height="400" alt="basic image" />
</main>
</div>
)
}

export default Page
21 changes: 21 additions & 0 deletions test/integration/image-component/default/test/index.test.js
@@ -1,6 +1,7 @@
/* eslint-env jest */

import cheerio from 'cheerio'
import validateHTML from 'html-validator'
import {
check,
findPort,
Expand Down Expand Up @@ -1039,6 +1040,26 @@ function runTests(mode) {
}
}
})

it('should be valid W3C HTML', async () => {
let browser
try {
browser = await webdriver(appPort, '/valid-html-w3c')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets loop through every page here to ensure all usages of the image component are valid HTML

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try but that requires a lot of changes to these pages because a lot of them are not valid W3C because of all kinds of errors like duplicated IDs etc. And I feel like it's unrelated to this PR.

Testing only with the /valid-html-w3c page keeps things simple and only test what is really needed.
Will keep you updated! 👍

await waitFor(1000)
expect(await browser.hasElementByCssSelector('img')).toBeTruthy()
const url = await browser.url()
const result = await validateHTML({
url,
format: 'json',
isLocal: true,
})
expect(result.messages).toEqual([])
} finally {
if (browser) {
await browser.close()
}
}
})
}

describe('Image Component Tests', () => {
Expand Down