diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eb99c6..4148966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - [#40](https://github.com/green-code-initiative/creedengo-javascript/pull/40) Add rule `@creedengo/avoid-autoplay` (GCI36) +- [#46](https://github.com/green-code-initiative/creedengo-javascript/pull/46) Add rule `@creedengo/prefer-lighter-formats-for-image-files` (GCI31) ## [2.0.0] - 2025-01-22 diff --git a/eslint-plugin/README.md b/eslint-plugin/README.md index 0285cea..35086e1 100644 --- a/eslint-plugin/README.md +++ b/eslint-plugin/README.md @@ -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 | ✅ | diff --git a/eslint-plugin/docs/rules/prefer-lighter-formats-for-image-files.md b/eslint-plugin/docs/rules/prefer-lighter-formats-for-image-files.md new file mode 100644 index 0000000..d8e3e29 --- /dev/null +++ b/eslint-plugin/docs/rules/prefer-lighter-formats-for-image-files.md @@ -0,0 +1,88 @@ +# Prefer lighter formats for image files (`@creedengo/prefer-lighter-formats-for-image-files`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + + +## 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 +Unoptimized image of a cat // +Non-compliant + +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 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 + + + ... + +``` + +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 + +- [CNUMR best practices](https://github.com/cnumr/best-practices/blob/main/chapters/BP_080_en.md) - Optimize images +- [WSG UX15-2](https://w3c.github.io/sustyweb/star.html#UX15-2) - Optimizing All Image Assets for a Variety of Different Resolutions +- [RGESN 5.1](https://ecoresponsable.numerique.gouv.fr/publications/referentiel-general-ecoconception/critere/5.1/) - Référentiel général d'écoconception de services numériques 🇫🇷 + +### Articles & blog posts + +- [greenspector.com - Which image format choose to reduce energy consumption and environmental impact?](https://greenspector.com/en/which-image-format-to-choose-to-reduce-its-energy-consumption-and-its-environmental-impact/) +- [dodonut.com - The Most Efficient Web Image Formats. Use Cases For Different Types Of Images.](https://dodonut.com/blog/use-cases-of-web-image-formats/) diff --git a/eslint-plugin/lib/rules/prefer-lighter-formats-for-image-files.js b/eslint-plugin/lib/rules/prefer-lighter-formats-for-image-files.js new file mode 100644 index 0000000..f3c9c85 --- /dev/null +++ b/eslint-plugin/lib/rules/prefer-lighter-formats-for-image-files.js @@ -0,0 +1,72 @@ +/* + * 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 . + */ + +"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: { + PreferLighterFormatsForImageFiles: + "You should use lighter formats for image files such as {{ eligibleExtensions }}", + }, + schema: [], + }, + create(context) { + const eligibleExtensions = ["webp", "avif", "svg", "jxl"]; + + 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 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) return; + + const imgExtension = srcValue.substring(dotIndex + 1); + + if (eligibleExtensions.includes(imgExtension.toLowerCase())) return; + + context.report({ + node, + messageId: "PreferLighterFormatsForImageFiles", + data: { eligibleExtensions: eligibleExtensions.join(", ") }, + }); + }, + }; + }, +}; diff --git a/eslint-plugin/tests/lib/rules/prefer-lighter-formats-for-image-files.js b/eslint-plugin/tests/lib/rules/prefer-lighter-formats-for-image-files.js new file mode 100644 index 0000000..979fe9d --- /dev/null +++ b/eslint-plugin/tests/lib/rules/prefer-lighter-formats-for-image-files.js @@ -0,0 +1,86 @@ +/* + * 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 . + */ + +"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 preferLighterFormatsForImageFilesError = { + messageId: "PreferLighterFormatsForImageFiles", + type: "JSXOpeningElement", +}; + +ruleTester.run("prefer-lighter-formats-for-image-files", rule, { + valid: [ + ` + A cat + `, + ` + A cat + `, + ` + A cat + `, + ` + + + ... + + `, + ` + A cat + `, + ` + + `, + ], + + invalid: [ + { + code: ` + A cat + `, + errors: [preferLighterFormatsForImageFilesError], + }, + { + code: ` + A cat + `, + errors: [preferLighterFormatsForImageFilesError], + }, + ], +}); diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java index b6a6bcd..6fb8973 100644 --- a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java @@ -45,6 +45,7 @@ public static List> getAllChecks() { NoMultipleStyleChanges.class, NoTorch.class, PreferCollectionsWithPagination.class, + PreferLighterFormatsForImageFiles.class, PreferShorthandCSSNotations.class, ProvidePrintCSS.class ); diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/PreferLighterFormatsForImageFiles.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/PreferLighterFormatsForImageFiles.java new file mode 100644 index 0000000..58dd4b8 --- /dev/null +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/PreferLighterFormatsForImageFiles.java @@ -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 . + */ +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"; + } + +} diff --git a/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json b/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json index e20d8f5..8be08f9 100644 --- a/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json +++ b/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json @@ -9,6 +9,7 @@ "GCI26", "GCI29", "GCI30", + "GCI31", "GCI36", "GCI523", "GCI530"