Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
40 changes: 20 additions & 20 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
# 5.5.0

- Ajout du réglage `is_lcp` à `get_the_image()` (fetchpriority high, decoding sync, pas de lazy loading)
- Ajout du réglage `is_lcp` à `get_the_image()` (`fetchpriority` élevé, pas de lazy loading ; `decoding` en opt-in via les attributs)
- Ne jamais produire `fetchpriority="high"` avec `loading="lazy"` dans les helpers d’images
- Ajout des helpers `get_theme_static_image_markup()` : images statiques du thème avec width/height (CLS) et conservation de `alt=""` pour les images décoratives
- Abaissement du `fetchpriority` du logo personnalisé pour ne pas concurrencer l’image LCP
- Ajout du filtre `bea_theme_framework_preload_fonts` (vide par défaut) pour précharger les polices above-the-fold
- Réécriture de `Animation` avec des IntersectionObservers partagés au lieu d’une boucle au scroll (mêmes options et callbacks)
- Ajout des helpers `get_theme_static_image_markup()` / `the_theme_static_image()` : images statiques du thème avec `width` / `height` (CLS) et conservation de `alt=""` pour les images décoratives
- Stockage des dimensions des images statiques dans `dist/assets.json` au build (`imageDimensions`) pour éviter les lectures disque répétées à la requête
- Ajout du filtre `bea_theme_framework_preload_fonts` (vide par défaut) pour précharger les polices affichées au premier rendu
- Réécriture de `Animation` avec des `IntersectionObserver` partagés au lieu d’une boucle au scroll (mêmes options et callbacks)
- Ajout de l’utilitaire JS `deferInitialization()` pour exécuter du code non critique à idle (INP)
- Documentation de la base Core Web Vitals dans le README
- Documentation de la base Core Web Vitals dans le README (dont compatibilité ARI / `is_lcp`)

# 5.3.2

- Mise à jour de CHANGELOG.md et package.json avec le numéro de version BFF le plus récent
- Indentation par tabulations imposée dans la base de code
- Remplacement de toutes les règles dépréciées des dernières évolutions Dart Sass
- Ajout d’une option d’attribut role dans le service link
- Ajout d’une option d’attribut `role` dans le service link
- Suppression du contenu personnalisé du fichier index.php
- Suppression du package browsersync
- Améliorations a11y (skip link, bloc image, bloc tableau)
- Suppression du package BrowserSync
- Améliorations a11y (lien d’évitement, bloc image, bloc tableau)
- Ajout du lien de partage Bluesky
- Suppression du custom JS each function
- Ajout de la logique de paramètre URL pour désactiver les animations JS/CSS
- Mise à jour de la version du package oneloop js
- Suppression du custom JS « each »
- Ajout de la logique de paramètre d’URL pour désactiver les animations JS/CSS
- Mise à jour de la version du package oneloop.js
- Passage de Node 20.11.0 à 22.13.0
- Ajout du comportement logique RTL
- Mise à jour des règles CSS ACF vers Gutenberg
Expand All @@ -43,7 +43,7 @@
# 5.3.0 et 5.3.1

- Correction de l’exemple de namespace
- Injection de données dans get_terms_list
- Injection de données dans `get_terms_list`

# 5.2.1

Expand All @@ -52,14 +52,14 @@
# 5.2.0

- Mise à jour des packages npm @wordpress
- Refactor des scripts éditeur pour utiliser les packages @wordpress
- Refactorisation des scripts éditeur pour utiliser les packages @wordpress
- Mise à jour de l’enregistrement des assets avec le nouveau mécanisme asset data
- Ajout d’une nouvelle méthode pour charger les données
- Ajout et configuration du package dependency-extraction-webpack-plugin

# 5.1.6

- Mise à jour des packages npm postcss
- Mise à jour des packages npm PostCSS

# 5.1.5

Expand All @@ -76,7 +76,7 @@
# 5.1.2

- Mise à jour du package npm canvas
- Correction du script webpack des tailles d’images
- Correction du script Webpack des tailles d’images

