Note : Ce contenu a été créé avant que Fabernovel ne fasse partie du groupe EY, le 5 juillet 2022.
Retour d’expérience sur nos précédents projets
Sur notre premier projet Angular2+, à savoir ce site même, nous avions mis en place un workflow front (j’entends ici : workflow de compilation javascript + styles + assets) basique, emprunté à nos autres projets Drupal ou Symfony. A savoir : gulp pour s’occuper du Sass, des fonts, des SVGs et autres assets et en laissant Webpack de s’occuper de la transpilation du code Typescript vers Javascript. Donc en dehors des guidelines Angular, notamment pour les feuilles de style, regroupées dans un sous-dossier dans /app/styles au lieu de bien les ventiler sur chaque composant.
Bilan : ça fonctionne, mais cela oblige à avoir deux watchers qui tournent en parallèle, un pour les scripts et un pour le reste. On s’ampute également d’une feature intéressante d’angular : l’encapsulation de la vue. Et enfin c’est moyen pour la performance, puisque tout est chargé à la première page visitée.
Scope des styles
Cette feature proposée par défaut par Angular permet d’instaurer une limite dans l’application des styles. C’est intéressant à deux niveaux :
- La modularité : les styles définis dans un composant ne s’appliqueront qu’à ce composant précisément. Pas besoin de s’inquiéter des effets de bord que peut engendrer une modification trop générique, seul ce composant sera affecté. Ça permet également d’utiliser des noms de classes qui ont plus de sens dans chaque composant.Pour ce faire, Angular utilise un identifiant qui est ajouté en attribut de chaque élément du DOM du composant et utilisé dans les sélecteurs CSS pour scoper les styles.
Ici l’identifiant est _ngcontent-c10
- La structure de l’application : plus besoin d’aller chercher les feuilles de style dans des dossiers d’assets annexes, elles sont dorénavant regroupées au même niveaux que les fichiers de markup, testing, et Typescript du composant.
Angular-CLI à la rescousse
Pour les projets suivants, nous avons systématisé l’utilisation d’Angular-CLI, qui rend d’inestimables services. Cet outil permet de créer et configurer un projet en quelques lignes de commandes, de générer des modules, des composants et des services dans les règles de l’art. Il nous dispense de rentrer dans la fastidieuse config Webpack, même s’il reste toujours possible de la customiser manuellement. Le CLI facilite également la compilation en mode production (AOT inclus) ou dev, voire le lancement des tests unitaires et fonctionnels, le serve avec Hot Reload. Bref, plein de bonnes choses !
A noter : le fichier de configuration du CLI embarque des paramètres modifiables pour façonner sa propre architecture.
Quel framework CSS ?
Historiquement, chez FABERNOVEL CODE, nous utilisons Bootstrap et Sass. Parallèlement, nous lorgnions du côté de Material qui propose des composants très au point. Il a fallu trancher le jour où un projet nous a imposé une charte très “Material like”, et cela a donné lieu à pas mal de débats. Bilan des forces en présences :
MaterialBootstrap
Superbe intégration dans Angular
Composants très aboutis en particulier sur les interactions mobiles
Facilité de customisation (notamment via les variables)
Bonne documentation
Layout puissant
Bootstrap
Superbe intégration dans Angular
Composants très aboutis en particulier sur les interactions mobiles
Facilité de customisation (notamment via les variables)
Bonne documentation
Layout puissant
Usage de jQuery
Faible niveau de personnalisation
Pas de grille / utilitaire de layout
Faible documentation / pas à jour
Intégration dans Angular non-officiel (le module n’est pas maintenu par la core team bootstrap)
Approche générique (en comparaison avec l’approche composant d’Angular)
Les usages sont en fait totalement différents.
Bootstrap
propose une base de composant CSS customisables facilement, via de nombreuses variables très developer-friendly. Il permet une grande rapidité d’intégration de la charte graphique. Son statut de quasi-standard le rend manipulable par tous les développeurs, même les développeurs backend s’y sont mis. :)
Les composants Bootstrap basés sur du JavaScript ne seront pas utilisables en l’état dans le Typescript des composants Angular. Si l’on souhaite les utiliser, il faut faire l’effort d’installer un package supplémentaire (ngx-bootstrap) qui n’est pas maintenu par la core team de Bootstrap, et qui n’a pas encore porté l’intégralité des composants Javascript du framework original.
Material
offre quand à lui une panoplie de composants très bien intégrés fonctionnellement dans Angular. Ils suivent précisément les guidelines visuels de Material Design dictés par Google. En revanche, les possibilités de customisation sont faibles et peu intuitives (pas de variable exposée, surcharge au cas par cas obligatoire, documentation mal tenue). Et surtout, Material n’embarque pas de système de grille avec colonne, contrairement à Bootstrap. Nous ne parlerons pas de angular-flex-layout ici, car sans l’avoir testé en conditions réelles, difficile de se prononcer. En revanche, voici un premier retour de ma part : tout les styles sont inlines REALLY ?
Bilan
: nous avons choisi de mixer subtilement le meilleur des deux mondes : Bootstrap pour la grille, et Material pour ses composants, en favorisant une architecture orientée composant tout en préservant la performance. L’enjeu est d’éviter une CSS finale énorme chargeant tous les styles dès le début.
La configuration
Commençons par installer Angular CLI en global :
npm i -g @angular/cli
Puis initialisons un nouveau projet
ng new PROJECT-NAME
Maintenant, un peu de customisation : indiquons au CLI que nous souhaitons utiliser Sass. Soit on passe par les lignes de commande ng set defaults.styleExt scss, soit on édite directement le fichier de config du CLI .angular-cli.json en y ajoutant ceci :
"defaults": { "styleExt": "scss" }
À partir de là, tous les composants générés seront accompagnés de leur feuille de style au format sass. Il est à noter que seuls les fichiers présents comme valeur de l’attribut styleUrls du décorateur du composant seront compilés en CSS en output. Le code écrit dans l’attribut style du décorateur ne sera pas interprété.
On installe ensuite Bootstrap & Material dans leurs dernières versions avec au choix npm ou yarn (angular-CLI propose les deux alternatives) :
Npm i bootstrap @angular/material @angular/cdk @angular/animations
Ou
Yarn add bootstrap @angular/material @angular/cdk @angular/animations
Les modules CDK et Animations sont essentiels pour le fonctionnement de Material, CDK étant à la base des composants et Animations permettant de les animer via Angular. Certains composants nécessitent également d’embarquer HammerJS comme dépendance de votre projet, à vous de juger si c’est nécessaire.
Une fois ces modules installés, mettons en place notre stack de style. Pour cela, nous créons un dossier “styles” à la racines du dossier root du CLI, soit “./src/styles”. Dans ce dossier, nous allons créer 3 répertoires : “common”, “material”, et “mixins”, ainsi qu’un fichier “main.scss” qui sera le point d’entrée des styles de l’application, et qui ne contiendra que des imports Sass. Déclarons maintenant ce point d’entrée à Angular-CLI en remplaçant la valeur de la clé “styles” dans .angular-cli.json par “styles/main.scss”
Mettons en place nos variables et mixins réglementaires. Pour cela, on crée un fichier “_variables.scss” dans “./src/styles/common/” et un fichier “_helpers.scss” dans “./src/styles/mixins/” dans lequel nous déclarons nos variables et autres mixins que nous utiliserons au cours de nos développements. À la fin de ces fichiers, il faut importer les variables et mixins bootstrap comme ceci : “@import “~bootstrap/scss/mixins” & @import “~bootstrap/scss/variables”. “~" sert de référence au dossier root du CLI.
Oui, mais si on ré-importe systématiquement ce fichiers dans nos composant ne risque-t-on pas d’importer plusieurs fois bootstrap dans l’application ?
Non, car tant qu’une règle CSS ( .my-element {my-rule: value;} ) n’est pas déclarée alors Sass n’exporte rien. C’est pourquoi il est important de ne JAMAIS déclarer de règle de style dans ces fichiers (variables.scss et helpers.scss) car ils seront ré-importés dans les composants Angular qui auront besoin d’avoir accès aux variables et mixins de l’application. Ainsi, si nous y déclarions des règles de styles, elles seraient déclarées autant de fois que les fichiers sont importés, ce qui risque d’alourdir considérablement le bundle CSS final.
Dans un second temps, et pour se faciliter la vie, créons un fichiers “_utils.scss” (au même niveau que “main.scss”) qui ne sera constitué que de 3 imports :
@import '~bootstrap/scss/functions'; @import './common/variables'; @import './mixins/helpers';
Nous importons ensuite ce fichier dans “main.scss” en premier.
Ensuite, pour ne pas embarquer trop de composants Bootstrap inutiles dans notre application (comme évoqué plus haut, nous n'utilisons bootstrap que pour le layout et l’application de la charte graphique du client), nous importons les styles au détail. Pour l’instant, notre point d’entrée (main.scss) ressemble donc à ça :
// This file import Bootstrap's functions, mixins & variables // + our custom mixins & variables. // Import it in whatever angular component you need // to access Bootstrap variables & mixins there (and ours !) @import './utils'; // Bootstrap imports -------------------------------- // @import '~bootstrap/scss/root', '~bootstrap/scss/reboot', '~bootstrap/scss/type', '~bootstrap/scss/images', '~bootstrap/scss/code', '~bootstrap/scss/grid', '~bootstrap/scss/tables', '~bootstrap/scss/buttons', '~bootstrap/scss/transitions', '~bootstrap/scss/button-group', '~bootstrap/scss/nav', '~bootstrap/scss/navbar', '~bootstrap/scss/card', '~bootstrap/scss/badge', '~bootstrap/scss/alert', '~bootstrap/scss/progress', '~bootstrap/scss/media', '~bootstrap/scss/list-group', '~bootstrap/scss/close', '~bootstrap/scss/utilities;
Occupons-nous ensuite de Material. On crée donc un fichier “material.scss” dans “./src/styles/material/” qui va nous servir de base pour le theming du framework. Pour cela, Material met à disposition quelques mixins utiles à la customisation des couleurs et typographies de ses composants.
Passons rapidement cette étape : retenons simplement qu’ils faut dans un premier temps importer les mixins nécessaire au theming material via
@import '~@angular/material/theming';
Puis, après avoir créé nos palettes de couleurs customs et enregistrés nos fontes chartées, il suffit d’appliquer le thème comme ceci :
// Generating our custom theme (based on custom palettes) $custom-theme: mat-light-theme($custom-theme-primary, $custom-theme-accent, $custom-theme-warn); @include angular-material-theme($custom-theme);
Le theming Material (comme dit plus haut) est assez limité pour l’instant. Si l’on souhaite aller plus loin que modifier les couleurs et la typographie, alors il va falloir ruser en analysant le markup généré des composants, puis en ajoutant une classe modifier (convention BEM) sur le composant, on saura surcharger les styles material par les nôtres. Ainsi, si l’on souhaite avoir un élément de formulaire en uppercase on pourra procéder comme ceci :
.mat-form-field--uppercase { &.mat-form-field { text-transform: uppercase; } }
Ainsi s’organise notre stack de style de base avec Bootstrap et Material. Il subsiste un problème pas encore évoqué, mais déjà résolu : nous n’avons pas accès aux variables et helpers depuis nos composants Angular. La seule solution est de les ré-importer dans chacun des composants. C’est pour ça que nous avons créer le fichier “utils.scss”, et c’est également pour ça qu’il est très important de ne pas y déclarer de règles de styles, uniquement des éléments dont l’output sera vide s’il n’est pas appliqué. Vous pouvez vérifier avec https://www.sassmeister.com/. Si on ne déclare qu’une variable, l’output CSS sera vide, contrairement à si on y déclare une règle de style. C’est la même chose pour les functions Sass.
Il est donc sans risque de ré-importer notre fichiers “utils.scss” autant de fois que nous en avons besoin, l’output CSS ne sera pas affecté.
Lazy-loading
Le lazy-loading modulaire est parti intégrante d’Angular. Nous l’utilisons via le router, et il s’avère utile pour les grosses applications. Cela évite de charger l’intégralité des modules dès l’initialisation et les modules manquants sont chargés au détail, lorsque l’on consulte le routing du module. Afin que cette logique soit également appliquée pour les styles, il est important de faire en sorte que le sass présent dans “./src/styles/” soit généraliste, car il sera chargé dès l’initialisation de l’application, contrairement aux styles des composants Angular, qui lui sera lazy-loadé au besoin de l’utilisateur.
Exemple :
Regardons comment Angular gère le lazy-loading de ses modules. Voici l’onglet network à l’initialisation de notre application :
On retrouve nos bundles JS Angular classiques, ainsi que deux chunks (morceaux / modules) correspondant à la route requêtée à l’initialisation de l’application.
Et voici l’état de notre <head> au même instant :
On constate que notre chunk javascript a bien été injecté dans l’application, et les style de ces composants également.
Le lazyloading est donc bien appliqué sur les styles, si l’on respecte le découpage par composant poussé par Angular.
Conclusion
Fini le temps où l’on bricolait les styles dans un dossiers “assets” et où l’on ne gérait les règles qu’à un niveau global. Dorénavant le style, au sein d’Angular (mais aussi comme dans d’autres maxi-frameworks javascript) s’aborde comme le reste de la stack, comme du VRAI développement, avec ses imports, ses composants, le lazy-loading, les variables, etc.
Voici un repository pour voir plus en détail ce qui est expliqué dans l’article :
https://github.com/ferdinanddoremus/starter-ng5-BS4-Material