Skip to content

Frontend - Vite, themes SCSS, JS

Le pipeline frontend a remplace l'ancien Gulp (un gulpfile.js legacy subsiste mais n'est plus utilise). Tout se lance depuis src/Web avec Node 22.12.0.

Vite

Config : src/Web/vite.config.js (+ helper vite.config.helper.js).

  • Entrees decouvertes dynamiquement (vite.config.helper.js) :
    • JS : entree unique scripts/index.js -> wwwroot/js/index.js.
    • SCSS : globSync sur styles/themes/*/main.scss ; chaque theme produit wwwroot/css/<theme>/main.css (via assetFileNames : un fichier styles-<theme>.css est renomme en <theme>/main.css).
  • emptyOutDir: false (ne vide pas wwwroot, qui contient aussi favicons/og/images).
  • Plugin maison cleanupScssJs : Vite genere un stub JS vide pour chaque entree SCSS ; ce plugin supprime ces js/styles-*.js.
  • vite-plugin-live-reload : surveille wwwroot/css/**/*.css et wwwroot/js/**/*.js pour declencher le rechargement navigateur.
  • Dev server : port 8080, proxy / -> https://localhost:44360 (l'app .NET).
  • Prod : terser (drop console), cssMinify, sourcemaps desactivees. Dev : sourcemaps + rollup-plugin-visualizer (scripts/logs/stats.html).
  • Alias : @ -> scripts, @styles -> styles.

Commandes (package.json) :

  • npm run dev : vite build --mode development puis chokidar re-build sur changement styles/**/*.scss et scripts/**/*.js.
  • npm run build : build (mode par defaut).
  • npm run build:prod : ESLint puis vite build --mode production (une erreur de lint stoppe le build).

PostCSS

Chaine appliquee aux CSS (vite.config.js) :

  1. postcss-sort-media-queries
  2. autoprefixer
  3. postcss-combine-duplicated-selectors (removeDuplicatedValues)
  4. en prod uniquement : PurgeCSS puis cssnano

SCSS via api: "modern-compiler", deprecations silencieuses (if-function, global-builtin, color-functions, import).

PurgeCSS

purgeCss() (vite.config.helper.js) scanne Views/**/*.cshtml et scripts/**/*.js. Safelist : standard = /^js-/, /^is-/, /^has-/, /^u-/, /^util--/ ; deep = /^cookie/. Toute classe injectee dynamiquement (par JS, par une lib) doit etre ajoutee a cette safelist sinon elle sera purgee en prod.

Themes SCSS

