Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,22 @@ Add `@creedengo` to the `plugins` section of your `.eslintrc`, followed by rules
⚠️ Configurations set to warn in.\
✅ Set in the `recommended` configuration.

| Name | Description | ⚠️ |
| :------------------------------------------------------------------------------------- | :-------------------------------------------------------- | :- |
| [avoid-autoplay](docs/rules/avoid-autoplay.md) | Avoid autoplay for videos and audio content | ✅ |
| [avoid-brightness-override](docs/rules/avoid-brightness-override.md) | Should avoid to override brightness | ✅ |
| [avoid-css-animations](docs/rules/avoid-css-animations.md) | Avoid usage of CSS animations | ✅ |
| [avoid-high-accuracy-geolocation](docs/rules/avoid-high-accuracy-geolocation.md) | Avoid using high accuracy geolocation in web applications | ✅ |
| [limit-db-query-results](docs/rules/limit-db-query-results.md) | Should limit the number of returns for a SQL query | ✅ |
| [no-empty-image-src-attribute](docs/rules/no-empty-image-src-attribute.md) | Disallow usage of image with empty source attribute | ✅ |
| [no-import-all-from-library](docs/rules/no-import-all-from-library.md) | Should not import all from library | ✅ |
| [no-multiple-access-dom-element](docs/rules/no-multiple-access-dom-element.md) | Disallow multiple access of same DOM element | ✅ |
| [no-multiple-style-changes](docs/rules/no-multiple-style-changes.md) | Disallow multiple style changes at once | ✅ |
| [no-torch](docs/rules/no-torch.md) | Should not programmatically enable torch mode | ✅ |
| [prefer-collections-with-pagination](docs/rules/prefer-collections-with-pagination.md) | Prefer API collections with pagination | ✅ |
| [prefer-shorthand-css-notations](docs/rules/prefer-shorthand-css-notations.md) | Encourage usage of shorthand CSS notations | ✅ |
| [provide-print-css](docs/rules/provide-print-css.md) | Enforce providing a print stylesheet | ✅ |
| Name | Description | ⚠️ |
| :--------------------------------------------------------------------------------------------- | :-------------------------------------------------------- | :- |
| [avoid-autoplay](docs/rules/avoid-autoplay.md) | Avoid autoplay for videos and audio content | ✅ |
| [avoid-brightness-override](docs/rules/avoid-brightness-override.md) | Should avoid to override brightness | ✅ |
| [avoid-css-animations](docs/rules/avoid-css-animations.md) | Avoid usage of CSS animations | ✅ |
| [avoid-high-accuracy-geolocation](docs/rules/avoid-high-accuracy-geolocation.md) | Avoid using high accuracy geolocation in web applications | ✅ |
| [limit-db-query-results](docs/rules/limit-db-query-results.md) | Should limit the number of returns for a SQL query | ✅ |
| [no-empty-image-src-attribute](docs/rules/no-empty-image-src-attribute.md) | Disallow usage of image with empty source attribute | ✅ |
| [no-import-all-from-library](docs/rules/no-import-all-from-library.md) | Should not import all from library | ✅ |
| [no-multiple-access-dom-element](docs/rules/no-multiple-access-dom-element.md) | Disallow multiple access of same DOM element | ✅ |
| [no-multiple-style-changes](docs/rules/no-multiple-style-changes.md) | Disallow multiple style changes at once | ✅ |
| [no-torch](docs/rules/no-torch.md) | Should not programmatically enable torch mode | ✅ |
| [prefer-collections-with-pagination](docs/rules/prefer-collections-with-pagination.md) | Prefer API collections with pagination | ✅ |
| [prefer-lighter-formats-for-image-files](docs/rules/prefer-lighter-formats-for-image-files.md) | Prefer lighter formats for image files | ✅ |
| [prefer-shorthand-css-notations](docs/rules/prefer-shorthand-css-notations.md) | Encourage usage of shorthand CSS notations | ✅ |
| [provide-print-css](docs/rules/provide-print-css.md) | Enforce providing a print stylesheet | ✅ |

<!-- end auto-generated rules list -->

Expand Down
100 changes: 100 additions & 0 deletions eslint-plugin/docs/rules/prefer-lighter-formats-for-image-files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Prefer lighter formats for image files (`@creedengo/prefer-lighter-formats-for-image-files`)

