<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Design on kenji.blog</title><link>http://kenji.blog/fr/categories/design/</link><description>Recent content in Design on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>fr</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 03:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/fr/categories/design/index.xml" rel="self" type="application/rss+xml"/><item><title>Personnalisation du thème Hugo : Comment intégrer Tailwind CSS</title><link>http://kenji.blog/fr/p/hugo-tailwind-css-customization-guide/</link><pubDate>Sun, 13 Sep 2026 03:00:00 +0900</pubDate><guid>http://kenji.blog/fr/p/hugo-tailwind-css-customization-guide/</guid><description>&lt;img src="http://kenji.blog/p/hugo-tailwind-css-customization-guide/img/eyecatch.jpg" alt="Featured image of post Personnalisation du thème Hugo : Comment intégrer Tailwind CSS" />&lt;h1 id="introduction--la-puissante-synergie-entre-le-générateur-de-site-statique-hugo-et-tailwind-css">Introduction : La puissante synergie entre le générateur de site statique Hugo et Tailwind CSS
&lt;/h1>&lt;p>Dans le développement Web front-end moderne, concilier les performances et l&amp;rsquo;expérience de développement (DX : Developer Experience) est l&amp;rsquo;une des priorités majeures de tout projet. L&amp;rsquo;association de &lt;strong>Hugo&lt;/strong>, qui possède l&amp;rsquo;une des vitesses de compilation les plus rapides au monde parmi les générateurs de sites statiques (SSG), et de &lt;strong>Tailwind CSS&lt;/strong>, qui a introduit le paradigme novateur du &amp;ldquo;utility-first&amp;rdquo; (utilitaire d&amp;rsquo;abord), constitue sans doute l&amp;rsquo;une des réponses ultimes à ce défi.&lt;/p>
&lt;p>Hugo est écrit en Go et présente des performances exceptionnelles, permettant de compiler un site de plusieurs milliers de pages en quelques secondes, voire en millisecondes. D&amp;rsquo;autre part, Tailwind CSS permet d&amp;rsquo;accélérer l&amp;rsquo;itération du design en éliminant les allers-retours entre les fichiers CSS et HTML grâce à l&amp;rsquo;écriture directe d&amp;rsquo;innombrables classes utilitaires prédéfinies (&lt;code>flex&lt;/code>, &lt;code>text-center&lt;/code>, &lt;code>mt-4&lt;/code>, etc.) dans le code HTML.&lt;/p>
&lt;p>Cet article explique de manière approfondie et détaillée comment intégrer Tailwind CSS à un thème Hugo, et comment construire un pipeline d&amp;rsquo;assets avancé (Hugo Pipes) en utilisant PostCSS, depuis les bases de l&amp;rsquo;architecture jusqu&amp;rsquo;à l&amp;rsquo;optimisation mathématique des performances.&lt;/p>
&lt;hr>
&lt;h2 id="1-lévolution-du-css-utility-first-et-lapproche-orientée-composants">1. L&amp;rsquo;évolution du CSS &amp;ldquo;utility-first&amp;rdquo; et l&amp;rsquo;approche orientée composants
&lt;/h2>&lt;p>Avant de passer aux instructions d&amp;rsquo;intégration de Tailwind CSS, il est très utile de comprendre pourquoi nous devrions utiliser Tailwind CSS, ainsi que l&amp;rsquo;histoire et l&amp;rsquo;évolution de la philosophie de conception CSS qui se cache derrière.&lt;/p>
&lt;h3 id="les-limites-de-la-conception-css-traditionnelle-bem-et-oocss">Les limites de la conception CSS traditionnelle (BEM et OOCSS)
&lt;/h3>&lt;p>Autrefois dans le développement Web, l&amp;rsquo;utilisation de noms de classes sémantiques était considérée comme la meilleure pratique. Par exemple, pour créer un composant de carte, le code HTML et le CSS étaient séparés comme suit :&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;card&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;card__image&amp;#34;&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;image.jpg&amp;#34;&lt;/span> &lt;span class="na">alt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;thumbnail&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;card__content&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h2&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;card__title&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Titre&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h2&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;card__description&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>La description va ici.&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">card&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">border-radius&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">box-shadow&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="mi">6&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="nb">rgba&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mf">0.1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#ffffff&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">overflow&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">hidden&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">card__title&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">1.5&lt;/span>&lt;span class="kt">rem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">font-weight&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">bold&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#333333&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">/* La suite contient d&amp;#39;autres styles détaillés */&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Une conception basée sur BEM (Block Element Modifier) comme celle-ci fonctionne bien lorsque le projet est de petite taille, mais a tendance à causer les problèmes suivants :&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Épuisement et fatigue liés au nommage&lt;/strong> : À chaque fois que vous créez un composant similaire, vous devez penser à un nouveau nom de classe (ex. &lt;code>card-news&lt;/code>, &lt;code>card-featured&lt;/code>, etc.).&lt;/li>
&lt;li>&lt;strong>Gonflement du CSS&lt;/strong> : Le nombre de lignes de CSS augmente à chaque ajout de fonctionnalité. Le CSS existant est rarement supprimé par peur de ne pas savoir &amp;ldquo;où il est utilisé&amp;rdquo;, ce qui conduit à l&amp;rsquo;accumulation de code mort.&lt;/li>
&lt;li>&lt;strong>Changement de contexte&lt;/strong> : Puisque la structure HTML et les styles CSS sont gérés dans des fichiers séparés, le nombre d&amp;rsquo;allers-retours entre les onglets dans l&amp;rsquo;éditeur augmente de manière exponentielle.&lt;/li>
&lt;/ol>
&lt;h3 id="le-changement-de-paradigme-avec-tailwind-css">Le changement de paradigme avec Tailwind CSS
&lt;/h3>&lt;p>Tailwind CSS résout ces problèmes en utilisant l&amp;rsquo;approche de la &amp;ldquo;combinaison de classes utilitaires&amp;rdquo;. Le composant de carte ci-dessus ressemblerait à ceci si vous utilisiez Tailwind CSS :&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;rounded-lg shadow-md bg-white overflow-hidden&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;w-full h-48 object-cover&amp;#34;&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;image.jpg&amp;#34;&lt;/span> &lt;span class="na">alt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;thumbnail&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;p-6&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h2&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-2xl font-bold text-gray-800&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Titre&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h2&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;mt-2 text-gray-600&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>La description va ici.&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Étant donné que les noms de classe eux-mêmes représentent des valeurs de style spécifiques (&lt;code>p-6&lt;/code> équivaut à &lt;code>padding: 1.5rem;&lt;/code>, etc.), vous pouvez prévoir le résultat final du rendu simplement en regardant le HTML. De plus, le compilateur JIT (Just-In-Time) de Tailwind extrait uniquement les classes réellement utilisées dans le fichier CSS de production, ce qui réduit considérablement la taille du fichier CSS.&lt;/p>
&lt;hr>
&lt;h2 id="2-larchitecture-de-hugo-pipes-et-postcss">2. L&amp;rsquo;architecture de Hugo Pipes et PostCSS
&lt;/h2>&lt;p>Pour intégrer Tailwind CSS dans Hugo, il est nécessaire de comprendre le pipeline de traitement des assets appelé &lt;strong>Hugo Pipes&lt;/strong>. Hugo Pipes est une fonctionnalité puissante qui permet de gérer tous les traitements liés aux assets directement dans Hugo, comme la compilation de Sass/SCSS, le bundle et la minification JavaScript, et l&amp;rsquo;exécution de &lt;strong>PostCSS&lt;/strong> que nous utiliserons ici.&lt;/p>
&lt;p>PostCSS est un outil permettant de transformer le CSS à l&amp;rsquo;aide de plugins JavaScript. Tailwind CSS lui-même fonctionne en réalité comme un plugin PostCSS.&lt;/p>
&lt;h3 id="le-mécanisme-de-transformation-ast-abstract-syntax-tree-avec-postcss">Le mécanisme de transformation AST (Abstract Syntax Tree) avec PostCSS
&lt;/h3>&lt;p>Comprendre comment PostCSS traite le CSS est extrêmement utile lors de la résolution de problèmes. Le diagramme Mermaid ci-dessous montre le pipeline par lequel PostCSS lit un fichier CSS, le transforme via des plugins et produit le CSS final.&lt;/p>
&lt;pre class="mermaid">
flowchart TD
A[&amp;#34;CSS brut (styles.css)&amp;#34;] --&amp;gt;|Parseur| B[&amp;#34;AST (Arbre de syntaxe abstraite)&amp;#34;]
B --&amp;gt; C[&amp;#34;Plugin 1 : Tailwind CSS&amp;#34;]
C --&amp;gt; D[&amp;#34;Plugin 2 : Autoprefixer&amp;#34;]
D --&amp;gt; E[&amp;#34;Plugin N : cssnano&amp;#34;]
E --&amp;gt;|Stringifier| F[&amp;#34;CSS compilé et optimisé&amp;#34;]
&lt;/pre>
&lt;ol>
&lt;li>&lt;strong>Parseur&lt;/strong> : Analyse la chaîne CSS brute en entrée et la convertit en AST (Arbre de syntaxe abstraite), une structure de données manipulable par le programme.&lt;/li>
&lt;li>&lt;strong>Plugins&lt;/strong> :
&lt;ul>
&lt;li>&lt;strong>Tailwind CSS&lt;/strong> : Analyse les fichiers de modèle (HTML ou Markdown) et ajoute les classes utilitaires utilisées sous forme de nœuds à l&amp;rsquo;AST. Il développe également les directives &lt;code>@tailwind&lt;/code>.&lt;/li>
&lt;li>&lt;strong>Autoprefixer&lt;/strong> : Consulte la base de données &lt;code>Can I Use&lt;/code> et ajoute les préfixes vendeurs nécessaires (&lt;code>-webkit-&lt;/code>, &lt;code>-moz-&lt;/code>, etc.) aux propriétés de l&amp;rsquo;AST.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Stringifier&lt;/strong> : Convertit l&amp;rsquo;AST transformé en une chaîne CSS compréhensible par le navigateur pour sa sortie.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="3-configuration-de-lenvironnement-et-prérequis">3. Configuration de l&amp;rsquo;environnement et prérequis
&lt;/h2>&lt;p>Passons maintenant aux étapes d&amp;rsquo;intégration concrètes. Tout d&amp;rsquo;abord, nous vérifierons si les logiciels nécessaires sont installés.&lt;/p>
&lt;h3 id="prérequis">Prérequis
&lt;/h3>&lt;ol>
&lt;li>
&lt;p>&lt;strong>Hugo Extended Version&lt;/strong> :
Vous avez besoin de la &lt;strong>version Extended&lt;/strong> de Hugo, et non de la version standard, car elle inclut des fonctionnalités de traitement Sass/SCSS et une intégration native de PostCSS. Exécutez la commande suivante dans le terminal et assurez-vous que la chaîne &lt;code>extended&lt;/code> est présente dans les informations de version.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">hugo version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Sortie attendue :&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># hugo v0.121.2-4146... windows/amd64 BuildDate=... VendorInfo=gohugoio +extended&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>Node.js et npm&lt;/strong> :
Les dépendances comme Tailwind CSS et PostCSS fonctionnent sur Node.js. Assurez-vous que Node.js (version LTS recommandée) est installé.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">node -v
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm -v
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;/ol>
&lt;h3 id="installation-des-paquets-npm">Installation des paquets npm
&lt;/h3>&lt;p>Initialisez npm dans le répertoire racine de votre projet (là où se trouve le fichier de configuration de Hugo &lt;code>hugo.toml&lt;/code>) et installez les paquets nécessaires.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Génération de package.json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm init -y
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Installation de Tailwind CSS, PostCSS et Autoprefixer en tant que dépendances de développement&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm install -D tailwindcss postcss postcss-cli autoprefixer
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;blockquote>
&lt;p>[!IMPORTANT]
Si &lt;code>postcss-cli&lt;/code> n&amp;rsquo;est pas installé, une erreur peut survenir lorsque Hugo appelle PostCSS en interne. Assurez-vous de l&amp;rsquo;installer car Hugo Pipes utilise &lt;code>postcss-cli&lt;/code> en interne.&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h2 id="4-création-des-fichiers-de-configuration-postcss--tailwind-css">4. Création des fichiers de configuration (PostCSS &amp;amp; Tailwind CSS)
&lt;/h2>&lt;p>Une fois l&amp;rsquo;installation des paquets terminée, créez deux fichiers de configuration importants qui contrôlent le comportement du projet. Placez-les dans le répertoire racine du projet.&lt;/p>
&lt;h3 id="création-de-tailwindconfigjs">Création de tailwind.config.js
&lt;/h3>&lt;p>Exécutez la commande suivante dans le terminal pour générer le fichier de configuration par défaut.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">npx tailwindcss init
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Ouvrez le fichier &lt;code>tailwind.config.js&lt;/code> généré dans votre éditeur et configurez la propriété &lt;code>content&lt;/code>. C&amp;rsquo;est une étape très importante. Tailwind analyse les fichiers aux chemins spécifiés ici et extrait les classes utilisées. Spécifiez précisément les fichiers de disposition (layouts) et de contenu en fonction de la structure du projet Hugo.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/** @type {import(&amp;#39;tailwindcss&amp;#39;).Config} */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Spécifie les cibles d&amp;#39;analyse en fonction de la structure du répertoire Hugo
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">content&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;./content/**/*.md&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;./content/**/*.html&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;./layouts/**/*.html&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;./assets/**/*.js&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Si vous utilisez un thème, vous devez également inclure le répertoire du thème
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// &amp;#34;./themes/mon-theme/layouts/**/*.html&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">theme&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">extend&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Les extensions de couleurs personnalisées ou de polices se font ici
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;brand-primary&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;#3490dc&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;brand-secondary&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;#ffed4a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fontFamily&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;sans&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;Helvetica Neue&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Arial&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Hiragino Kaku Gothic ProN&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Meiryo&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;sans-serif&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">plugins&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Ajoutez des plugins officiels si nécessaire (ex. plugin Typography)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// require(&amp;#39;@tailwindcss/typography&amp;#39;),
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="création-de-postcssconfigjs">Création de postcss.config.js
&lt;/h3>&lt;p>Ensuite, créez &lt;code>postcss.config.js&lt;/code> à la racine du projet pour définir quels plugins PostCSS exécuter et dans quel ordre.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">plugins&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">tailwindcss&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">autoprefixer&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Avec cette configuration, lorsque Hugo appellera PostCSS, le traitement de Tailwind CSS sera effectué en premier, suivi de l&amp;rsquo;ajout des préfixes vendeurs par Autoprefixer.&lt;/p>
&lt;hr>
&lt;h2 id="5-construction-du-pipeline-dassets-css-dans-hugo">5. Construction du pipeline d&amp;rsquo;assets CSS dans Hugo
&lt;/h2>&lt;p>Une fois la configuration terminée, nous allons enfin intégrer Tailwind CSS dans le thème Hugo.&lt;/p>
&lt;h3 id="5-1-création-du-fichier-css-point-dentrée">5-1. Création du fichier CSS point d&amp;rsquo;entrée
&lt;/h3>&lt;p>Créez un fichier CSS qui servira de point d&amp;rsquo;entrée dans le répertoire &lt;code>assets/css/&lt;/code> (créez-le s&amp;rsquo;il n&amp;rsquo;existe pas). Nous le nommerons &lt;code>main.css&lt;/code>.&lt;/p>
&lt;p>&lt;strong>Chemin du fichier : &lt;code>assets/css/main.css&lt;/code>&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="c">/* Chargement des styles de base de Tailwind (CSS reset, etc.) */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">@&lt;/span>&lt;span class="k">tailwind&lt;/span> &lt;span class="nt">base&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">/* Chargement des classes de composants */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">@&lt;/span>&lt;span class="k">tailwind&lt;/span> &lt;span class="nt">components&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">/* Chargement des classes utilitaires */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">@&lt;/span>&lt;span class="k">tailwind&lt;/span> &lt;span class="nt">utilities&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">/* Vous pouvez ajouter votre propre CSS personnalisé ici si nécessaire,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"> mais il est recommandé d&amp;#39;utiliser extend de tailwind.config.js autant que possible */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">@&lt;/span>&lt;span class="k">layer&lt;/span> &lt;span class="nt">components&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nc">btn-primary&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">@apply&lt;/span> &lt;span class="err">bg-blue-500&lt;/span> &lt;span class="n">hover&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="n">bg-blue-700&lt;/span> &lt;span class="kc">text&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="kc">white&lt;/span> &lt;span class="k">font&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="kc">bold&lt;/span> &lt;span class="n">py-2&lt;/span> &lt;span class="n">px-4&lt;/span> &lt;span class="n">rounded&lt;/span> &lt;span class="k">transition&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">colors&lt;/span> &lt;span class="n">duration-300&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="5-2-modification-du-fichier-de-disposition-headhtml">5-2. Modification du fichier de disposition (head.html)
&lt;/h3>&lt;p>Ensuite, nous allons écrire le pipeline pour charger le fichier CSS ci-dessus depuis un modèle Hugo et le traiter avec PostCSS. Généralement, vous modifierez le modèle partiel qui définit la balise &lt;code>&amp;lt;head&amp;gt;&lt;/code> (par exemple, &lt;code>layouts/partials/head.html&lt;/code>).&lt;/p>
&lt;p>&lt;strong>Chemin du fichier : &lt;code>layouts/partials/head.html&lt;/code>&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go-html-template" data-lang="go-html-template">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">meta&lt;/span> &lt;span class="na">charset&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">meta&lt;/span> &lt;span class="na">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;viewport&amp;#34;&lt;/span> &lt;span class="na">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;width=device-width, initial-scale=1&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="na">.Title&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span> | &lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="na">.Site.Title&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- Récupère assets/css/main.css --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$css&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">:=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">resources&lt;/span>&lt;span class="na">.Get&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;css/main.css&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- Définition des options pour PostCSS --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$options&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">:=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">dict&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;inlineImports&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">true&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$css&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$css&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">resources&lt;/span>&lt;span class="na">.PostCSS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$options&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- Pipeline d&amp;#39;optimisation des assets pour l&amp;#39;environnement de production --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">hugo&lt;/span>&lt;span class="na">.IsProduction&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- 1. Minification (compression) --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$css&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$css&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">minify&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- 2. Fingerprint (ajout de hachage pour le contournement du cache) --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$css&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$css&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">fingerprint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;sha512&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- 3. Sortie de la balise avec SRI (Subresource Integrity) --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">link&lt;/span> &lt;span class="na">rel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;stylesheet&amp;#34;&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$css&lt;/span>&lt;span class="na">.RelPermalink&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">&amp;#34;&lt;/span> &lt;span class="na">integrity&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$css&lt;/span>&lt;span class="na">.Data.Integrity&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">&amp;#34;&lt;/span> &lt;span class="na">crossorigin&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;anonymous&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">else&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- Dans l&amp;#39;environnement de développement, sortie sans compression (priorité à la vitesse de build) --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">link&lt;/span> &lt;span class="na">rel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;stylesheet&amp;#34;&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nx">$css&lt;/span>&lt;span class="na">.RelPermalink&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">end&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="explication-du-pipeline-et-diagramme-mermaid">Explication du pipeline et diagramme Mermaid
&lt;/h4>&lt;p>Illustrons la série d&amp;rsquo;étapes de traitement que le code modèle Go ci-dessus effectue sur le fichier CSS.&lt;/p>
&lt;pre class="mermaid">
flowchart TD
A[&amp;#34;assets/css/main.css&amp;#34;] --&amp;gt;|resources.Get| B[&amp;#34;Espace mémoire de Hugo&amp;#34;]
B --&amp;gt;|resources.PostCSS| C[&amp;#34;Processus PostCSS + Tailwind JIT&amp;#34;]
C --&amp;gt; D{&amp;#34;hugo.IsProduction ?&amp;#34;}
D -- Oui (Environnement de production) --&amp;gt; E[&amp;#34;minify (Suppression des espaces et sauts de ligne)&amp;#34;]
E --&amp;gt; F[&amp;#34;fingerprint (Calcul de hachage)&amp;#34;]
F --&amp;gt; G[&amp;#34;public/css/main.[hash].css (Avec SRI)&amp;#34;]
D -- Non (Environnement de développement) --&amp;gt; H[&amp;#34;public/css/main.css&amp;#34;]
&lt;/pre>
&lt;ol>
&lt;li>&lt;strong>&lt;code>resources.Get&lt;/code>&lt;/strong> : Recherche le fichier spécifié dans le répertoire &lt;code>assets&lt;/code> et le charge en tant qu&amp;rsquo;objet ressource en mémoire.&lt;/li>
&lt;li>&lt;strong>&lt;code>resources.PostCSS&lt;/code>&lt;/strong> : Applique le traitement de Tailwind CSS et Autoprefixer au code source CSS en se référant à &lt;code>postcss.config.js&lt;/code> à la racine du projet. En environnement de développement (&lt;code>hugo server&lt;/code>), le mode JIT est activé, générant rapidement uniquement les classes nécessaires lors de la modification des fichiers.&lt;/li>
&lt;li>&lt;strong>&lt;code>minify&lt;/code>&lt;/strong> : Supprime les espaces blancs et les commentaires inutiles pour minimiser la taille du fichier lors de la compilation pour l&amp;rsquo;environnement de production (ex. &lt;code>hugo --environment production&lt;/code>).&lt;/li>
&lt;li>&lt;strong>&lt;code>fingerprint&lt;/code>&lt;/strong> : Calcule un hachage SHA basé sur le contenu du fichier et l&amp;rsquo;ajoute au nom du fichier (ex. &lt;code>main.ab12cd...css&lt;/code>). Cela permet de réaliser un &amp;ldquo;cache busting&amp;rdquo; (contournement du cache), s&amp;rsquo;assurant que le nouveau fichier est chargé lors d&amp;rsquo;une mise à jour du CSS, tout en utilisant le puissant cache du navigateur.&lt;/li>
&lt;li>&lt;strong>&lt;code>integrity&lt;/code>&lt;/strong> : Utilise la valeur de hachage calculée par le Fingerprint pour générer un attribut SRI afin de prévenir toute falsification depuis un CDN ou autre.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="6-analyse-mathématique-des-performances-dans-loptimisation-css">6. Analyse mathématique des performances dans l&amp;rsquo;optimisation CSS
&lt;/h2>&lt;p>L&amp;rsquo;un des plus grands avantages de l&amp;rsquo;adoption de Tailwind CSS est la minimisation extrême de la taille du fichier CSS distribué. Analysons quantitativement l&amp;rsquo;impact de cela sur les performances Web (en particulier le First Contentful Paint : FCP) à l&amp;rsquo;aide de modèles mathématiques.&lt;/p>
&lt;h3 id="modèle-de-réduction-de-la-taille-des-fichiers-css">Modèle de réduction de la taille des fichiers CSS
&lt;/h3>&lt;p>Avec les frameworks CSS traditionnels (comme Bootstrap), tous les styles, même ceux qui ne sont pas utilisés, sont chargés, ce qui tend à augmenter la taille du fichier $S_{original}$ (environ 150 Ko à 200 Ko).
En supposant que $S_{purged}$ soit la taille après application du processus de purge (Purge) des classes inutilisées par le compilateur JIT de Tailwind CSS, et $R_{purge}$ le taux de réduction, on peut l&amp;rsquo;exprimer comme suit :&lt;/p>
$$
S_{purged} = S_{original} \times (1 - R_{purge})
$$&lt;p>Dans un projet typique, $R_{purge}$ atteint près de $0.9$ (réduction de 90%), et $S_{purged}$ se limite à seulement 10 Ko - 20 Ko environ.&lt;/p>
&lt;p>De plus, lors de la distribution, le serveur compresse les fichiers avec Brotli ou Gzip. Si le taux de compression est $R_{compress}$ (généralement autour de 0.7 - 0.8), la taille finale de la charge utile transmise sur le réseau, $S_{final}$, est calculée par la formule suivante :&lt;/p>
$$
S_{final} = S_{purged} \times (1 - R_{compress})
$$&lt;h3 id="chemin-de-rendu-critique-et-latence-du-réseau">Chemin de rendu critique et latence du réseau
&lt;/h3>&lt;p>Le temps nécessaire au navigateur pour afficher le premier contenu à l&amp;rsquo;écran (FCP) peut être approximé par la somme du temps de téléchargement HTML, du temps de téléchargement CSS et du temps de rendu.&lt;/p>
$$
T_{FCP} \approx RTT + \frac{S_{HTML}}{BW} + RTT + \frac{S_{final}}{BW} + T_{render}
$$&lt;p>Ici,&lt;/p>
&lt;ul>
&lt;li>$RTT$ : Round Trip Time (Temps de latence aller-retour avec le serveur)&lt;/li>
&lt;li>$BW$ : Bande passante du réseau (Bandwidth)&lt;/li>
&lt;/ul>
&lt;p>Dans les environnements où $BW$ est faible et $RTT$ est important (latence élevée) comme les réseaux mobiles, l&amp;rsquo;approche de Tailwind CSS qui réduit $S_{final}$ à quelques kilo-octets permet de rapprocher le terme $\frac{S_{final}}{BW}$ au plus près de zéro, ce qui est le moteur des scores spectaculaires obtenus (Google PageSpeed Insights, etc.).&lt;/p>
&lt;hr>
&lt;h2 id="7-démarrage-du-serveur-de-développement-et-vérification-du-hot-reload">7. Démarrage du serveur de développement et vérification du Hot Reload
&lt;/h2>&lt;p>Une fois toute la configuration terminée, démarrez le serveur de développement Hugo pour vérifier que Tailwind CSS fonctionne correctement.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">hugo server -D
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Accédez à &lt;code>http://localhost:1313/&lt;/code> dans votre navigateur et vérifiez que le site s&amp;rsquo;affiche.
Ouvrez un fichier de contenu Markdown ou un modèle Hugo (les fichiers sous &lt;code>layouts/&lt;/code>) et essayez d&amp;rsquo;ajouter une classe.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- Exemple d&amp;#39;application de classes Tailwind pour un test --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;bg-gradient-to-r from-blue-500 to-purple-600 text-white p-8 rounded-xl shadow-2xl text-center transform transition duration-500 hover:scale-105&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-4xl font-extrabold tracking-tight&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Tailwind CSS + Hugo est génial !&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;mt-4 text-lg font-medium&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Vérifiez que le rechargement à chaud (hot reload) s&amp;#39;applique instantanément.&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Au moment où vous sauvegardez le fichier, le puissant observateur de fichiers de Hugo coopère avec le compilateur JIT de Tailwind pour reconstruire le CSS en quelques millisecondes, et vous devriez ressentir le plaisir de voir votre navigateur se recharger automatiquement (hot reload).&lt;/p>
&lt;h3 id="dépannage--si-les-styles-ne-sappliquent-pas">Dépannage : Si les styles ne s&amp;rsquo;appliquent pas
&lt;/h3>&lt;p>Si les modifications ne s&amp;rsquo;appliquent pas, vérifiez les points suivants :&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Paramétrage du chemin &lt;code>content&lt;/code> dans &lt;code>tailwind.config.js&lt;/code>&lt;/strong>
Si le chemin des fichiers à analyser est incorrect, Tailwind ne pourra pas détecter les classes utilisées dans ces fichiers et ne les inclura pas dans le CSS en sortie. Surtout si vous utilisez un thème, assurez-vous de ne pas avoir oublié d&amp;rsquo;inclure le chemin du répertoire du thème.&lt;/li>
&lt;li>&lt;strong>Erreurs PostCSS&lt;/strong>
Si une erreur telle que &lt;code>Error: failed to transform resource: PostCSS not found&lt;/code> s&amp;rsquo;affiche dans les journaux du serveur Hugo du terminal, il est probable que &lt;code>npm install&lt;/code> ne se soit pas exécuté correctement ou que &lt;code>postcss-cli&lt;/code> soit manquant.&lt;/li>
&lt;li>&lt;strong>Effacer le cache Hugo&lt;/strong>
Dans de rares cas, l&amp;rsquo;ancien CSS peut persister à cause du cache de Hugo. Arrêtez le serveur, démarrez-le avec &lt;code>hugo server --ignoreCache&lt;/code>, ou essayez de supprimer le répertoire temporaire du système d&amp;rsquo;exploitation (comme &lt;code>/tmp/hugo_cache/&lt;/code>).&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="8-build-pour-lenvironnement-de-production-et-améliorations-avancées">8. Build pour l&amp;rsquo;environnement de production et améliorations avancées
&lt;/h2>&lt;p>Lors du déploiement de votre site sur un serveur de production (Netlify, Vercel, GitHub Pages, Cloudflare Pages, etc.), vous devez définir les variables d&amp;rsquo;environnement et exécuter le pipeline d&amp;rsquo;optimisation pour la production.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Exemple de commande de build pour la production&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">NODE_ENV&lt;/span>&lt;span class="o">=&lt;/span>production hugo --minify --environment production
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>L&amp;rsquo;ajout de l&amp;rsquo;indicateur &lt;code>--environment production&lt;/code> exécute le bloc &lt;code>{{ if hugo.IsProduction }}&lt;/code> dans &lt;code>head.html&lt;/code>, déclenchant la minification CSS et l&amp;rsquo;ajout du Fingerprint.&lt;/p>
&lt;h3 id="stylisation-du-markdown-avec-le-plugin-typography">Stylisation du Markdown avec le plugin Typography
&lt;/h3>&lt;p>Dans les sites de blogs et de documentation comme ceux créés avec Hugo, vous ne pouvez pas ajouter directement des classes aux éléments HTML purs (tels que &lt;code>&amp;lt;h1&amp;gt;&lt;/code>, &lt;code>&amp;lt;p&amp;gt;&lt;/code>, &lt;code>&amp;lt;ul&amp;gt;&lt;/code>) générés à partir du Markdown. Le &lt;strong>plugin Typography&lt;/strong> officiel de Tailwind est extrêmement utile dans de tels cas.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Installation du plugin&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">npm install -D @tailwindcss/typography
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>
&lt;p>Ajout à &lt;code>tailwind.config.js&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">plugins&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;@tailwindcss/typography&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>
&lt;p>Application dans le modèle
Il suffit d&amp;rsquo;ajouter la classe &lt;code>prose&lt;/code> (et des variantes de couleur et de taille selon vos préférences) à l&amp;rsquo;élément conteneur qui affiche le corps de l&amp;rsquo;article pour appliquer de magnifiques styles par défaut.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go-html-template" data-lang="go-html-template">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">article&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;prose prose-lg prose-blue mx-auto mt-10&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cp">{{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="na">.Content&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="cp">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">article&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;/ol>
&lt;p>Grâce à cela, il n&amp;rsquo;est plus du tout nécessaire d&amp;rsquo;écrire des sélecteurs CSS complexes à la main (&lt;code>.article-content h2 { ... }&lt;/code>), et la modularité des composants est parfaitement préservée.&lt;/p>
&lt;hr>
&lt;h2 id="9-conclusion--la-réalisation-dun-écosystème-front-end-hautement-maintenable">9. Conclusion : La réalisation d&amp;rsquo;un écosystème front-end hautement maintenable
&lt;/h2>&lt;p>Félicitations. Vous disposez maintenant d&amp;rsquo;un pipeline d&amp;rsquo;assets de développement Web parfait, combinant le moteur de génération de site statique ultra-rapide de Hugo, les capacités de stylisation modernes de Tailwind CSS et l&amp;rsquo;extensibilité de PostCSS.&lt;/p>
&lt;p>Le point fort de cette architecture est que &lt;strong>&amp;ldquo;la configuration ne se fait qu&amp;rsquo;une seule fois&amp;rdquo;&lt;/strong>. Une fois le pipeline construit, les développeurs peuvent créer des interfaces utilisateur complexes à une vitesse fulgurante en ajoutant simplement des classes utilitaires intuitives aux modèles HTML ou Markdown, sans avoir à ouvrir le moindre fichier CSS.&lt;/p>
&lt;p>De plus, étant donné que la taille du CSS généré est toujours minimisée, cela contribue directement à l&amp;rsquo;amélioration des scores Core Web Vitals, ce qui est extrêmement avantageux du point de vue du SEO.&lt;/p>
&lt;p>La combinaison de Hugo et Tailwind CSS restera sans doute l&amp;rsquo;une des &amp;ldquo;meilleures options&amp;rdquo; pour tout projet, des blogs techniques personnels aux sites d&amp;rsquo;entreprise à grande échelle. N&amp;rsquo;hésitez pas à tirer parti de cette chaîne d&amp;rsquo;outils puissante pour profiter d&amp;rsquo;une expérience de développement Web confortable !&lt;/p></description></item><item><title>Bases de l'architecture logicielle : Qu'est-ce que la Clean Architecture ?</title><link>http://kenji.blog/fr/p/clean-architecture-basics-guide/</link><pubDate>Sat, 12 Sep 2026 12:00:00 +0900</pubDate><guid>http://kenji.blog/fr/p/clean-architecture-basics-guide/</guid><description>&lt;img src="http://kenji.blog/p/clean-architecture-basics-guide/img/eyecatch.jpg" alt="Featured image of post Bases de l'architecture logicielle : Qu'est-ce que la Clean Architecture ?" />&lt;p>Dans le développement logiciel moderne, la construction d&amp;rsquo;un « système résilient aux changements » est un défi éternel. Changements des exigences métier, émergence de nouveaux frameworks, refonte de l&amp;rsquo;interface utilisateur (UI), migration de base de données. Face à tous ces changements, on recherche une architecture capable de s&amp;rsquo;adapter avec souplesse sans avoir à reconstruire le système entier. L&amp;rsquo;une des réponses à ce besoin est la &lt;strong>Clean Architecture&lt;/strong>, proposée par Robert C. Martin (surnommé Oncle Bob, Uncle Bob).&lt;/p>
&lt;p>Dans cet article, nous explorerons l&amp;rsquo;essence de la Clean Architecture à travers son histoire, son objectif, les détails de ses 4 couches, la règle de dépendance, ainsi que des exemples concrets d&amp;rsquo;implémentation. Nous fournirons une explication technique très approfondie et détaillée.&lt;/p>
&lt;h2 id="1-les-problèmes-des-architectures-traditionnelles-et-lhistoire-de-la-clean-architecture">1. Les problèmes des architectures traditionnelles et l&amp;rsquo;histoire de la Clean Architecture
&lt;/h2>&lt;p>Historiquement, l&amp;rsquo;architecture logicielle a connu divers changements de paradigme. Dans les premiers systèmes, le code de la logique métier, de l&amp;rsquo;UI et de l&amp;rsquo;accès aux données était fortement couplé (ce qu&amp;rsquo;on appelle du code spaghetti). Par la suite, dans le but d&amp;rsquo;appliquer la séparation des préoccupations (Separation of Concerns), l&amp;rsquo;architecture en 3 tiers (couche de présentation, couche de logique métier, couche d&amp;rsquo;accès aux données) s&amp;rsquo;est popularisée.&lt;/p>
&lt;p>Cependant, l&amp;rsquo;architecture en 3 tiers traditionnelle présentait un problème majeur : « &lt;strong>le domaine (la logique métier) finit par dépendre de la base de données et des frameworks&lt;/strong> ».&lt;/p>
&lt;p>Par exemple, si la couche de logique métier appelle directement la couche d&amp;rsquo;accès aux données (comme un ORM), un changement de schéma de base de données ou d&amp;rsquo;ORM se répercute sur la logique métier. En d&amp;rsquo;autres termes, il y avait une contradiction où les « règles métier », qui sont les plus importantes et ne devraient pas changer, dépendaient de « l&amp;rsquo;infrastructure », qui est la plus susceptible de subir des modifications techniques.&lt;/p>
&lt;p>Pour résoudre ce problème, les architectures suivantes ont été conçues :&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Architecture Hexagonale (Ports and Adapters)&lt;/strong> - Alistair Cockburn&lt;/li>
&lt;li>&lt;strong>Architecture Oignon (Onion Architecture)&lt;/strong> - Jeffrey Palermo&lt;/li>
&lt;li>&lt;strong>DCI (Data, Context and Interaction)&lt;/strong> - James Coplien, Trygve Reenskaug&lt;/li>
&lt;li>&lt;strong>BCE (Boundary-Control-Entity)&lt;/strong> - Ivar Jacobson&lt;/li>
&lt;/ul>
&lt;p>Toutes ces architectures ont le même objectif : la « &lt;strong>séparation des préoccupations&lt;/strong> ». Il s&amp;rsquo;agit de diviser le logiciel en couches, où chacune peut être testée indépendamment et reste indépendante des agents externes (UI, base de données, frameworks).&lt;/p>
&lt;p>Robert C. Martin a intégré les concepts de ces excellentes architectures et les a regroupés sous une seule règle pratique qu&amp;rsquo;il a nommée la « &lt;strong>Clean Architecture&lt;/strong> ».&lt;/p>
&lt;h2 id="2-objectifs-et-caractéristiques-de-la-clean-architecture">2. Objectifs et caractéristiques de la Clean Architecture
&lt;/h2>&lt;p>Un système qui adopte la Clean Architecture possède les caractéristiques suivantes :&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Indépendant des frameworks (Independent of Frameworks)&lt;/strong> : L&amp;rsquo;architecture ne dépend pas de l&amp;rsquo;existence de bibliothèques logicielles riches en fonctionnalités. Cela permet d&amp;rsquo;utiliser les frameworks comme des « outils » et vous évite d&amp;rsquo;avoir à contraindre le système aux limites de ces frameworks.&lt;/li>
&lt;li>&lt;strong>Testable&lt;/strong> : Les règles métier peuvent être testées sans l&amp;rsquo;interface utilisateur (UI), la base de données, le serveur web ou tout autre élément externe.&lt;/li>
&lt;li>&lt;strong>Indépendant de l&amp;rsquo;interface utilisateur (Independent of UI)&lt;/strong> : L&amp;rsquo;UI peut changer facilement sans modifier le reste du système. Par exemple, une UI Web pourrait être remplacée par une UI console sans changer les règles métier.&lt;/li>
&lt;li>&lt;strong>Indépendant de la base de données (Independent of Database)&lt;/strong> : Vous pouvez remplacer Oracle ou SQL Server par Mongo, BigTable, CouchDB ou autre. Vos règles métier ne sont pas liées à la base de données.&lt;/li>
&lt;li>&lt;strong>Indépendant de tout agent externe (Independent of any external agency)&lt;/strong> : En fait, vos règles métier ne connaissent absolument rien du monde extérieur.&lt;/li>
&lt;/ol>
&lt;h2 id="3-les-4-couches-layers-de-la-clean-architecture">3. Les 4 couches (Layers) de la Clean Architecture
&lt;/h2>&lt;p>La Clean Architecture est généralement représentée par des cercles concentriques. Plus on se rapproche du centre, plus le logiciel contient des politiques de haut niveau (règles métier abstraites). Plus on s&amp;rsquo;éloigne vers l&amp;rsquo;extérieur, plus on trouve les mécanismes (détails concrets).&lt;/p>
&lt;pre class="mermaid">
graph TD
subgraph &amp;#34;External Interfaces (Frameworks &amp;amp; Drivers)&amp;#34;
A[&amp;#34;Web&amp;#34;]
B[&amp;#34;UI&amp;#34;]
C[&amp;#34;Base de données&amp;#34;]
D[&amp;#34;APIs externes&amp;#34;]
end
subgraph &amp;#34;Interface Adapters&amp;#34;
E[&amp;#34;Contrôleurs&amp;#34;]
F[&amp;#34;Passerelles&amp;#34;]
G[&amp;#34;Présentateurs&amp;#34;]
end
subgraph &amp;#34;Application Business Rules (Use Cases)&amp;#34;
H[&amp;#34;Interacteurs de Cas d&amp;#39;Utilisation&amp;#34;]
end
subgraph &amp;#34;Enterprise Business Rules (Entities)&amp;#34;
I[&amp;#34;Entités&amp;#34;]
end
A --&amp;gt; E
B --&amp;gt; E
C --&amp;gt; F
D --&amp;gt; F
E --&amp;gt; H
F --&amp;gt; H
G --&amp;gt; H
H --&amp;gt; I
&lt;/pre>
&lt;h3 id="31-entités-entities">3.1. Entités (Entities)
&lt;/h3>&lt;p>Les entités encapsulent les règles métier de l&amp;rsquo;entreprise à grande échelle (Enterprise Business Rules). Une entité peut être un objet avec des méthodes, ou un ensemble de structures de données et de fonctions. Ce sont les règles les plus générales et de plus haut niveau, réutilisables dans plusieurs applications différentes au sein de l&amp;rsquo;entreprise.
Même si vous ne créez qu&amp;rsquo;une seule application, ces entités constituent les objets métier de l&amp;rsquo;application. Elles ne sont en aucun cas affectées par un changement externe (comme un changement de navigation dans les pages ou un changement de sécurité).&lt;/p>
&lt;h3 id="32-cas-dutilisation-use-cases">3.2. Cas d&amp;rsquo;utilisation (Use Cases)
&lt;/h3>&lt;p>La couche des cas d&amp;rsquo;utilisation contient les règles métier spécifiques à l&amp;rsquo;application (Application Business Rules). C&amp;rsquo;est ici que sont encapsulés et mis en œuvre tous les cas d&amp;rsquo;utilisation du système. Les cas d&amp;rsquo;utilisation orchestrent le flux de données vers et depuis les entités, et dirigent ces entités pour utiliser leurs règles métier globales afin d&amp;rsquo;atteindre les objectifs du système.
Les modifications apportées à cette couche ne doivent pas affecter les entités. De plus, cette couche ne doit pas être affectée par des changements externes tels que la base de données, l&amp;rsquo;UI ou les frameworks. Les cas d&amp;rsquo;utilisation sont totalement isolés de ces préoccupations.&lt;/p>
&lt;h3 id="33-adaptateurs-dinterface-interface-adapters">3.3. Adaptateurs d&amp;rsquo;interface (Interface Adapters)
&lt;/h3>&lt;p>La couche des adaptateurs d&amp;rsquo;interface est un ensemble d&amp;rsquo;adaptateurs qui convertissent les données du format le plus pratique pour les cas d&amp;rsquo;utilisation et les entités vers le format le plus pratique pour les agents externes tels que la base de données ou le Web.
Par exemple, dans le monde du Web, c&amp;rsquo;est ici que se trouvent les éléments de l&amp;rsquo;architecture MVC (Model-View-Controller) d&amp;rsquo;une GUI. Les contrôleurs prennent les entrées de l&amp;rsquo;utilisateur et les transmettent aux cas d&amp;rsquo;utilisation, et les présentateurs reçoivent la sortie des cas d&amp;rsquo;utilisation et la formatent pour la vue (UI).
C&amp;rsquo;est également le rôle de cette couche de convertir les données dans un format compréhensible par la base de données (comme le SQL). Aucun code à l&amp;rsquo;intérieur de cette couche ne devrait rien savoir de la base de données.&lt;/p>
&lt;h3 id="34-frameworks-et-pilotes-frameworks--drivers">3.4. Frameworks et Pilotes (Frameworks &amp;amp; Drivers)
&lt;/h3>&lt;p>La couche la plus externe est composée d&amp;rsquo;outils tels que la base de données, le framework Web, etc. En général, on n&amp;rsquo;écrit pas beaucoup de code ici, si ce n&amp;rsquo;est du code de liaison (« glue code ») pour communiquer avec les cercles intérieurs.
C&amp;rsquo;est dans cette couche que tous les détails sont conservés. Le Web est un détail. La base de données est un détail. Nous gardons ces éléments à l&amp;rsquo;extérieur pour qu&amp;rsquo;ils fassent le moins de dégâts possible.&lt;/p>
&lt;h2 id="4-la-règle-de-dépendance-the-dependency-rule">4. La Règle de Dépendance (The Dependency Rule)
&lt;/h2>&lt;p>Il existe une règle fondamentale et absolue pour que la Clean Architecture fonctionne. C&amp;rsquo;est la « &lt;strong>Règle de Dépendance (The Dependency Rule)&lt;/strong> ».&lt;/p>
&lt;blockquote>
&lt;p>Les dépendances du code source ne doivent pointer que vers l&amp;rsquo;intérieur (vers les politiques de plus haut niveau).&lt;/p>
&lt;/blockquote>
&lt;p>Rien dans un cercle intérieur ne doit connaître quoi que ce soit d&amp;rsquo;un cercle extérieur. En particulier, le nom d&amp;rsquo;un élément (fonction, classe, variable, etc.) déclaré dans un cercle extérieur ne doit pas être mentionné par le code dans un cercle intérieur.
De même, les formats de données utilisés dans un cercle extérieur ne doivent pas être utilisés par un cercle intérieur, en particulier si ces formats sont générés par un framework d&amp;rsquo;un cercle extérieur.&lt;/p>
&lt;pre class="mermaid">
graph LR
A[&amp;#34;Frameworks et Pilotes&amp;#34;] --&amp;gt;| Dépend de | B[&amp;#34;Adaptateurs d&amp;#39;interface&amp;#34;]
B --&amp;gt;| Dépend de | C[&amp;#34;Cas d&amp;#39;utilisation&amp;#34;]
C --&amp;gt;| Dépend de | D[&amp;#34;Entités&amp;#34;]
style A fill:#f9f9f9,stroke:#333,stroke-width:2px
style B fill:#e6f7ff,stroke:#333,stroke-width:2px
style C fill:#fff0f6,stroke:#333,stroke-width:2px
style D fill:#f6ffed,stroke:#333,stroke-width:2px
&lt;/pre>
&lt;p>Exprimé mathématiquement, si nous définissons l&amp;rsquo;indice de la couche comme $L_i$, où $i=0$ est l&amp;rsquo;entité (la couche la plus interne) et $i=3$ le framework (la couche la plus externe), si une dépendance existe d&amp;rsquo;une couche $L_m$ à $L_n$, l&amp;rsquo;inégalité suivante doit toujours être vérifiée :&lt;/p>
$$ m > n $$&lt;p>En d&amp;rsquo;autres termes, le vecteur de dépendance $\vec{D}$ pointe toujours vers le centre.&lt;/p>
&lt;h2 id="5-franchir-les-frontières--le-principe-dinversion-des-dépendances-dip">5. Franchir les frontières : Le Principe d&amp;rsquo;Inversion des Dépendances (DIP)
&lt;/h2>&lt;p>En essayant de respecter la règle de dépendance, on se heurte rapidement à un problème majeur : « &lt;strong>Que faire si un cas d&amp;rsquo;utilisation a besoin de récupérer des données depuis la base de données ?&lt;/strong> »&lt;/p>
&lt;p>Si la couche des cas d&amp;rsquo;utilisation (à l&amp;rsquo;intérieur) appelle directement la couche des adaptateurs d&amp;rsquo;interface (l&amp;rsquo;implémentation du Repository à l&amp;rsquo;extérieur), la dépendance pointerait vers l&amp;rsquo;extérieur, ce qui violerait la règle de dépendance.&lt;/p>
&lt;p>La solution à ce problème réside dans le « D » des principes SOLID (Dependency Inversion Principle : le Principe d&amp;rsquo;Inversion des Dépendances).&lt;/p>
&lt;h3 id="définition-du-principe-dinversion-des-dépendances-dip">Définition du Principe d&amp;rsquo;Inversion des Dépendances (DIP)
&lt;/h3>&lt;ol>
&lt;li>Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d&amp;rsquo;abstractions.&lt;/li>
&lt;li>Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions.&lt;/li>
&lt;/ol>
&lt;p>Pour y parvenir, on définit une &lt;strong>interface (abstraction)&lt;/strong> dans la couche des cas d&amp;rsquo;utilisation, et on l&amp;rsquo;&lt;strong>implémente&lt;/strong> dans la couche externe (les adaptateurs d&amp;rsquo;interface). La couche des cas d&amp;rsquo;utilisation ne dépend que de l&amp;rsquo;interface qu&amp;rsquo;elle a elle-même définie, et non de l&amp;rsquo;implémentation concrète externe.&lt;/p>
&lt;pre class="mermaid">
classDiagram
class UseCaseInteractor {
- UserRepository repository
+ execute()
}
class UserRepository {
&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt;
+ findById(id)
+ save(user)
}
class UserRepositoryImpl {
+ findById(id)
+ save(user)
}
UseCaseInteractor --&amp;gt; UserRepository : &amp;#34;Dépend de&amp;#34;
UserRepositoryImpl ..|&amp;gt; UserRepository : &amp;#34;Implémente&amp;#34;
note for UseCaseInteractor &amp;#34;Couche interne (Cas d&amp;#39;utilisation)&amp;#34;
note for UserRepository &amp;#34;Couche interne (Cas d&amp;#39;utilisation)&amp;#34;
note for UserRepositoryImpl &amp;#34;Couche externe (Adaptateurs d&amp;#39;interface)&amp;#34;
&lt;/pre>
&lt;p>Dans le diagramme ci-dessus, le flux de contrôle (Control Flow) à l&amp;rsquo;exécution est &lt;code>UseCaseInteractor&lt;/code> $\rightarrow$ &lt;code>UserRepositoryImpl&lt;/code>. Cependant, la dépendance du code source (Source Code Dependency) est &lt;code>UserRepositoryImpl&lt;/code> $\rightarrow$ &lt;code>UserRepository&lt;/code> (vers l&amp;rsquo;intérieur). En utilisant le polymorphisme, nous avons pu diriger la dépendance du code source dans la direction opposée au flux de contrôle. C&amp;rsquo;est pour cela qu&amp;rsquo;on l&amp;rsquo;appelle l&amp;rsquo;&lt;strong>inversion&lt;/strong> des dépendances.&lt;/p>
&lt;h2 id="6-exemple-dimplémentation-concrète-en-typescript">6. Exemple d&amp;rsquo;implémentation concrète en TypeScript
&lt;/h2>&lt;p>Voici un exemple simple d&amp;rsquo;implémentation de la Clean Architecture en TypeScript (une fonctionnalité d&amp;rsquo;inscription d&amp;rsquo;utilisateur).&lt;/p>
&lt;h3 id="61-entités-entities">6.1. Entités (Entities)
&lt;/h3>&lt;p>Il s&amp;rsquo;agit des règles métier les plus centrales.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/domain/entities/User.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">export&lt;/span> &lt;span class="kr">class&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">constructor&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">public&lt;/span> &lt;span class="kr">readonly&lt;/span> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">public&lt;/span> &lt;span class="kr">readonly&lt;/span> &lt;span class="nx">name&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">public&lt;/span> &lt;span class="kr">readonly&lt;/span> &lt;span class="nx">email&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">public&lt;/span> &lt;span class="kr">readonly&lt;/span> &lt;span class="nx">createdAt&lt;/span>: &lt;span class="kt">Date&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Règles métier spécifiques à l&amp;#39;entité (ex: vérification de la longueur du nom, etc.)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">public&lt;/span> &lt;span class="nx">isValid&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kr">boolean&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">email&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">includes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;@&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="62-cas-dutilisation-use-cases">6.2. Cas d&amp;rsquo;utilisation (Use Cases)
&lt;/h3>&lt;p>Dans la couche des cas d&amp;rsquo;utilisation, nous définissons les structures de données d&amp;rsquo;entrée et de sortie (DTO) ainsi que l&amp;rsquo;interface du Repository pour inverser les dépendances.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/application/repositories/UserRepository.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;../../domain/entities/User&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Interface définie par la couche des cas d&amp;#39;utilisation
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">export&lt;/span> &lt;span class="kr">interface&lt;/span> &lt;span class="nx">UserRepository&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">findByEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">email&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">User&lt;/span> &lt;span class="err">|&lt;/span> &lt;span class="na">null&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">user&lt;/span>: &lt;span class="kt">User&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">void&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/application/usecases/RegisterUser/RegisterUserDTO.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">export&lt;/span> &lt;span class="kr">interface&lt;/span> &lt;span class="nx">RegisterUserInputDTO&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">email&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kr">interface&lt;/span> &lt;span class="nx">RegisterUserOutputDTO&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">email&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">createdAt&lt;/span>: &lt;span class="kt">Date&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/application/usecases/RegisterUser/RegisterUserUseCase.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;../../domain/entities/User&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">UserRepository&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;../../repositories/UserRepository&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">RegisterUserInputDTO&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">RegisterUserOutputDTO&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;./RegisterUserDTO&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kr">class&lt;/span> &lt;span class="nx">RegisterUserUseCase&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Dépend d&amp;#39;une abstraction (interface). Ne dépend pas d&amp;#39;une implémentation concrète.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">constructor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">private&lt;/span> &lt;span class="kr">readonly&lt;/span> &lt;span class="nx">userRepository&lt;/span>: &lt;span class="kt">UserRepository&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">public&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="nx">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span>: &lt;span class="kt">RegisterUserInputDTO&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">RegisterUserOutputDTO&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">existingUser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">userRepository&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">findByEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">email&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">existingUser&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;User already exists&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">newUser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">User&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">crypto&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">randomUUID&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">input&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">input&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">email&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">newUser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">isValid&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Invalid user data&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Appelle le processus de sauvegarde en base de données à l&amp;#39;extérieur,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// mais la dépendance pointe vers l&amp;#39;intérieur (vers l&amp;#39;interface)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">userRepository&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">newUser&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">newUser.id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>: &lt;span class="kt">newUser.name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">email&lt;/span>: &lt;span class="kt">newUser.email&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">createdAt&lt;/span>: &lt;span class="kt">newUser.createdAt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="63-adaptateurs-dinterface-interface-adapters">6.3. Adaptateurs d&amp;rsquo;interface (Interface Adapters)
&lt;/h3>&lt;p>Nous créons ici le processus d&amp;rsquo;accès concret à la base de données (l&amp;rsquo;implémentation du Repository) et le Controller qui gère les requêtes HTTP.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/adapters/repositories/PostgresUserRepository.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">UserRepository&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;../../application/repositories/UserRepository&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;../../domain/entities/User&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// On suppose que c&amp;#39;est un client de base de données de la couche externe (Driver)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">DatabaseClient&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;../../infrastructure/database/DatabaseClient&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kr">class&lt;/span> &lt;span class="nx">PostgresUserRepository&lt;/span> &lt;span class="kr">implements&lt;/span> &lt;span class="nx">UserRepository&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">constructor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">private&lt;/span> &lt;span class="kr">readonly&lt;/span> &lt;span class="nx">dbClient&lt;/span>: &lt;span class="kt">DatabaseClient&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">public&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="nx">findByEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">email&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">User&lt;/span> &lt;span class="err">|&lt;/span> &lt;span class="na">null&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">record&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dbClient&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;SELECT * FROM users WHERE email = $1&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">email&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">record&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">record&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">record&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">record&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">email&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">record&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">created_at&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">public&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="nx">save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">user&lt;/span>: &lt;span class="kt">User&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">void&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dbClient&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">query&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;INSERT INTO users (id, name, email, created_at) VALUES ($1, $2, $3, $4)&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">user&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">user&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">email&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">user&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createdAt&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/adapters/controllers/UserController.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">RegisterUserUseCase&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;../../application/usecases/RegisterUser/RegisterUserUseCase&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kr">class&lt;/span> &lt;span class="nx">UserController&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">constructor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">private&lt;/span> &lt;span class="kr">readonly&lt;/span> &lt;span class="nx">registerUserUseCase&lt;/span>: &lt;span class="kt">RegisterUserUseCase&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">public&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="nx">register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>: &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>: &lt;span class="kt">any&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">void&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">input&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>: &lt;span class="kt">req.body.name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">email&lt;/span>: &lt;span class="kt">req.body.email&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">registerUserUseCase&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">201&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">output&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>: &lt;span class="kt">any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">400&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">message&lt;/span>: &lt;span class="kt">error.message&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="64-composant-principal-injection-de-dépendances--di">6.4. Composant principal (Injection de dépendances : DI)
&lt;/h3>&lt;p>Au démarrage de l&amp;rsquo;application, nous construisons toutes les dépendances (câblage). C&amp;rsquo;est ce qu&amp;rsquo;on appelle la Racine de Composition (Composition Root).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/infrastructure/web/server.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="nx">express&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;express&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">DatabaseClient&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;../database/DatabaseClient&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">PostgresUserRepository&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;../../adapters/repositories/PostgresUserRepository&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">RegisterUserUseCase&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;../../application/usecases/RegisterUser/RegisterUserUseCase&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">UserController&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;../../adapters/controllers/UserController&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">express&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">use&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">express&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 1. Initialisation du Driver
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">dbClient&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">DatabaseClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="cm">/* Informations de connexion */&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 2. Initialisation de l&amp;#39;Adaptateur (Instanciation de la classe concrète)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">userRepository&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">PostgresUserRepository&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">dbClient&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 3. Initialisation du Cas d&amp;#39;Utilisation (Injection de la classe concrète dans l&amp;#39;interface = DI)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">registerUserUseCase&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">RegisterUserUseCase&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">userRepository&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 4. Initialisation du Contrôleur
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">userController&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">UserController&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">registerUserUseCase&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Routage
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/users&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">userController&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">listen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Server is running on port 3000&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>De cette manière, le « script de démarrage » situé dans la couche la plus externe prend en charge les détails désagréables (l&amp;rsquo;instanciation des classes concrètes) et ne transmet que des interfaces propres aux couches internes, isolant ainsi complètement la logique métier du monde extérieur.&lt;/p>
&lt;h2 id="7-considérations-mathématiques-sur-le-couplage-et-la-cohésion">7. Considérations mathématiques sur le couplage et la cohésion
&lt;/h2>&lt;p>En génie logiciel, le &lt;strong>couplage (Coupling)&lt;/strong> et la &lt;strong>cohésion (Cohesion)&lt;/strong> sont des métriques utilisées pour évaluer la qualité d&amp;rsquo;une architecture.&lt;/p>
&lt;p>Le couplage $C$ représente la force de dépendance entre les modules. Si le module $A$ dépend du module $B$, en posant le nombre total de dépendances du système à $N_{dep}$ et le nombre de modules à $N_{mod}$, l&amp;rsquo;un des indicateurs de la complexité peut s&amp;rsquo;exprimer ainsi :&lt;/p>
$$ Complexity \propto \frac{N_{dep}}{N_{mod}} $$&lt;p>Dans la Clean Architecture, l&amp;rsquo;application du principe DIP permet d&amp;rsquo;orienter les flèches de dépendance physique vers des abstractions. L&amp;rsquo;instabilité (Instability : $I$), c&amp;rsquo;est-à-dire la fréquence de modification d&amp;rsquo;une abstraction (interface), est conçue pour être très faible.&lt;/p>
&lt;p>L&amp;rsquo;instabilité $I$ est calculée par la formule suivante (définie par Robert C. Martin) :&lt;/p>
&lt;ul>
&lt;li>$C_e$ (Efferent Coupling) : Couplage sortant (le nombre d&amp;rsquo;éléments dont dépend le composant)&lt;/li>
&lt;li>$C_a$ (Afferent Coupling) : Couplage entrant (le nombre d&amp;rsquo;éléments dépendant du composant)&lt;/li>
&lt;/ul>
$$ I = \frac{C_e}{C_e + C_a} $$&lt;ul>
&lt;li>Si $I = 0$, le composant est totalement stable (il ne dépend de rien, mais d&amp;rsquo;autres dépendent de lui).&lt;/li>
&lt;li>Si $I = 1$, le composant est totalement instable (personne ne dépend de lui, et il dépend d&amp;rsquo;autres composants).&lt;/li>
&lt;/ul>
&lt;p>La « couche des entités » de la Clean Architecture a un $C_e = 0$ (ne dépend pas de l&amp;rsquo;extérieur), donc $I = 0$. C&amp;rsquo;est la couche la plus stable.
À l&amp;rsquo;inverse, la « couche UI » ou la « couche DB » ont un $C_a \approx 0$ et un $C_e > 0$, donc $I \approx 1$. Ce sont des couches facilement modifiables (couches instables).&lt;/p>
&lt;p>Le SDP (Stable Dependencies Principle : Principe de Dépendances Stables), qui est un principe fondamental de l&amp;rsquo;architecture, stipule que « &lt;strong>les dépendances doivent pointer vers des composants plus stables (ceux dont le $I$ est plus petit)&lt;/strong> ». Les cercles concentriques de la Clean Architecture sont justement la visualisation de ce SDP, où l&amp;rsquo;architecture est conçue pour que les dépendances aillent de l&amp;rsquo;extérieur ($I=1$) vers l&amp;rsquo;intérieur ($I=0$).&lt;/p>
&lt;h2 id="8-stratégie-de-test-et-clean-architecture">8. Stratégie de test et Clean Architecture
&lt;/h2>&lt;p>L&amp;rsquo;un des plus grands avantages de la Clean Architecture est la &lt;strong>facilité de test&lt;/strong>. Les couches étant séparées, il est possible d&amp;rsquo;écrire des tests adaptés à chaque couche de manière indépendante.&lt;/p>
&lt;h3 id="81-test-des-entités-test-unitaire">8.1. Test des entités (Test Unitaire)
&lt;/h3>&lt;p>Puisqu&amp;rsquo;il s&amp;rsquo;agit d&amp;rsquo;une logique pure sans aucune dépendance externe, ni base de données ni mock ne sont nécessaires. C&amp;rsquo;est le test le plus fiable et le plus rapide à exécuter.&lt;/p>
&lt;h3 id="82-test-des-cas-dutilisation-test-unitaire-avec-mocks">8.2. Test des cas d&amp;rsquo;utilisation (Test Unitaire avec Mocks)
&lt;/h3>&lt;p>Comme toutes les dépendances externes telles que les repositories sont définies sous forme d&amp;rsquo;interfaces, il suffit d&amp;rsquo;injecter (DI) des &lt;strong>mocks pour les tests ou des implémentations en mémoire (Fake)&lt;/strong> lors des tests. Il n&amp;rsquo;est pas nécessaire de lancer une vraie base de données. Cela permet de tester rapidement les branchements complexes et la gestion des exceptions de la logique métier.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Exemple de test de cas d&amp;#39;utilisation (en supposant l&amp;#39;utilisation de Jest)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;L\&amp;#39;inscription avec une adresse e-mail existante doit générer une erreur&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Création d&amp;#39;un Fake Repository
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">mockRepo&lt;/span>: &lt;span class="kt">UserRepository&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">findByEmail&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">email&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;1&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Test&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">email&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">()),&lt;/span> &lt;span class="c1">// Retourne un utilisateur existant
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">save&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">useCase&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">RegisterUserUseCase&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mockRepo&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Exécution du cas d&amp;#39;utilisation et assertion de l&amp;#39;erreur
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">useCase&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">execute&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;Bob&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">email&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;test@example.com&amp;#39;&lt;/span> &lt;span class="p">}))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">rejects&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">toThrow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;User already exists&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="83-test-des-adaptateurs-test-dintégration">8.3. Test des adaptateurs (Test d&amp;rsquo;Intégration)
&lt;/h3>&lt;p>La classe d&amp;rsquo;implémentation du Repository est testée en se connectant réellement à la base de données pour vérifier que le SQL est correct. Le test du Controller vérifie la partie qui reçoit la requête HTTP et renvoie le JSON. Ici, on ne procède pas à une validation détaillée de la logique métier, mais on vérifie seulement si la « conversion » et la « communication » sont correctes.&lt;/p>
&lt;h2 id="9-les-inconvénients-de-la-clean-architecture-et-quand-ladopter">9. Les inconvénients de la Clean Architecture et quand l&amp;rsquo;adopter
&lt;/h2>&lt;p>Bien qu&amp;rsquo;elle semble être une solution universelle, la Clean Architecture n&amp;rsquo;est pas une balle d&amp;rsquo;argent. Elle présente des inconvénients (compromis) tels que :&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Augmentation des coûts d&amp;rsquo;apprentissage initiaux et des coûts de développement&lt;/strong> : Le nombre de fichiers et d&amp;rsquo;interfaces (abstractions) augmente considérablement. Il y aura beaucoup de « code de la plaque de base (boilerplate) » tel que le reconditionnement des DTO.&lt;/li>
&lt;li>&lt;strong>Surdimensionnée pour les petits projets&lt;/strong> : Adopter cette architecture pour un prototype construit en quelques jours ou pour un outil à usage unique soumis à peu de changements représente souvent un coût inutile. Elle est également inadaptée pour une simple API ne gérant que des opérations CRUD.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Quand l&amp;rsquo;adopter&lt;/strong> :&lt;/p>
&lt;ul>
&lt;li>Les produits dont la maintenance et l&amp;rsquo;exploitation sont prévues sur le long terme (plusieurs années).&lt;/li>
&lt;li>Les systèmes où les règles métier sont complexes et font l&amp;rsquo;objet de changements de spécifications fréquents.&lt;/li>
&lt;li>Lorsque vous souhaitez répartir le travail au sein d&amp;rsquo;une grande équipe de développement (front-end, back-end, infrastructure, etc.).&lt;/li>
&lt;li>Lorsque vous souhaitez la combiner avec le Domain-Driven Design (DDD) pour modéliser des domaines métier complexes.&lt;/li>
&lt;/ul>
&lt;h2 id="10-résumé">10. Résumé
&lt;/h2>&lt;p>La Clean Architecture est une philosophie de conception visant à protéger le cœur du système, c&amp;rsquo;est-à-dire les « règles métier », contre les « détails » tels que l&amp;rsquo;UI, la base de données ou les frameworks.&lt;/p>
&lt;p>En son cœur se trouvent la &lt;strong>règle de dépendance&lt;/strong> et le &lt;strong>Principe d&amp;rsquo;Inversion des Dépendances (DIP)&lt;/strong>. En les appliquant correctement, les logiciels deviennent flexibles face aux changements, plus faciles à tester, et peuvent maintenir leur valeur sur le long terme.&lt;/p>
&lt;p>L&amp;rsquo;important n&amp;rsquo;est pas de reproduire aveuglément la structure de répertoires de la Clean Architecture, mais de comprendre l&amp;rsquo;essence de « &lt;strong>pourquoi diviser de cette façon&lt;/strong> » et « &lt;strong>vers où pointent les flèches de dépendance&lt;/strong> », afin de l&amp;rsquo;appliquer correctement en fonction de la taille et de la complexité de votre propre projet.&lt;/p>
&lt;hr>
&lt;p>&lt;em>Reference: &amp;ldquo;Clean Architecture: A Craftsman&amp;rsquo;s Guide to Software Structure and Design&amp;rdquo; by Robert C. Martin&lt;/em>&lt;/p></description></item><item><title>Points clés de la conception CSS pour la prise en charge du mode sombre sur les blogs</title><link>http://kenji.blog/fr/p/dark-mode-css-design-patterns/</link><pubDate>Sat, 12 Sep 2026 12:00:00 +0900</pubDate><guid>http://kenji.blog/fr/p/dark-mode-css-design-patterns/</guid><description>&lt;img src="http://kenji.blog/p/dark-mode-css-design-patterns/img/eyecatch.jpg" alt="Featured image of post Points clés de la conception CSS pour la prise en charge du mode sombre sur les blogs" />&lt;p>Dans le développement web moderne, la prise en charge du mode sombre (Dark Mode) n&amp;rsquo;est plus une simple « fonctionnalité intéressante à avoir » (Nice to have), mais est devenue une « exigence essentielle » (Must have) pour améliorer l&amp;rsquo;expérience utilisateur (UX). Surtout pour les médias tels que les blogs et les sites de documentation, qui impliquent de longues périodes de lecture de texte, la prise en charge du mode sombre est extrêmement importante car elle réduit la fatigue oculaire des utilisateurs et diminue la consommation de batterie des appareils.&lt;/p>
&lt;p>Dans cet article, nous explorerons en profondeur, du point de vue d&amp;rsquo;un ingénieur front-end, les défis techniques inévitables liés à l&amp;rsquo;intégration du mode sombre sur un blog, ainsi que les points clés d&amp;rsquo;une conception CSS hautement maintenable. Nous couvrirons tout ce qu&amp;rsquo;il faut savoir sur l&amp;rsquo;implémentation du mode sombre : l&amp;rsquo;utilisation des CSS Custom Properties (variables CSS), les contrôles JavaScript avancés et la collaboration SSR pour prévenir le FOUC (Flash of Unstyled Content), la conception des couleurs (RGB, HSL, et le plus récent OKLCH) pour assurer l&amp;rsquo;accessibilité (WCAG 2.1 AAA), et même des exemples de code pratiques utilisant Tailwind CSS.&lt;/p>
&lt;hr>
&lt;h2 id="1-bases-de-la-conception-de-thèmes-avec-les-css-custom-properties-variables-css">1. Bases de la conception de thèmes avec les CSS Custom Properties (variables CSS)
&lt;/h2>&lt;p>Actuellement, l&amp;rsquo;approche la plus standard et la plus puissante pour implémenter le mode sombre est l&amp;rsquo;utilisation des &lt;strong>CSS Custom Properties (variables CSS)&lt;/strong>. Contrairement aux variables des préprocesseurs CSS comme Sass (&lt;code>$color&lt;/code>) qui sont résolues de manière statique lors de la compilation, les variables CSS sont résolues et écrasées dynamiquement à l&amp;rsquo;exécution par le navigateur. Cela permet de modifier instantanément la palette de couleurs de l&amp;rsquo;ensemble de la page simplement en changeant de classe via JavaScript.&lt;/p>
&lt;h3 id="11-définition-dun-thème-de-couleurs-de-base">1.1 Définition d&amp;rsquo;un thème de couleurs de base
&lt;/h3>&lt;p>Tout d&amp;rsquo;abord, nous définissons la palette de couleurs du mode clair (par défaut) en utilisant la pseudo-classe &lt;code>:root&lt;/code>. Ensuite, le modèle de conception classique consiste à écraser ces variables lorsqu&amp;rsquo;un attribut tel que &lt;code>[data-theme='dark']&lt;/code> (ou la classe &lt;code>.dark&lt;/code>) est ajouté.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="c">/* Définition des variables pour le mode clair (par défaut) */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">:&lt;/span>&lt;span class="nd">root&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-bg-primary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#ffffff&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-bg-secondary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#f3f4f6&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-text-primary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#111827&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-text-secondary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#4b5563&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-accent&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#3b82f6&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-border&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#e5e7eb&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">/* Écrasement des variables en mode sombre */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>&lt;span class="nt">data-theme&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;dark&amp;#39;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-bg-primary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#111827&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-bg-secondary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#1f2937&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-text-primary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#f9fafb&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-text-secondary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#9ca3af&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-accent&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#60a5fa&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-border&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#374151&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">/* Application pratique */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">body&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nf">var&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="kc">color&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">bg&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">primary&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nf">var&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="kc">color&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="kc">text&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">primary&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">transition&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">background-color&lt;/span> &lt;span class="mf">0.3&lt;/span>&lt;span class="kt">s&lt;/span> &lt;span class="kc">ease&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">color&lt;/span> &lt;span class="mf">0.3&lt;/span>&lt;span class="kt">s&lt;/span> &lt;span class="kc">ease&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">a&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nf">var&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="kc">color&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">accent&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>De cette façon, en séparant complètement la spécification de la disposition et de la typographie de celle de la couleur (thème), la maintenabilité du CSS est considérablement améliorée.&lt;/p>
&lt;h3 id="12-utilisation-de-media-prefers-color-scheme-dark">1.2 Utilisation de @media (prefers-color-scheme: dark)
&lt;/h3>&lt;p>Si le mode sombre est configuré au niveau du système d&amp;rsquo;exploitation (OS), il est souhaitable du point de vue de l&amp;rsquo;UX d&amp;rsquo;appliquer automatiquement le thème sombre dès la première visite de l&amp;rsquo;utilisateur sur le site Web. Cela est rendu possible par la requête multimédia (media query) &lt;code>@media (prefers-color-scheme: dark)&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="c">/* Solution de repli lorsque le mode sombre est configuré dans l&amp;#39;OS */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">@&lt;/span>&lt;span class="k">media&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nt">prefers-color-scheme&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nt">dark&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">:&lt;/span>&lt;span class="nd">root&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nd">not&lt;/span>&lt;span class="o">([&lt;/span>&lt;span class="nt">data-theme&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;light&amp;#39;&lt;/span>&lt;span class="o">])&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-bg-primary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#111827&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-bg-secondary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#1f2937&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-text-primary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#f9fafb&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-text-secondary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#9ca3af&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-accent&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#60a5fa&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--color-border&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#374151&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Avec cette syntaxe, à moins que l&amp;rsquo;utilisateur n&amp;rsquo;ait explicitement sélectionné le mode clair (&lt;code>data-theme='light'&lt;/code>), le paramètre du mode sombre de l&amp;rsquo;OS est respecté et les variables sont écrasées.&lt;/p>
&lt;hr>
&lt;h2 id="2-comprendre-les-espaces-colorimétriques-et-laccessibilité-wcag-21-aaa">2. Comprendre les espaces colorimétriques et l&amp;rsquo;accessibilité (WCAG 2.1 AAA)
&lt;/h2>&lt;p>Dans la conception des couleurs du mode sombre, il ne suffit pas de simplement « rendre le fond noir et le texte blanc ». Si le contraste est trop fort, cela peut provoquer un effet de halo, rendant la lecture difficile ; à l&amp;rsquo;inverse, si le contraste est trop faible, la visibilité en pâtit. Dans les directives pour l&amp;rsquo;accessibilité des contenus Web (WCAG - Web Content Accessibility Guidelines), les ratios de contraste pour assurer la visibilité sont strictement définis.&lt;/p>
&lt;h3 id="21-formule-de-calcul-du-ratio-de-contraste-des-wcag">2.1 Formule de calcul du ratio de contraste des WCAG
&lt;/h3>&lt;p>Le ratio de contraste (Contrast Ratio) $CR$ dans les WCAG est défini de la manière suivante en utilisant la luminance relative (Relative Luminance) de la couleur de fond et de la couleur de premier plan.&lt;/p>
$$CR = \frac{L_{lighter} + 0.05}{L_{darker} + 0.05}$$&lt;p>Ici, $L_{lighter}$ est la luminance relative de la couleur la plus claire, et $L_{darker}$ est la luminance relative de la couleur la plus sombre (les valeurs vont de 0.0 à 1.0). Pour atteindre le niveau AAA des WCAG 2.1, un ratio de contraste de &lt;strong>7:1 ou plus&lt;/strong> est requis pour le texte normal, et de &lt;strong>4.5:1 ou plus&lt;/strong> pour le texte de grande taille.&lt;/p>
&lt;p>La luminance relative $L$ est calculée à partir des valeurs RGB de l&amp;rsquo;espace colorimétrique sRGB avec la formule complexe suivante.&lt;/p>
$$L = 0.2126 \times R + 0.7152 \times G + 0.0722 \times B$$&lt;p>Chaque composante ($R, G, B$) utilise une valeur normalisée obtenue en divisant la valeur d&amp;rsquo;origine sur 8 bits ($R_{sRGB}$) par 255, puis subit la conversion suivante pour lever la correction gamma.&lt;/p>
$$
R, G, B =
\begin{cases}
\frac{C_{sRGB}}{12.92} &amp; \text{if } C_{sRGB} \le 0.03928 \\
\left( \frac{C_{sRGB} + 0.055}{1.055} \right)^{2.4} &amp; \text{otherwise}
\end{cases}
$$&lt;p>Il est difficile d&amp;rsquo;effectuer ce calcul manuellement, mais en utilisant des outils de conception de couleurs, on peut sélectionner mécaniquement des couleurs qui répondent à un ratio de contraste de 7:1 ($CR \ge 7.0$).&lt;/p>
&lt;h3 id="22-hsl-vs-rgb-vs-oklch">2.2 HSL vs RGB vs OKLCH
&lt;/h3>&lt;p>Lors de la création de palettes de couleurs, les formats RGB ou HSL étaient autrefois dominants. Cependant, ceux-ci présentent un défaut majeur en termes d&amp;rsquo;« uniformité perceptuelle ».&lt;/p>
&lt;ul>
&lt;li>&lt;strong>RGB&lt;/strong> : Il s&amp;rsquo;agit des trois couleurs primaires de la lumière mécanique, ce qui rend difficile pour les humains de faire des ajustements intuitifs comme « éclaircir » ou « assombrir ».&lt;/li>
&lt;li>&lt;strong>HSL&lt;/strong> : Il utilise la teinte (Hue), la saturation (Saturation) et la luminosité (Lightness), mais la « luminosité (L) » du HSL ne correspond pas à la luminosité perçue par l&amp;rsquo;œil humain. Par exemple, un jaune pur et un bleu pur avec une luminosité de 50 % en HSL ont la même luminosité numériquement, mais le jaune paraît infiniment plus lumineux à l&amp;rsquo;œil humain.&lt;/li>
&lt;li>&lt;strong>OKLCH&lt;/strong> : C&amp;rsquo;est le dernier espace colorimétrique introduit récemment dans CSS Color Module Level 4. Composé de la luminosité perceptuelle (Lightness), de la chromaticité/saturation (Chroma) et de la teinte (Hue), il &lt;strong>correspond parfaitement aux caractéristiques visuelles humaines (uniformité perceptuelle)&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;p>L&amp;rsquo;utilisation d&amp;rsquo;OKLCH permet de conserver la même luminosité perceptuelle (Lightness) même si l&amp;rsquo;on change la teinte (Hue), ce qui rend la génération d&amp;rsquo;une palette de couleurs pour le mode sombre extrêmement prévisible et sûre.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="c">/* Exemple de définition de variables CSS utilisant OKLCH */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">:&lt;/span>&lt;span class="nd">root&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">/* Mode clair avec une luminosité de base élevée et une saturation modérée */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--bg-base&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nf">oklch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.98&lt;/span> &lt;span class="mf">0.01&lt;/span> &lt;span class="mi">250&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--text-base&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nf">oklch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.25&lt;/span> &lt;span class="mf">0.02&lt;/span> &lt;span class="mi">250&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--primary-brand&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nf">oklch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.65&lt;/span> &lt;span class="mf">0.15&lt;/span> &lt;span class="mi">250&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>&lt;span class="nt">data-theme&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;dark&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">/* En mode sombre, il suffit d&amp;#39;inverser la luminosité pour maintenir facilement le contraste perceptuel */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--bg-base&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nf">oklch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.20&lt;/span> &lt;span class="mf">0.02&lt;/span> &lt;span class="mi">250&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--text-base&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nf">oklch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.95&lt;/span> &lt;span class="mf">0.01&lt;/span> &lt;span class="mi">250&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">--primary-brand&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nf">oklch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.75&lt;/span> &lt;span class="mf">0.15&lt;/span> &lt;span class="mi">250&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c">/* Légèrement plus clair pour assurer la visibilité en mode sombre */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>En adoptant ainsi l&amp;rsquo;OKLCH, vous pouvez construire simplement une logique qui garantit un ratio de contraste constant (au niveau WCAG AAA) à travers plusieurs thèmes.&lt;/p>
&lt;hr>
&lt;h2 id="3-prévention-du-fouc-flash-of-unstyled-content-et-hydratation-ssr">3. Prévention du FOUC (Flash of Unstyled Content) et hydratation SSR
&lt;/h2>&lt;p>Ce qui préoccupe le plus les développeurs dans la prise en charge du mode sombre, c&amp;rsquo;est le problème de scintillement de l&amp;rsquo;écran appelé &lt;strong>FOUC (Flash of Unstyled Content)&lt;/strong>.&lt;/p>
&lt;h3 id="31-le-piège-du-basculement-de-thème-par-js-côté-client">3.1 Le piège du basculement de thème par JS côté client
&lt;/h3>&lt;p>Dans les SPA comme React ou Vue (ou les sites statiques via SSG), il est courant d&amp;rsquo;enregistrer les paramètres de l&amp;rsquo;utilisateur dans le &lt;code>localStorage&lt;/code> et de les lire avec JavaScript pour basculer de thème. Cependant, si ce processus est effectué via &lt;code>useEffect&lt;/code> dans React par exemple, les problèmes suivants se produisent.&lt;/p>
&lt;ol>
&lt;li>Le navigateur rend le HTML/CSS du mode clair.&lt;/li>
&lt;li>Le bundle JS est chargé et exécuté.&lt;/li>
&lt;li>Le paramètre &lt;code>dark&lt;/code> est lu depuis le &lt;code>localStorage&lt;/code>.&lt;/li>
&lt;li>La classe &lt;code>dark&lt;/code> est ajoutée au HTML, et l&amp;rsquo;écran s&amp;rsquo;assombrit soudainement (scintillement).&lt;/li>
&lt;/ol>
&lt;h3 id="32-une-prévention-parfaite-du-fouc--utilisation-de-cookie-et-ssr">3.2 Une prévention parfaite du FOUC : Utilisation de Cookie et SSR
&lt;/h3>&lt;p>La meilleure pratique pour prévenir complètement le FOUC et éviter les erreurs d&amp;rsquo;hydratation consiste à &lt;strong>enregistrer le paramètre de thème de l&amp;rsquo;utilisateur dans &lt;code>document.cookie&lt;/code> et à renvoyer le HTML avec les classes appropriées ajoutées lors du rendu côté serveur (SSR)&lt;/strong>.&lt;/p>
&lt;p>Le diagramme de séquence ci-dessous illustre le flux idéal d&amp;rsquo;initialisation du thème à l&amp;rsquo;aide de cookies.&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
participant User as &amp;#34;Utilisateur&amp;#34;
participant Browser as &amp;#34;Navigateur OS&amp;#34;
participant Server as &amp;#34;Serveur SSR&amp;#34;
participant DOM as &amp;#34;Client DOM&amp;#34;
User-&amp;gt;&amp;gt;Browser: &amp;#34;Demande de page&amp;#34;
Browser-&amp;gt;&amp;gt;Server: &amp;#34;Requête GET avec Cookie (theme=dark)&amp;#34;
Server--&amp;gt;&amp;gt;Browser: &amp;#34;Renvoie HTML avec &amp;lt;html data-theme=&amp;#39;dark&amp;#39;&amp;gt;&amp;#34;
Browser-&amp;gt;&amp;gt;DOM: &amp;#34;Rendu HTML initial (Aucun FOUC)&amp;#34;
DOM-&amp;gt;&amp;gt;DOM: &amp;#34;Exécution du script d&amp;#39;hydratation&amp;#34;
alt &amp;#34;Cookie absent (Première visite)&amp;#34;
DOM-&amp;gt;&amp;gt;Browser: &amp;#34;Vérifie OS prefers-color-scheme&amp;#34;
Browser--&amp;gt;&amp;gt;DOM: &amp;#34;Résultat matchMedia (ex. dark)&amp;#34;
DOM-&amp;gt;&amp;gt;DOM: &amp;#34;Définit data-theme=&amp;#39;dark&amp;#39; via script bloquant en ligne&amp;#34;
end
&lt;/pre>
&lt;h3 id="33-ligne-de-défense-via-un-script-en-ligne-pour-les-sites-statiques-sans-cookies">3.3 Ligne de défense via un script en ligne (Pour les sites statiques sans cookies)
&lt;/h3>&lt;p>Pour les blogs générés uniquement de manière statique (SSG) où le SSR est impossible (comme les exports statiques de Hugo, Gatsby ou Astro), il est indispensable de placer un script JavaScript en ligne s&amp;rsquo;exécutant de manière bloquante à l&amp;rsquo;intérieur de la balise &lt;code>&amp;lt;head&amp;gt;&lt;/code>, qui ajoute la classe juste avant le rendu du DOM.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- À placer à la fin de la balise &amp;lt;head&amp;gt; --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">localTheme&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">localStorage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getItem&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;theme&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">osTheme&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">window&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">matchMedia&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;(prefers-color-scheme: dark)&amp;#39;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">matches&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="s1">&amp;#39;dark&amp;#39;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;light&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">theme&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">localTheme&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">osTheme&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">documentElement&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">setAttribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;data-theme&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">theme&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Ce petit script bloque le rendu du navigateur et s&amp;rsquo;exécute immédiatement. Ainsi, au moment où l&amp;rsquo;écran est affiché, l&amp;rsquo;attribut &lt;code>data-theme&lt;/code> est déjà configuré, ce qui permet de prévenir complètement le scintillement de l&amp;rsquo;écran (FOUC).&lt;/p>
&lt;hr>
&lt;h2 id="4-approches-dimplémentation-avec-tailwind-css-et-scsscss-natif">4. Approches d&amp;rsquo;implémentation avec Tailwind CSS et SCSS/CSS natif
&lt;/h2>&lt;p>Lors de l&amp;rsquo;intégration du mode sombre dans un projet réel, il est important de comprendre l&amp;rsquo;approche de chaque outil.&lt;/p>
&lt;h3 id="41-le-mode-sombre-dans-tailwind-css">4.1 Le mode sombre dans Tailwind CSS
&lt;/h3>&lt;p>Tailwind CSS fournit par défaut une variante &lt;code>dark:&lt;/code>, ce qui rend l&amp;rsquo;implémentation du mode sombre très facile. On configure la propriété &lt;code>darkMode&lt;/code> dans le fichier de configuration (&lt;code>tailwind.config.js&lt;/code>).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// tailwind.config.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// &amp;#39;media&amp;#39; (dépendant des paramètres OS) ou &amp;#39;class&amp;#39; (basculable manuellement)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">darkMode&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;class&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">theme&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">extend&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">colors&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cm">/* Extension de la palette de couleurs Tailwind à l&amp;#39;aide de variables CSS */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">primary&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;rgb(var(--color-primary) / &amp;lt;alpha-value&amp;gt;)&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">background&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;rgb(var(--color-background) / &amp;lt;alpha-value&amp;gt;)&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Du côté HTML, il suffit d&amp;rsquo;ajouter les classes de la manière suivante.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-2xl font-bold&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Hello World&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;mt-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Tailwind makes dark mode incredibly easy.&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Cependant, écrire &lt;code>dark:bg-xxx&lt;/code> pour tous les éléments peut causer une surcharge excessive des composants. Pour les blogs ou applications de grande envergure, il est recommandé d&amp;rsquo;adopter une conception hybride (conception de couleurs sémantiques) où &lt;strong>les variables CSS servent de base, et Tailwind référence ces variables CSS&lt;/strong>.&lt;/p>
&lt;p>Le diagramme de classes ci-dessous montre l&amp;rsquo;héritage des variables CSS et les couches d&amp;rsquo;application.&lt;/p>
&lt;pre class="mermaid">
classDiagram
class GlobalCSSVariables {
&amp;#34;--color-brand-500&amp;#34;
&amp;#34;--color-gray-900&amp;#34;
}
class SemanticVariables {
&amp;#34;--bg-primary&amp;#34;
&amp;#34;--text-base&amp;#34;
&amp;#34;--accent&amp;#34;
}
class TailwindConfig {
&amp;#34;theme.colors.background&amp;#34;
&amp;#34;theme.colors.primary&amp;#34;
}
class UIComponents {
&amp;#34;class=&amp;#39;bg-background text-primary&amp;#39;&amp;#34;
}
GlobalCSSVariables &amp;lt;|-- SemanticVariables : &amp;#34;:root &amp;amp; .dark&amp;#34;
SemanticVariables &amp;lt;|-- TailwindConfig : &amp;#34;tailwind.config.js&amp;#34;
TailwindConfig &amp;lt;.. UIComponents : &amp;#34;Applique les classes utilitaires&amp;#34;
&lt;/pre>
&lt;h3 id="42-implémentation-en-raw-scsscss-utilisation-des-mixins">4.2 Implémentation en Raw SCSS/CSS (Utilisation des Mixins)
&lt;/h3>&lt;p>Pour les projets qui n&amp;rsquo;utilisent pas Tailwind et écrivent leur propre SCSS, on utilise les &lt;code>@mixin&lt;/code> pour encapsuler les styles du mode sombre.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-scss" data-lang="scss">&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/* Définition du Mixin SCSS */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">@mixin&lt;/span>&lt;span class="nf"> dark-mode&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cm">/* Supporte à la fois l&amp;#39;attribut [data-theme=&amp;#39;dark&amp;#39;] et les paramètres de l&amp;#39;OS */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">[&lt;/span>&lt;span class="nt">data-theme&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;dark&amp;#39;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="k">&amp;amp;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">@content&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">@media&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">prefers-color-scheme&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="ni">dark&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nd">:root:not&lt;/span>&lt;span class="o">([&lt;/span>&lt;span class="nt">data-theme&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;light&amp;#39;&lt;/span>&lt;span class="o">])&lt;/span> &lt;span class="k">&amp;amp;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">@content&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/* Exemple d&amp;#39;utilisation */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nc">.card&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">background-color&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mh">#ffffff&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">color&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mh">#333333&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">border&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="ni">solid&lt;/span> &lt;span class="mh">#eeeeee&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">@include&lt;/span>&lt;span class="nd"> dark-mode&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">background-color&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mh">#1a202c&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">color&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mh">#e2e8f0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">border-color&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mh">#2d3748&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Cette méthode est intuitive, mais la taille du fichier CSS compilé a tendance à s&amp;rsquo;alourdir (la requête multimédia est dupliquée pour chaque sélecteur). C&amp;rsquo;est pourquoi la tendance actuelle se tourne plutôt vers une conception axée sur les variables CSS (Custom Properties).&lt;/p>
&lt;hr>
&lt;h2 id="5-optimisation-du-mode-sombre-pour-les-images-image-et-les-svg">5. Optimisation du mode sombre pour les images (Image) et les SVG
&lt;/h2>&lt;p>Même si la conception des couleurs pour le texte et les arrière-plans est achevée, si les images et les icônes (SVG) placées comme contenu restent en mode clair, elles paraîtront éblouissantes et se détacheront trop en mode sombre. Leur optimisation est également essentielle.&lt;/p>
&lt;h3 id="51-filtres-css-pour-réduire-la-luminosité-des-images">5.1 Filtres CSS pour réduire la luminosité des images
&lt;/h3>&lt;p>Les images bitmap telles que les photos peuvent être trop lumineuses si elles sont affichées telles quelles en mode sombre. En utilisant la propriété CSS &lt;code>filter&lt;/code> pour réduire légèrement la luminosité (brightness) et augmenter le contraste (contrast) de l&amp;rsquo;image, on peut la faire fondre naturellement dans l&amp;rsquo;interface du thème sombre.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>&lt;span class="nt">data-theme&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;dark&amp;#39;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="nt">img&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nd">not&lt;/span>&lt;span class="o">([&lt;/span>&lt;span class="nt">src&lt;/span>&lt;span class="o">*=&lt;/span>&lt;span class="s2">&amp;#34;.svg&amp;#34;&lt;/span>&lt;span class="o">])&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">/* Réduit la luminosité et augmente un peu le contraste */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">filter&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">brightness&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.8&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nb">contrast&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">1.1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">transition&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">filter&lt;/span> &lt;span class="mf">0.3&lt;/span>&lt;span class="kt">s&lt;/span> &lt;span class="kc">ease&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>&lt;span class="nt">data-theme&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;dark&amp;#39;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="nt">img&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nd">hover&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">/* Restaure la luminosité d&amp;#39;origine au survol (si l&amp;#39;utilisateur veut voir les détails) */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">filter&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">brightness&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nb">contrast&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="52-remplacement-dimages-via-la-balise-picture">5.2 Remplacement d&amp;rsquo;images via la balise &lt;code>&amp;lt;picture&amp;gt;&lt;/code>
&lt;/h3>&lt;p>Les images de logos ou les diagrammes explicatifs (comme les JPEG avec un fond blanc fixe) ne peuvent pas être gérés uniquement par des filtres. Dans ces cas-là, la bonne solution consiste à utiliser l&amp;rsquo;élément HTML &lt;code>&amp;lt;picture&amp;gt;&lt;/code> et les requêtes multimédias (media queries) pour afficher un autre fichier image spécifique au mode sombre.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">picture&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- Affiché pour les utilisateurs ayant configuré leur OS en mode sombre --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">source&lt;/span> &lt;span class="na">srcset&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/img/logo-dark.png&amp;#34;&lt;/span> &lt;span class="na">media&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;(prefers-color-scheme: dark)&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- Par défaut (mode clair) --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/img/logo-light.png&amp;#34;&lt;/span> &lt;span class="na">alt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;Logo du site&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">picture&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>※ Toutefois, cette méthode ne se synchronise pas avec les basculements manuels comme via &lt;code>localStorage&lt;/code> (elle ne dépend que des paramètres de l&amp;rsquo;OS). Si vous implémentez un basculement manuel, vous devrez modifier dynamiquement la source &lt;code>src&lt;/code> des images avec JS ou basculer un &lt;code>display: none&lt;/code> via des classes CSS.&lt;/p>
&lt;h3 id="53-prise-en-charge-de-currentcolor-pour-les-icônes-svg">5.3 Prise en charge de &lt;code>currentColor&lt;/code> pour les icônes SVG
&lt;/h3>&lt;p>La méthode la plus élégante pour les SVG en ligne utilisés pour des icônes est de lier leur couleur de remplissage à la couleur du texte de l&amp;rsquo;élément parent. Pour ce faire, spécifiez &lt;code>currentColor&lt;/code> dans l&amp;rsquo;attribut &lt;code>fill&lt;/code> ou &lt;code>stroke&lt;/code> du SVG.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- La valeur de la propriété color du CSS (ex: var(--text-primary)) est appliquée automatiquement --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">svg&lt;/span> &lt;span class="na">viewBox&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;0 0 24 24&amp;#34;&lt;/span> &lt;span class="na">fill&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;currentColor&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">path&lt;/span> &lt;span class="na">d&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;M12 2L2 22h20L12 2z&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">svg&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Ainsi, lors du passage au mode sombre, si la couleur du texte de l&amp;rsquo;élément parent devient blanche, l&amp;rsquo;icône SVG passera aussi automatiquement à une teinte blanche.&lt;/p>
&lt;hr>
&lt;h2 id="6-conclusion--vers-une-conception-durable-du-mode-sombre">6. Conclusion : Vers une conception durable du mode sombre
&lt;/h2>&lt;p>Pour implémenter un mode sombre de haute qualité sur un blog ou une application web, une conception CSS couvrant les points suivants est indispensable.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Utiliser les CSS Custom Properties&lt;/strong> : Éviter le codage en dur (hardcoding) des spécifications de couleurs et les abstraire dans des noms de variables sémantiques (ex: &lt;code>--bg-primary&lt;/code>).&lt;/li>
&lt;li>&lt;strong>Adopter l&amp;rsquo;espace colorimétrique OKLCH&lt;/strong> : Concevoir de manière logique des ratios de contraste hautement accessibles (7:1 ou plus) répondant à la norme WCAG 2.1 AAA, au sein d&amp;rsquo;un espace colorimétrique perceptuellement uniforme.&lt;/li>
&lt;li>&lt;strong>Appliquer des mesures strictes contre le FOUC&lt;/strong> : Éliminer complètement les scintillements de l&amp;rsquo;écran lors du chargement initial grâce à l&amp;rsquo;association du SSR et des cookies, ou via des scripts bloquants en ligne dans le &lt;code>&amp;lt;head&amp;gt;&lt;/code>.&lt;/li>
&lt;li>&lt;strong>Optimiser les médias et les ressources&lt;/strong> : Harmoniser les éléments autres que le texte avec le thème sombre en exploitant &lt;code>filter: brightness()&lt;/code>, &lt;code>currentColor&lt;/code> et la balise &lt;code>&amp;lt;picture&amp;gt;&lt;/code>.&lt;/li>
&lt;/ol>
&lt;p>Ces attentions minutieuses, qui vont bien au-delà d&amp;rsquo;une simple « inversion de couleurs », constituent les conditions d&amp;rsquo;un blog moderne offrant une expérience de lecture (reading experience) supérieure, agréable pour les yeux et appréciée durablement par les utilisateurs. Développeurs sur le point d&amp;rsquo;introduire le mode sombre, n&amp;rsquo;hésitez pas à vous référer aux modèles de conception présentés dans cet article.&lt;/p></description></item></channel></rss>