Arborescence sous src/Web/styles/ :

  • themes/<theme>/main.scss : point d'entree d'un theme. Importe Bootstrap (@use ".../node_modules/bootstrap/scss/bootstrap") et les partials base/ (header, footer, cookies, a11y, sections d'exemple). Peut surcharger via with (...).
  • themes/<theme>/_bootstrap_variables.scss : overrides des tokens Bootstrap par theme.
  • base/ : _bootstrap_variables.scss, layout/ (_header, _footer, _cookies, _a11y), sections/, elements/.

Themes livres dans le template : default (reference), cave, piscines (exemples de variations, dont une section card-section propre a piscines). Ce sont des exemples a adapter/supprimer dans un projet aval.

Couleurs et valeurs de design - toujours une variable, jamais une valeur en dur

Règle : une couleur ne s'écrit qu'une fois, dans une variable. Partout ailleurs, on référence la variable. Aucun #3214a8, rgb(...) ou hsl(...) littéral dans une règle de style, une vue, un fichier backoffice ou un module JS.

Vaut aussi pour les autres valeurs de design répétées : rayons, ombres, durées de transition, points de rupture, graisses.

Pourquoi

Une couleur en dur ne casse rien tout de suite. Elle coûte plus tard, de trois façons :

  1. On ne peut plus la changer d'un geste. La même valeur finit copiée dans plusieurs fichiers et une seule occurrence est oubliée au moment du changement de charte.
  2. Elle casse les thèmes. styles/base/ est partagé par tous les thèmes. Un color: #000 enfoui dans un partial partagé est invisible jusqu'au jour où un thème sombre s'affiche.
  3. Elle survit aux merges upstream. base/ vient du template : une valeur en dur ajoutée là revient à chaque git merge upstream.

La forme par défaut : variable SASS + @use

C'est du SASS : on utilise le mécanisme du langage. Un fichier de variables, importé par @use dans chaque fichier qui en a besoin. Pas de custom property CSS par défaut.

Nature de la valeurFichierConsommé par
Tout ce qui est propre à un thème : couleurs de marque, neutres, couleurs de composants, réglages de champs, header, footerstyles/themes/<theme>/_bootstrap_variables.scssthemes/<theme>/main.scss et les partials du thème
Valeur partagée par tous les thèmes et non colorée : points de rupture, échellesstyles/abstracts/_<sujet>.scss (dossier absent du template, à créer au premier besoin)n'importe quel partial, via @use
Valeur locale à un composant, surchargeableen tête du partial$nom: valeur !default;

Le fichier de variables du thème ne se limite pas aux overrides Bootstrap : il porte toutes les valeurs du thème, avec des noms sémantiques.

scss
// styles/themes/<theme>/_bootstrap_variables.scss

// Overrides Bootstrap
$primary:   #ee4305;
$secondary: #13134f;
$body-color: #13134f;

// Neutres
$grey:       #999999;
$grey-dark:  #555555;
$near-black: #151515;

// Alias semantiques, composes a partir des precedents
$field-dark:   $near-black;
$input-placeholder-color: $grey;
$footer-bg:    #04041a;
$footer-text:  $grey;

Composer plutôt que recopier. Une couleur brute est déclarée une seule fois, puis les usages la référencent ($field-dark: $near-black). Changer le neutre met à jour tous ses usages.

Consommer

scss
// styles/themes/<theme>/layout/_footer.scss
@use "../bootstrap_variables" as vars;

.footer {
  background: vars.$footer-bg;
  color: vars.$footer-text;
}

Le main.scss du thème charge le fichier une fois et passe les overrides à Bootstrap :

scss
@use "bootstrap_variables" as customBootstrapVars;
@use "../../../node_modules/bootstrap/scss/bootstrap" with (
  $primary: customBootstrapVars.$primary,
  $body-color: customBootstrapVars.$body-color
);

Pour une valeur partagée non thématique, même mécanique avec un alias court :

scss
@use "../../abstracts/breakpoints" as bp;

.card { padding: 1rem; @include bp.up(md) { padding: 2rem; } }

Le seul cas où une custom property reste nécessaire

Un partial de styles/base/ est chargé par tous les thèmes : il ne peut pas savoir lequel, donc il ne peut pas faire @use d'un fichier de variables de thème. Deux issues, dans cet ordre :

  1. Le partial ne pose pas de couleur. Il ne fait que du structurel (grille, espacements, positionnement) et laisse la couleur au thème. C'est la solution à préférer, et elle suffit dans la grande majorité des cas.
  2. Le partial a réellement besoin d'une couleur variable : là seulement, une custom property CSS que chaque thème définit dans son :root. C'est le seul mécanisme lu au runtime, donc le seul qui laisse un fichier compilé une fois servir plusieurs thèmes.

Autrement dit, var() n'est pas le défaut : c'est le recours quand @use est structurellement impossible.

Trois pièges

Le fichier de variables du thème ne remonte pas dans base/. Déclarer $grey dans themes/apol/_bootstrap_variables.scss ne le rend pas disponible dans base/layout/_footer.scss. Un color: #999 dans un partial de base/ alors que $grey: #999999 existe dans le thème est un cas réel, présent dans plusieurs dépôts : la variable existe, mais elle n'est pas atteignable depuis là. C'est le signal qu'il faut soit remonter la règle dans le thème, soit passer par une custom property.

var() ne se résout pas dans une data-URI SVG. Un background: url("data:image/svg+xml,... fill='%23206bff'...") a la couleur cuite dedans, et aucune variable n'y changera quoi que ce soit. On ne tokenise pas l'intérieur d'une data-URI : un thème qui veut une autre couleur redéclare la déclaration background entière. Dans un SVG inline en Razor, en revanche, fill="var(--nom)" fonctionne normalement.

Le backoffice n'a pas de SASS. Les CSS d'App_Plugins/ sont hors du pipeline Vite, donc @use n'y est pas disponible. La règle s'y applique quand même, avec le seul outil disponible : déclarer en :root et référencer. Une couleur de marque écrite en dur dans deux fichiers d'App_Plugins est exactement ce que cette règle existe pour empêcher.

Détecter

bash
# Valeurs en dur dans le SCSS, hors declarations de variables.
LC_ALL=C grep -rn "#[0-9a-fA-F]\{3,8\}\b" src/Web/styles --include="*.scss" | grep -v '^\s*\$'

# Une meme couleur ecrite dans plusieurs fichiers = a factoriser.
LC_ALL=C grep -rhio "#[0-9a-f]\{6\}" src/Web/styles src/Web/App_Plugins | sort | uniq -c | sort -rn

Bootstrap d'abord - le SCSS custom est le dernier recours

Le template livre Bootstrap 5.3. Objectif : écrire le moins de SCSS custom possible. La mise en page, les espacements, la typographie et les états courants passent par les classes utilitaires dans le HTML, pas par des règles SCSS.

Règle stricte : ne jamais écrire en SCSS une propriété qui a une classe utilitaire exacte. Avant d'écrire une déclaration, vérifier s'il existe un utilitaire.

Propriété CSSClasse utilitaire
display: blockd-block
display: flexd-flex
display: inline-flex + align-items: center + justify-content: centerd-inline-flex align-items-center justify-content-center
font-weight: 700 / 600 / 500fw-bold / fw-semibold / fw-medium
text-transform: uppercasetext-uppercase
text-decoration: nonetext-decoration-none
position: relativeposition-relative
padding / marginp-* / m-* (py-3, mb-4, ...)
bordure simpleborder, border-bottom, border-0
colonnes de grillerow-cols-md-2, col-lg-6, ...

Ces propriétés vont dans le HTML, pas dans le .scss.

Ce qui justifie du SCSS custom

Ce que les utilitaires ne couvrent pas : décors et dégradés, glyphes de police d'icônes (::before), effets de survol composés, white-space, animations, sélecteurs d'état complexes, et tout ce qui relève du contrat de tokens du thème.

La seule exception à la règle stricte

Les éléments dont on ne contrôle pas le markup parce qu'une bibliothèque JavaScript les rend : vanilla-cookieconsent (.cc-*), un calendrier, un carrousel, une carte. Il n'y a pas de HTML à éditer, donc leur style passe forcément par du SCSS qui cible leurs classes. C'est le seul cas où redéclarer en SCSS une propriété qui a un utilitaire est admis.

Pourquoi cette règle compte dans ce dépôt

Deux effets, en plus de la lisibilité :

  • PurgeCSS garde les utilitaires trouvés dans le markup et supprime le SCSS mort. Moins de SCSS custom donne une feuille plus petite sans réglage de safelist.
  • Contrat upstream : styles/base/ vient du template et se fait merger dans chaque projet aval. Chaque règle custom ajoutée là est une surface de conflit à chaque git merge upstream. Un utilitaire dans une vue ne crée aucun conflit.

JavaScript

  • scripts/index.js : expose Bootstrap sur window, puis sur DOMContentLoaded -> load -> requestAnimationFrame charge en lazy modules/exampleSection ; charge modules/cookiesConsent via requestIdleCallback (fallback setTimeout) pour ne pas bloquer le LCP.
  • scripts/modules/ : un module par fonctionnalite, objet avec init() (ex. exampleSection.js, cookiesConsent.js).
  • scripts/helpers/utilityHelper.js : utilitaires partages.
  • Charge dans _MasterLayout via <script src="/js/index.js" asp-append-version="true" defer>.

ESLint

eslint.config.js (flat-config) cible scripts/**/*.js. Regles en error : prefer-const, no-var, eqeqeq, prefer-template, no-duplicate-imports, no-implicit-globals, default-case.

Important

  • Ne jamais editer src/Web/wwwroot/css|js (genere). Editer styles/ et scripts/.
  • /css et /js sont fingerprintes (asp-append-version) et caches en immutable (voir overview.md).

Contributors

No contributors

Changelog

No recent changes