⚠️ This rule _warns_ in the ✅ `recommended` config.

<!-- end auto-generated rule header -->

## Why is this an issue?

Using appropriate image formats and optimizing image sizes is essential for improving website performance, user experience, and overall environmental impact.
Larger image file sizes consume more bandwidth, increasing the data transfer required to load a web page.
Some image formats are generally considered better for eco-friendly web design and should be used in most cases.

We recommend using the following formats:

- *WebP*, developed by Google, is a modern image format that provides high compression efficiency without significant loss of quality.
- *AVIF* (AV1 Image File Format) is a relatively new and highly efficient image format that is based on the AV1 video codec.
- *SVG* (Scalable Vector Graphics) is a vector image format that is based on XML.
Files are lightweight and can be scaled without loss of quality.

```html
<img src="./assets/images/cat.jpg" alt="Unoptimized image of a cat"/> // Non-compliant

<img src="./assets/images/cat.webp" alt="Optimized image of a cat"/> // Compliant
```

Remember that the best image format may vary depending on the specific use case, content, and requirements of your website.
Always test and evaluate the performance of different formats to find the optimal balance between image quality and file size.

### Picture

Images often represent most of the downloaded bytes, right after videos and just before CSS and JavaScript libraries.
Optimizing images is important to reduce used bandwidth. The first step is to choose the ideal format for your
display needs.

Raster images should be reserved for photos and interface elements that cannot be displayed with icons or CSS styles.

The appropriate format depends on the image properties : black & white or color, color palette, need for transparency...
Among these properties, the possibility to irremediably alter images quality (lossy compression) tends to favor formats such as JPEG, JPEG XL,
AVIF, or WebP, while needing transparency and/or the impossibility to alter the image quality (lossless compression) will tend to favor
PNG or WebP lossless formats (which supports transparency).

Format importantly impacts images size: on average, .webp images will be 30% lighter than .jpeg
images or .png images. .avif images can be up to 20% lighter than .webp image and 50% lighter than .jepg images.

Don't forget to pay attention to browser support. .webp images will not be recognized by
old browsers and will not be displayed. It is possible to provide several formats for the same image
to overcome this issue. Some server-side modules (such as Google's modPageSpeed, also available for Apache
and Nginx) even allow you to provide the appropriate image for the browser that is calling the server.

Many tools will help you minimize images size:

- SQUOOSH
- CLOUDINARY
- ImageMagick
- PngCrush
- JpegTran

### Example

In this example, the DOM <picture> element informs the browser that there are two images: a .webp image and a
.jpg image, which is used by default. The browser will decide which image will be downloaded. If the .webp format
is supported, the image.webp image will be downloaded; otherwise, image.jpg image will be downloaded.

```html
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="..." loading="lazy">
</picture>
```

Also remember to consider browser compatibility.
Older browsers may not recognize .webp/.avif images and fail to display them.
To address this issue, you can supply multiple formats for the same image.

## Resources

### Documentation

- https://github.com/cnumr/best-practices/blob/main/chapters/BP_080_en.md[CNUMR best practices] - Optimize images

### Articles & blog posts

- https://greenspector.com/en/which-image-format-to-choose-to-reduce-its-energy-consumption-and-its-environmental-impact/[greenspector.com - Which image format choose to reduce energy consumption and environmental impact?]
- https://dodonut.com/blog/use-cases-of-web-image-formats/[dodonut.com - The Most Efficient Web Image Formats. Use Cases For Different Types Of Images.]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There are some issues with the Markdown format here


