-
-
Notifications
You must be signed in to change notification settings - Fork 8.8k
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
Add new prefer-ideal-image eslint rule #8826
base: main
Are you sure you want to change the base?
Changes from all commits
51f9a79
c239d1c
b28b8fd
6016b36
78a6a25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import rule from '../prefer-ideal-image'; | ||
import {RuleTester} from './testUtils'; | ||
|
||
const errorsJSX = [{messageId: 'image'}] as const; | ||
|
||
const ruleTester = new RuleTester({ | ||
parser: '@typescript-eslint/parser', | ||
parserOptions: { | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}, | ||
}); | ||
|
||
ruleTester.run('prefer-ideal-image', rule, { | ||
valid: [ | ||
{ | ||
code: "<IdealImage img='./path/to/img.png' />", | ||
}, | ||
{ | ||
code: "<IdealImage img={require('./path/to/img.png')} />", | ||
}, | ||
{ | ||
code: "<IdealImage img={{ src: { src: './path/to/img.png', preSrc: '', images: [{ width: 100 }]}, preSrc: './path/to/placeholder.png'}} />", | ||
}, | ||
], | ||
invalid: [ | ||
{ | ||
code: "<img src='./path/to/img.png' alt='some alt text' />", | ||
errors: errorsJSX, | ||
}, | ||
{ | ||
code: "<img src={require('./path/to/img.png')} alt='some alt text' />", | ||
errors: errorsJSX, | ||
}, | ||
{ | ||
code: "<img src='./path/to/img.png' srcset='./path/to/img-480w.jpg 480w, ./path/to/img-800w.png 800w' sizes='(max-width: 600px) 480px, 800px' alt='some alt text' />", | ||
errors: errorsJSX, | ||
Comment on lines
+44
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should not be rejected? |
||
}, | ||
], | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import {createRule} from '../util'; | ||
import type {TSESTree} from '@typescript-eslint/types/dist/ts-estree'; | ||
|
||
type Options = []; | ||
type MessageIds = 'image'; | ||
|
||
const docsUrl = | ||
'https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-ideal-image'; | ||
|
||
export default createRule<Options, MessageIds>({ | ||
name: 'prefer-ideal-image', | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: | ||
'enforce using Docusaurus IdealImage plugin component instead of <img> tags', | ||
recommended: false, | ||
}, | ||
schema: [], | ||
messages: { | ||
image: `Do not use an \`<img>\` element to embed images. Use the \`<IdealImage />\` component from \`@theme/IdealImage\` instead. See ${docsUrl}`, | ||
}, | ||
}, | ||
defaultOptions: [], | ||
|
||
create(context) { | ||
return { | ||
JSXOpeningElement(node) { | ||
const elementName = (node.name as TSESTree.JSXIdentifier).name; | ||
|
||
if (elementName !== 'img') { | ||
return; | ||
} | ||
|
||
context.report({node, messageId: 'image'}); | ||
}, | ||
}; | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
--- | ||
slug: /api/misc/@docusaurus/eslint-plugin/prefer-ideal-image | ||
--- | ||
|
||
# prefer-ideal-image | ||
|
||
Ensure that the `<IdealImage />` component provided by [`@theme/IdealImage`](../../plugins/plugin-ideal-image.mdx) Docusaurus plugin is used instead of `<img>` tags. | ||
|
||
The `@theme/IdealImage` Docusaurus plugin generates an almost ideal image (responsive, lazy-loading, and low quality placeholder). | ||
|
||
## Rule Details {#details} | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```html | ||
<img src="./path/to/img.png" alt="some alt text" /> | ||
|
||
<img src={require('./path/to/img.png')} alt='some alt text' /> | ||
|
||
<img | ||
src="./path/to/img.png" | ||
srcset="./path/to/img-480w.jpg 480w, ./path/to/img-800w.png 800w" | ||
sizes="(max-width: 600px) 480px, 800px" | ||
alt="some alt text" /> | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```javascript | ||
import Image from '@theme/IdealImage'; | ||
|
||
<IdealImage img='./path/to/img.png' /> | ||
|
||
<IdealImage img={require('./path/to/img.png')} /> | ||
|
||
<IdealImage | ||
img={{ | ||
src: { | ||
src: './path/to/img.png', | ||
preSrc: '', | ||
images: [{ width: 100 }] | ||
}, | ||
preSrc: './path/to/placeholder.png' | ||
}} | ||
/> | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
import type {ComponentProps} from 'react'; | ||
import React from 'react'; | ||
import Link from '@docusaurus/Link'; | ||
import Image from '@theme/IdealImage'; | ||
|
||
export default function ProductHuntCard({ | ||
className, | ||
|
@@ -21,8 +22,8 @@ export default function ProductHuntCard({ | |
to="https://www.producthunt.com/posts/docusaurus-2-0?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-docusaurus-2-0" | ||
className={className} | ||
style={{display: 'block', width: 250, height: 54, ...style}}> | ||
<img | ||
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=353916&theme=light" | ||
<Image | ||
img="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=353916&theme=light" | ||
Comment on lines
-24
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideal image is not designed to work with external URLs. |
||
alt="Docusaurus 2.0 - Build optimized websites quickly, focus on your content. | Product Hunt" | ||
style={{width: 250, height: 54, maxWidth: 'initial'}} | ||
width={250} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ import React, {type ReactNode} from 'react'; | |
import Translate from '@docusaurus/Translate'; | ||
import Link from '@docusaurus/Link'; | ||
import Heading from '@theme/Heading'; | ||
import Image from '@theme/IdealImage'; | ||
|
||
function WebsiteLink({to, children}: {to: string; children?: ReactNode}) { | ||
return ( | ||
|
@@ -40,9 +41,9 @@ function TeamProfileCard({ | |
<div className="card card--full-height"> | ||
<div className="card__header"> | ||
<div className="avatar avatar--vertical"> | ||
<img | ||
<Image | ||
className="avatar__photo avatar__photo--xl" | ||
src={`${githubUrl}.png`} | ||
img={`${githubUrl}.png`} | ||
Comment on lines
-43
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideal image is not designed to work with external URLs. |
||
alt={`${name}'s avatar`} | ||
/> | ||
<div className="avatar__intro"> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ import React, {type ReactNode} from 'react'; | |
import clsx from 'clsx'; | ||
|
||
import Link from '@docusaurus/Link'; | ||
import Image from '@theme/IdealImage'; | ||
import styles from './styles.module.css'; | ||
|
||
export interface Props { | ||
|
@@ -33,10 +34,10 @@ export default function Tweet({ | |
<div className={clsx('card', styles.tweet)}> | ||
<div className="card__header"> | ||
<div className="avatar"> | ||
<img | ||
<Image | ||
alt={name} | ||
className="avatar__photo" | ||
src={`https://unavatar.io/twitter/${handle}?fallback=https://github.com/${githubUsername}.png`} | ||
img={`https://unavatar.io/twitter/${handle}?fallback=https://github.com/${githubUsername}.png`} | ||
Comment on lines
-36
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideal image is not designed to work with external URLs. |
||
width="48" | ||
height="48" | ||
loading="lazy" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ import React, {type ReactNode} from 'react'; | |
import clsx from 'clsx'; | ||
|
||
import Link from '@docusaurus/Link'; | ||
import Image from '@theme/IdealImage'; | ||
import styles from './styles.module.css'; | ||
|
||
export interface Props { | ||
|
@@ -37,10 +38,10 @@ export default function TweetQuote({ | |
<figcaption> | ||
<Link to={profileUrl} rel="nofollow"> | ||
<div className="avatar"> | ||
<img | ||
<Image | ||
alt={name} | ||
className={clsx('avatar__photo', styles.avatarImg)} | ||
src={avatar} | ||
img={avatar} | ||
Comment on lines
+41
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideal image is not designed to work with external URLs. |
||
// loading="lazy" | ||
/> | ||
<div className={clsx('avatar__intro')}> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,10 +31,10 @@ function HeroBanner() { | |
<div className={styles.hero} data-theme="dark"> | ||
<div className={styles.heroInner}> | ||
<Heading as="h1" className={styles.heroProjectTagline}> | ||
<img | ||
<Image | ||
alt={translate({message: 'Docusaurus with Keytar'})} | ||
className={styles.heroLogo} | ||
src={useBaseUrl('/img/docusaurus_keytar.svg')} | ||
img={useBaseUrl('/img/docusaurus_keytar.svg')} | ||
Comment on lines
-34
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the ideal image plugin is not designed to work with SVGs Eventually we could import the svgs and use them as components instead of using |
||
width="200" | ||
height="200" | ||
/> | ||
|
@@ -190,12 +190,12 @@ function Feature({ | |
|
||
return ( | ||
<div className={clsx('col', className)}> | ||
<img | ||
<Image | ||
className={styles.featureImage} | ||
alt={feature.title} | ||
width={Math.floor(feature.image.width)} | ||
height={Math.floor(feature.image.height)} | ||
src={withBaseUrl(feature.image.src)} | ||
img={withBaseUrl(feature.image.src)} | ||
Comment on lines
+193
to
+198
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the ideal image plugin is not designed to work with SVGs Eventually we could import the svgs and use them as components instead of using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I had noticed that it only works with JPEGs and PNGs but somehow completely missed out in this case 😅
@slorber Should I address this change in this PR? |
||
loading="lazy" | ||
/> | ||
<Heading as="h3" className={clsx(styles.featureHeading)}> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ import React from 'react'; | |
import clsx from 'clsx'; | ||
import Link from '@docusaurus/Link'; | ||
import type {Props} from '@theme/BlogPostItem/Header/Author'; | ||
import Image from '@theme/IdealImage'; | ||
|
||
import styles from './styles.module.css'; | ||
|
||
|
@@ -21,16 +22,25 @@ export default function ChangelogAuthor({ | |
<div className={clsx('avatar margin-bottom--sm', className)}> | ||
{imageURL && ( | ||
<Link className="avatar__photo-link avatar__photo" href={url}> | ||
<img | ||
<Image | ||
className={styles.image} | ||
src={imageURL} | ||
alt={name} | ||
onError={(e) => { | ||
// Image returns 404 if the user's handle changes. We display a | ||
// fallback instead. | ||
e.currentTarget.src = | ||
'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" fill="none" stroke="%2325c2a0" stroke-width="30" version="1.1"><circle cx="300" cy="230" r="115"/><path stroke-linecap="butt" d="M106.81863443903,481.4 a205,205 1 0,1 386.36273112194,0"/></svg>'; | ||
img={{ | ||
src: { | ||
src: imageURL, | ||
preSrc: | ||
'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" fill="none" stroke="%2325c2a0" stroke-width="30" version="1.1"><circle cx="300" cy="230" r="115"/><path stroke-linecap="butt" d="M106.81863443903,481.4 a205,205 1 0,1 386.36273112194,0"/></svg>', | ||
images: [], | ||
}, | ||
preSrc: | ||
'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" fill="none" stroke="%2325c2a0" stroke-width="30" version="1.1"><circle cx="300" cy="230" r="115"/><path stroke-linecap="butt" d="M106.81863443903,481.4 a205,205 1 0,1 386.36273112194,0"/></svg>', | ||
}} | ||
alt={name} | ||
// onError={(e) => { | ||
// // Image returns 404 if the user's handle changes. We display a | ||
// // fallback instead. | ||
// e.currentTarget.src = | ||
// 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" fill="none" stroke="%2325c2a0" stroke-width="30" version="1.1"><circle cx="300" cy="230" r="115"/><path stroke-linecap="butt" d="M106.81863443903,481.4 a205,205 1 0,1 386.36273112194,0"/></svg>'; | ||
// }} | ||
Comment on lines
-24
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that seems overly complicated, using |
||
/> | ||
</Link> | ||
)} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should not be rejected?