# 5.1.1

Expand All @@ -102,12 +102,12 @@

# 5.0.6

- Ajout de helpers de templating (the_image, the_text, the_link, etc.)
- Ajout de helpers de templating (`the_image`, `the_text`, `the_link`, etc.)

# 5.0.0

- Ajout de Webpack 5
- Déplacement des fichiers de configuration build dans le dossier config
- Déplacement des fichiers de configuration build dans le dossier `config`
- Suppression des scripts bash
- Optimisation des images côté front
- Suppression des templates statiques
Expand All @@ -122,5 +122,5 @@
- Gestion du JS backend éditeur
- Gestion du CSS frontend/backend éditeur
- Version PHP minimale 7.2
- Suppression de loadFont.php et utilisation de l’enqueue de scripts classique
- Ajout de esc_attr sur defer/async script
- Suppression de `loadFont.php` et utilisation de l’enqueue de scripts classique
- Ajout de `esc_attr` sur les scripts defer/async
32 changes: 21 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ BFF inclut une configuration [wp-env](https://developer.wordpress.org/block-edit
Vous pouvez lancer wp-env sans installation globale via `npx @wordpress/env`, ou installer la CLI une fois et utiliser la commande `wp-env` :

```bash
# Global install (npm or yarn)
# Installation globale (npm ou yarn)
npm install -g @wordpress/env
# ou
yarn global add @wordpress/env
Expand Down Expand Up @@ -198,21 +198,31 @@ yarn bundle-report

### Ce que le thème fournit

- **Image LCP** : passez `'is_lcp' => true` dans les réglages de `get_the_image()` pour l’image principale above-the-fold (hero, couverture). Cela retire `loading="lazy"` et ajoute `fetchpriority="high"` et `decoding="sync"`. À utiliser sur une seule image par page.
- **Image LCP** : passez `'is_lcp' => true` dans les réglages de `get_the_image()` pour l’image principale au-dessus de la ligne de flottaison (hero, couverture). Cela retire `loading="lazy"` et ajoute `fetchpriority="high"`. Définissez `decoding` dans `$attributes` uniquement si une mesure le justifie (ex. WebPageTest). Une seule image par page. Par défaut `is_lcp => false` (opt-in).

```php
the_image( $image_id, [ 'alt' => '' ], [ 'size' => 'hero', 'is_lcp' => true ] );
```

- **Images décoratives** : passez `'alt' => ''` pour produire `alt=""`, afin que les technologies d’assistance ignorent l’image. Omettre `alt` n’est pas équivalent.
- **Images statiques du thème** (logos, placeholders, illustrations depuis `dist/`) : utilisez `get_theme_static_image_markup()` / `the_theme_static_image()`. `width` et `height` sont lus depuis le fichier (SVG ou raster) pour limiter les décalages de mise en page (CLS).
- **ARI (Auto Responsive Images)** : sur les projets avec ARI, ne pas activer `is_lcp` sur les images déjà gérées par ARI (taille / `data-location` au bon format). ARI pilote la priorité et le lazy-load ; `is_lcp` sur la même image est redondant ou incompatible. Réserver `is_lcp` aux projets sans ARI, ou valider manuellement (page hero avec ARI, sans `is_lcp`).

- **Images décoratives (`alt=""`)** — même objectif, deux helpers selon la source :
- **Médiathèque** (`the_image` / `get_the_image`) : passez `'alt' => ''` explicitement si l’image est décorative. Sinon WordPress reprend l’alt saisi dans le BO. Omettre la clé `alt` n’est pas équivalent à `alt=""`.
- **Fichiers buildés** dans `dist/` (`the_theme_static_image` / `get_theme_static_image_markup`) : décoratif par défaut (`alt=""` si vous ne passez pas `alt`). Indiquez un texte d’alt si l’image porte du sens (ex. logo + nom du site). Ne pas utiliser `the_image` pour ces fichiers (pas d’ID attachment).
- **Images statiques du thème** (placeholders, illustrations dans `dist/`) : le helper statique ajoute aussi `width` et `height` via `dist/assets.json` (`imageDimensions`, généré au build) pour limiter le CLS, sans lecture disque répétée lorsque le manifeste est présent. Hors manifeste : une lecture par chemin et par requête.

```php
// Média (décoratif)
the_image( $image_id, [ 'alt' => '', 'class' => 'card__thumb' ], [ 'size' => 'card' ] );

// Asset dist/ (décoratif par défaut)
the_theme_static_image( 'dist/images/placeholder.svg', [ 'class' => 'card__image', 'loading' => 'lazy' ] );

// Asset dist/ (alt porteur de sens)
the_theme_static_image( 'dist/images/logo.svg', [ 'alt' => get_bloginfo( 'name' ), 'class' => 'header__logo' ] );
```

- **Logo personnalisé** : reçoit `fetchpriority="low"` pour ne pas concurrencer la vraie image LCP.
- **Preload des polices** : opt-in, uniquement pour les polices rendues au premier affichage.
- **Preload des polices** : opt-in, uniquement pour les polices rendues au premier affichage. Indiquez le chemin **après build** (ex. polices Fontsource ou custom copiées dans `dist/fonts/`, en `.woff2`).

```php
add_filter( 'bea_theme_framework_preload_fonts', fn() => [ 'dist/fonts/my-font-regular.woff2' ] );
Expand All @@ -224,12 +234,12 @@ add_filter( 'bea_theme_framework_preload_fonts', fn() => [ 'dist/fonts/my-font-r

Cela dépend de l’hébergement, des extensions et du contenu ; ce n’est donc pas inclus dans le thème :

- Cache de page et réglages WP Rocket (Remove Unused CSS, defer / delay JS et leurs exclusions, exclusions lazyload pour l’image LCP, preconnect limités aux tiers chargés tôt).
- Cache de page et réglages WP Rocket (suppression du CSS inutilisé, différer / retarder le JS et leurs exclusions, exclusions du lazyload pour l’image LCP, preconnect limités aux tiers chargés tôt).
- `robots.txt` (endpoints techniques et AJAX) et en-têtes `Cache-Control` pour les assets statiques.
- Réglages du plugin SEO : URLs canoniques sur pagination personnalisée, doublons de données structurées.
- Polices : sous-ensemble des fichiers aux graisses et glyphes réellement utilisés, suppression des fichiers inutiles (italiques, graisses supplémentaires).
- Intégrations tierces (vidéos, formulaires, webinars) : chargement à l’interaction ou à l’entrée dans le viewport.
- Assets des plugins : dequeue des scripts et styles front sur les pages qui ne les utilisent pas.
- Assets des extensions : retirer les scripts et styles front (dequeue) sur les pages qui ne les utilisent pas.

## Éditeur WordPress (Gutenberg)

Expand All @@ -240,13 +250,13 @@ Le filtre `bff_editor_custom_settings` permet de personnaliser les styles et var
```php
add_filter( 'bff_editor_custom_settings', 'customize_editor_settings', 10, 1 );
function customize_editor_settings( $settings ) {
// Disable all block styles for Separator block
// Désactiver tous les styles du bloc Séparateur
$settings[ 'disableAllBlocksStyles' ] = [ 'core/separator' ];

// Disable specific block style for Button block
// Désactiver le style outline du bloc Bouton
$settings[ 'disabledBlocksStyles' ] = [ 'core/button' => [ 'outline' ] ];

// Allow only YouTube variation for Embed block
// Autoriser uniquement la variation YouTube du bloc Intégration
$settings[ 'allowedBlocksVariations' ] = [ 'core/embed' => [ 'youtube' ] ];

return $settings;
Expand Down
2 changes: 2 additions & 0 deletions config/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const DependencyExtractionWebpackPlugin = require('@wordpress/dependency-extract
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

const WebpackImageSizesPlugin = require('./webpack-image-sizes-plugin')
const WebpackStaticImageDimensionsPlugin = require('./webpack-static-image-dimensions-plugin')
const WebpackThemeJsonPlugin = require('./webpack-theme-json-plugin')
const SpriteHashPlugin = require('./webpack-sprite-hash-plugin')
const WebpackIconFilesPlugin = require('./webpack-icon-files-plugin')
Expand Down Expand Up @@ -64,6 +65,7 @@ module.exports = {
new WebpackManifestPlugin({
fileName: 'assets.json',
}),
new WebpackStaticImageDimensionsPlugin(),
]

if (mode === 'production') {
Expand Down
144 changes: 144 additions & 0 deletions config/webpack-static-image-dimensions-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
const fs = require('fs')
const path = require('path')

let sharp

try {
sharp = require('sharp')
} catch (error) {
sharp = null
}

const IMAGE_EXT_PATTERN = /\.(png|jpe?g|gif|svg|webp|avif)$/i

/**
* Parse intrinsic SVG dimensions (width/height attributes, then viewBox).
*
* @param {string} contents SVG file contents.
* @return {{ width: number, height: number }|null}
*/
function getSvgDimensionsFromContents(contents) {
if (!contents) {
return null
}

const widthMatch = contents.match(/\bwidth="(\d+(?:\.\d+)?)"/)
const heightMatch = contents.match(/\bheight="(\d+(?:\.\d+)?)"/)

if (widthMatch && heightMatch) {
return {
width: Math.round(Number(widthMatch[1])),
height: Math.round(Number(heightMatch[1])),
}
}

const viewBoxMatch = contents.match(/\bviewBox="[\d.\s-]+?\s+([\d.]+)\s+([\d.]+)"/)

if (viewBoxMatch) {
return {
width: Math.max(1, Math.round(Number(viewBoxMatch[1]))),
height: Math.max(1, Math.round(Number(viewBoxMatch[2]))),
}
}

return null
}

/**
* Read width/height from a built image file under dist/.
*
* @param {string} filePath Absolute path to the image file.
* @return {Promise<{ width: number, height: number }|null>}
*/
async function getImageDimensions(filePath) {
if (!fs.existsSync(filePath)) {
return null
}

if (/\.svg$/i.test(filePath)) {
const contents = fs.readFileSync(filePath, 'utf8')
return getSvgDimensionsFromContents(contents)
}

if (!sharp) {
return null
}

try {
const metadata = await sharp(filePath).metadata()

if (!metadata.width || !metadata.height) {
return null
}

return {
width: metadata.width,
height: metadata.height,
}
} catch (error) {
return null
}
}

/**
* Webpack plugin: merge static image dimensions into dist/assets.json.
*/
class WebpackStaticImageDimensionsPlugin {
constructor(options = {}) {
this.options = {
outputPath: 'dist',
manifestFile: 'assets.json',
imagesSubdir: 'images',
...options,
}
}

apply(compiler) {
compiler.hooks.afterEmit.tapAsync('WebpackStaticImageDimensionsPlugin', async (compilation, callback) => {
try {
const context = compiler.options.context
const outputPath = path.resolve(context, this.options.outputPath)
const manifestPath = path.join(outputPath, this.options.manifestFile)
const imageDimensions = {}

const assets = compilation.getAssets()

for (const asset of assets) {
if (!asset.name.startsWith(`${this.options.imagesSubdir}/`) || !IMAGE_EXT_PATTERN.test(asset.name)) {
continue
}

const filePath = path.join(outputPath, asset.name)
const dimensions = await getImageDimensions(filePath)

if (!dimensions) {
continue
}

const themeRelativePath = `dist/${asset.name.replace(/\\/g, '/')}`
imageDimensions[themeRelativePath] = dimensions
}

let manifest = {}

if (fs.existsSync(manifestPath)) {
const raw = fs.readFileSync(manifestPath, 'utf8')
const parsed = JSON.parse(raw)

if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
manifest = parsed
}
}

manifest.imageDimensions = imageDimensions

fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
callback()
} catch (error) {
callback(error)
}
})
}
}

module.exports = WebpackStaticImageDimensionsPlugin
Loading
Loading