### WSG
- [UX15-2: Optimizing All Image Assets for a Variety of Different Resolutions](https://w3c.github.io/sustyweb/star.html#UX15-2)

### RGESN 5.1

- https://ecoresponsable.numerique.gouv.fr/publications/referentiel-general-ecoconception/critere/5.1/

#### Objective
Reduce the size of files downloaded by users.

#### How to implement
Choose the image format best suited to the type of image and display context: use vector formats such as svg whenever possible (illustrations, icons, logos, graphs, etc.), jpg for photos and png for illustrations with solid colors.

#### Means of test or control
Evaluate the relevance of the image format displayed.
81 changes: 81 additions & 0 deletions eslint-plugin/lib/rules/prefer-lighter-formats-for-image-files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

"use strict";

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "Prefer lighter formats for image files",
category: "eco-design",
recommended: "warn",
},
messages: {
DefineFormatsForImageFiles:
"You should define a format for your image files and use light formats such as {{ eligibleExtensions }}",
PreferLighterFormatsForImageFiles:
"You should use lighter formats for image files such as : {{ eligibleExtensions }}",
},
schema: [],
},
create(context) {
return {
JSXOpeningElement(node) {
const tagName = node.name.name;
if (tagName?.toLowerCase() !== "img") return;

const parentTagName = node.parent?.parent?.openingElement?.name?.name;
if (parentTagName?.toLowerCase() === "picture") return;

const eligibleExtensions = ["webp", "avif", "svg", "jxl"];

const srcAttribut = node.attributes.find(
(attr) => attr.name.name === "src",
);

let srcValue = srcAttribut?.value?.value;

if (!srcValue) return;

srcValue = srcValue.substring(srcValue.lastIndexOf("/") + 1);
const dotIndex = srcValue.lastIndexOf(".");

if (dotIndex === -1) {
context.report({
node,
messageId: "DefineFormatsForImageFiles",
data: { eligibleExtensions: eligibleExtensions.join(", ") },
});
return;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To avoid any false-positive report, I propose to remove this check.

If the source filename does not have an extension, we can't know which format the file uses. Some cloud/storage services does not use extensions in URL but are sending a content-type header instead. In this case we do not want to warn the developer because the best practice may be respected.


const imgExtension = srcValue.substring(dotIndex + 1);

if (eligibleExtensions.includes(imgExtension.toLowerCase())) return;

context.report({
node,
messageId: "PreferLighterFormatsForImageFiles",
data: { eligibleExtensions: eligibleExtensions.join(", ") },
});
},
};
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/prefer-lighter-formats-for-image-files");
const RuleTester = require("eslint").RuleTester;

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 2021,
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
});
const defineFormatsForImageFilesError = {
messageId: "DefineFormatsForImageFiles",
type: "JSXOpeningElement",
};
const preferLighterFormatsForImageFilesError = {
messageId: "PreferLighterFormatsForImageFiles",
type: "JSXOpeningElement",
};

ruleTester.run("prefer-lighter-formats-for-image-files", rule, {
valid: [
`
<img src="./assets/images/cat.webp" alt="A cat"/>
`,
`
<img src="./assets/images/cat.avif" alt="A cat"/>
`,
`
<img src="./assets/images/cat.jxl" alt="A cat"/>
`,
`
<picture>
<source srcSet="image.webp" type="image/webp" />
<img src="image.jpg" alt="..." />
</picture>
`,
`
<img src="" alt="" />
`,
],

invalid: [
{
code: `
<img src="./assets/images/cat.jpg" alt="A cat"/>
`,
errors: [preferLighterFormatsForImageFilesError],
},
{
code: `
<img src="./assets/images/cat.png" alt="A cat"/>
`,
errors: [preferLighterFormatsForImageFilesError],
},
{
code: `
<img src="./assets/images/cat" alt="A cat"/>
`,
errors: [defineFormatsForImageFilesError],
},
{
code: `
<img src="assets/images.dir/cat" alt="A cat"/>
`,
errors: [defineFormatsForImageFilesError],
},
{
code: `
<img src="./assets/images.dir/cat" alt="A cat"/>
`,
errors: [defineFormatsForImageFilesError],
},
],
});
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public static List<Class<? extends JavaScriptCheck>> getAllChecks() {
NoMultipleStyleChanges.class,
NoTorch.class,
PreferCollectionsWithPagination.class,
PreferLighterFormatsForImageFiles.class,
PreferShorthandCSSNotations.class,
ProvidePrintCSS.class
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.greencodeinitiative.creedengo.javascript.checks;

import org.sonar.check.Rule;
import org.sonar.plugins.javascript.api.EslintBasedCheck;
import org.sonar.plugins.javascript.api.JavaScriptRule;
import org.sonar.plugins.javascript.api.TypeScriptRule;

@JavaScriptRule
@TypeScriptRule
@Rule(key = PreferLighterFormatsForImageFiles.RULE_KEY)
public class PreferLighterFormatsForImageFiles implements EslintBasedCheck {

public static final String RULE_KEY = "GCI31";

@Override
public String eslintKey() {
return "@creedengo/prefer-lighter-formats-for-image-files";
}

}