Fermer

août 9, 2019

Gestion des CSS inutilisés dans SASS pour améliorer les performances


Connaissez-vous l’impact des CSS non utilisées sur les performances? Spoiler: C’est beaucoup! Dans cet article, nous explorerons une solution orientée SASS pour traiter les CSS inutilisés; en évitant le besoin de dépendances compliquées de Node.js impliquant des navigateurs sans tête et une émulation DOM.

Dans le développement front-end moderne, les développeurs doivent s'efforcer d'écrire un CSS évolutif et facile à gérer. Autrement, ils risquent de perdre le contrôle de certains détails, tels que la cascade et le sélecteur, à mesure que le code grandit et que davantage de développeurs y contribuent.

Pour y parvenir, il est possible d’utiliser des méthodologies telles que le CSS orienté objet (OOCSS), qui Plutôt que d’organiser les CSS autour du contexte de la page, encourage la séparation de la structure (systèmes de grille, espacement, largeurs, etc.) de la décoration (polices, marque, couleurs, etc.).

Les noms de classe CSS tels que:

  • .blog -right-column
  • .latest_topics_list
  • .job-vacancy-ad

Sont remplacés par des alternatives plus réutilisables, qui appliquent les mêmes styles CSS, mais ne sont liées à aucun contexte particulier:

  • .col. -md-4
  • .list-group
  • .card

Cette approche est généralement mise en œuvre à l'aide d'un cadre SASS tel que Bootstrap Foundation ou de plus en plus souvent, un cadre sur mesure pouvant être adapté au projet.

Nous utilisons des classes CSS choisies dans une structure de modèles, de composants d’interface utilisateur et de classes d’utilitaires. L'exemple ci-dessous illustre un système de grille commun créé à l'aide de Bootstrap, qui s'empile verticalement, puis, une fois le point d'arrêt md atteint, bascule vers une présentation à trois colonnes.

Colonne 1
Colonne 2
Colonne 3

Par programme les classes générées telles que .col-12 et .col-md-4 sont utilisées ici pour créer ce modèle. Mais qu'en est-il de .col-1 à .col-11 .col-lg-4 .col-md-6 ou .col-sm-12 ? Ce sont tous des exemples de classes qui seront incluses dans la feuille de style CSS compilée, téléchargées et analysées par le navigateur, bien qu'elles ne soient pas utilisées.

Dans cet article, nous allons commencer par explorer l'impact que peut avoir le code CSS non utilisé sur la page. vitesses de chargement. Nous aborderons ensuite une solution existante pour le supprimer des feuilles de style, suivi de ma propre solution orientée SASS.

Mesure de l'impact des classes CSS inutilisées

Pendant que j'adore Sheffield United, les puissantes lames, le site Web CSS est regroupé dans un fichier unique de 568 Ko minifié, qui atteint 105 Ko même lorsque gzippé. C’est beaucoup.

Ceci est le site Web de Sheffield United mon équipe de football locale (c’est pour vous beaucoup dans les colonies). ( Grand aperçu )

Allons-nous voir à quel point ce code CSS est réellement utilisé sur leur page d'accueil? Une recherche rapide sur Google révèle de nombreux outils en ligne, mais je préfère utiliser l'outil de couverture dans Chrome, qui peut être exécuté directement à partir de l'outil de développement de Chrome. Essayons-le.

Le moyen le plus rapide d'accéder à l'outil de couverture dans les outils de développement est d'utiliser le raccourci clavier Ctrl + Maj + P ou Commande + Maj + P (Mac) pour ouvrir le menu de commandes. Dans celui-ci, tapez couverture puis sélectionnez l'option "Afficher la couverture". ( Grand aperçu )

Les résultats montrent que seuls 30kb de CSS de la feuille de style de 568kb sont utilisés par la page d'accueil, les 538ko restants correspondant aux styles requis pour le reste de le site Web. Cela signifie qu'un 94.8% du CSS est inutilisé.

Vous pouvez voir les timings de ce type pour tous les actifs de Chrome dans les outils de développement via Réseau -> Cliquez sur votre actif -> onglet Timing. ( Grand aperçu )

Le CSS fait partie du chemin de rendu critique d’une page Web, qui implique toutes les étapes qu'un navigateur doit terminer avant de pouvoir commencer le rendu de la page. Cela fait du CSS un élément bloquant le rendu.

Ainsi, lors du chargement du site Web de Sheffield United à l’aide d’une bonne connexion 3G, le téléchargement du fichier CSS et le rendu de la page peuvent prendre environ 1,15 s. C'est un problème.

Google l'a également reconnu. Lorsque vous exécutez un audit Lighthouse, en ligne ou via votre navigateur, le temps de chargement potentiel et les économies de taille de fichier qui pourraient être réalisés en supprimant les fichiers CSS inutilisés sont mis en évidence.

Dans Chrome (et Chromium Edge), vous pouvez activer les audits de Google Lighthouse en cliquant sur l'onglet Audit dans les outils de développement. ( Grand aperçu )

Solutions existantes

L’objectif est de déterminer quelles classes CSS ne sont pas nécessaires et de les supprimer de la feuille de style. Des solutions existantes sont disponibles pour tenter d'automatiser ce processus. Ils peuvent généralement être utilisés via un script de construction Node.js ou via des exécuteurs de tâches tels que Gulp. Ceux-ci incluent:

Ceux-ci fonctionnent généralement de la même manière:

  1. En bulldozer, le site Web est accessible via un navigateur sans navigateur (par exemple: puppeteer ) ou par une émulation DOM (par exemple: jsdom ).
  2. D'après les éléments HTML de la page, tous les fichiers CSS non utilisés sont identifiés.
  3. Ceci est supprimé de la feuille de style, pour ne laisser que ce qui est nécessaire.

Bien que ces outils automatisés soient parfaitement valides et que utilisé plusieurs d’entre eux dans le cadre de nombreux projets commerciaux, j’ai rencontré quelques inconvénients qui méritent d’être partagés:

  • Si les noms de classe contiennent des caractères spéciaux tels que '@' ou '/', ils ne le seront peut-être pas. reconnu sans écrire du code personnalisé. J'utilise BEM-IT de Harry Roberts, qui implique la structuration de noms de classe avec des suffixes sensibles, comme: u-width-6/12 @ lg alors j'ai déjà abordé ce problème.
  • Si le site Web utilise le déploiement automatisé, cela peut ralentir le processus de construction, surtout si vous avez beaucoup de pages et beaucoup de CSS.
  • Les connaissances relatives à ces outils doivent être partagées par toute l'équipe, sinon la confusion peut apparaître et frustration lorsque CSS est mystérieusement absent dans les feuilles de style de production.
  • Si votre site Web contient de nombreux scripts tiers exécutés, parfois lorsqu’ils sont ouverts dans un navigateur sans interface graphique, ceux-ci ne sont pas lisibles et peuvent provoquer des erreurs de filtrage. En général, vous devez donc écrire du code personnalisé pour exclure les scripts tiers lorsqu'un navigateur sans tête est détecté, ce qui peut s'avérer compliqué en fonction de votre configuration.
  • Généralement, ces types d'outils sont compliqués et introduisent de nombreuses dépendances supplémentaires le processus de construction. Comme pour toutes les dépendances de tiers, cela implique de s'appuyer sur le code de quelqu'un d'autre.

En gardant ces points à l'esprit, je me suis posé la question suivante:

En utilisant simplement SASS, est-il possible de mieux gérer le SASS que nous compilons ainsi, tout CSS inutilisé peut être exclu, sans pour autant supprimer brutalement les classes source du SASS?

Alerte Spoiler: La réponse est oui. Voici ce que j'ai trouvé.

Solution orientée SASS

La solution doit fournir un moyen rapide et facile de choisir ce que SASS doit être compilé, tout en étant suffisamment simple pour ne pas l'être. t ajouter plus de complexité au processus de développement ou empêcher les développeurs de tirer profit de choses telles que les classes CSS générées par programme.

Pour commencer, il existe un référentiel avec des scripts de construction et quelques exemples de styles à partir desquels vous pouvez cloner ici . .

Conseil: Si vous êtes bloqué, vous pouvez toujours faire un renvoi à la version complétée sur la branche principale .

cd . dans le référentiel, exécutez npm install puis npm run build pour compiler tout SASS en CSS, le cas échéant. Cela devrait créer un fichier css de 55 Ko dans le répertoire dist.

Si vous ouvrez ensuite /dist/index.html dans votre navigateur Web, vous devriez voir un composant assez standard, qui se développe en un clic pour le révéler. un peu de contenu. Vous pouvez également afficher ce document ici dans lequel les conditions de réseau réelles seront appliquées, afin que vous puissiez exécuter vos propres tests.

Nous utiliserons ce composant d'interface utilisateur d'extension comme sujet de test lors du développement du système orienté SASS. solution pour la gestion des CSS non utilisées. ( Grand aperçu )

Filtrage au niveau partiel

Dans une configuration SCSS typique, vous aurez probablement un seul fichier manifeste (par exemple: main.scss dans le repo), ou un par page (par exemple: index.scss produits.scss contact.scss ) où les éléments partiels de cadre sont importés. Suivant les principes OOCSS, ces importations peuvent ressembler à ceci:

Exemple 1
 / *
Modèles de conception non décorés
* /

@import 'objets / box';
@import 'objets / conteneur';
@import 'objets / layout';

/ *
Composants de l'interface utilisateur
* /

@import 'composants / bouton';
@import 'composants / expander';
@import 'composants / typographie';

/ *
Classes d'assistance hautement spécifiques
* /

@import 'utilities / alignments';
@import 'utilities / widths'; 

Si aucun de ces partiels n'est utilisé, le moyen naturel de filtrer ce fichier CSS inutilisé serait simplement de désactiver l'importation, ce qui empêcherait sa compilation.

Par exemple, si vous utilisez uniquement le composant expandeur, le manifeste se présentera généralement comme suit:

Exemple 2
 / *
Modèles de conception non décorés
* /
// @import 'objets / box';
// @import 'objets / conteneur';
// @import 'objets / layout';

/ *
Composants de l'interface utilisateur
* /
// @import 'components / button';
@import 'composants / expander';
// @import 'composants / typographie';

/ *
Classes d'assistance hautement spécifiques
* /
// @import 'utilities / alignments';
// @import 'utilities / widths'; 

Toutefois, conformément à OOCSS, nous séparons la décoration de la structure pour permettre une réutilisation maximale. Il est donc possible que le module d'expansion nécessite le CSS à partir d'autres classes d'objets, de composants ou d'utilitaires. correctement. À moins que le développeur ne soit conscient de ces relations en inspectant le code HTML, il se peut qu'il ne sache pas importer ces partiels, de sorte que toutes les classes requises ne seraient pas compilées.

] dist / index.html cela semble être le cas. Il utilise les styles des objets boîte et de présentation, du composant typographie et des utilitaires largeur et alignement.

dist / index.html

Toggle Expander

Lorum ipsum          

Laissons le problème en suspens en rendant ces relations officielles dans le SASS lui-même. Ainsi, une fois qu'un composant est importé, toutes les dépendances sont également importées automatiquement. De cette façon, le développeur n'a plus la surcharge supplémentaire d'avoir à auditer le code HTML pour savoir ce qu'il lui reste à importer.

Carte d'imports programmatiques

Pour que ce système de dépendance fonctionne, plutôt que de simplement commenter dans . @import dans le fichier manifeste, la logique SASS devra dicter si des partiels seront compilés ou non.

Dans src / scss / settings créez un nouveau partiel appelé _imports .scss @import dans settings / _core.scss puis créez la carte SCSS suivante:

src / scss / settings / _core. scss
 @import 'points d'arrêt';
@import 'espacement';
@import 'imports'; 
src / scss / settings / _imports.scss
 $ imports: (
   objet: (
    'boîte',
    'récipient',
    'disposition'
   ),
   composant: (
    'bouton',
    'expander',
    'typographie'
   ),
   utilité: (
    «alignements»,
    'largeurs'
   )
); 

Cette carte aura le même rôle que le manifeste d’importation de exemple 1 .

Exemple 4
 $ importations: (
   objet: (
    //'boîte',
    //'récipient',
    //'disposition'
   ),
   composant: (
    //'bouton',
    'expander',
    //'typographie'
   ),
   utilité: (
    // 'alignements',
    // 'largeurs'
   )
); 

Il devrait se comporter comme un jeu standard de @imports en ce sens que si certains partiels sont commentés (comme ci-dessus), le code ne doit pas être compilé avec build. [19659003] Mais comme nous voulons importer automatiquement les dépendances, nous devrions également pouvoir ignorer cette carte dans les bonnes circonstances.

Render Mixin

Commençons par ajouter une logique SASS. Créez _render.scss dans src / scss / tools puis ajoutez ses @import à outils / _core.scss .

. ] Dans le fichier, créez un mixin vide appelé render () .

src / scss / tools / _render.scss
 @mixin render () {

} 

Dans le mixin, nous devons écrire SASS qui fait ce qui suit:

  • render ()
    "Hey, $ importations le beau temps n’est-ce pas? Dis, as-tu l'objet conteneur dans ta carte? "
  • $ importations
    false
  • render ()
    “ C'est dommage, on dirait que sa composante de bouton? "
  • $ importations
    true
  • render ()
    " Nice! C'est la Cliquez sur le bouton en cours de compilation. Dites bonjour à ma femme. ”

Dans le SASS, cela se traduit comme suit:

src / scss / tools / _render.scss
 @mixin render ($ name, $ layer) {
   @if (index (map-get ($ imports, $ layer), $ name)) {
      @contenu;
   }
} 

En gros, vérifiez si le partiel est inclus dans la variable $ imports et si c'est le cas, convertissez-le en utilisant la directive @content de SASS, qui permet de passer un bloc de contenu. dans le mixin.

Nous l’utiliserions de la manière suivante:

Exemple 5
 @include render ('button', 'composant') {
   .c-button {
      // styles et al
   }

   // toute autre déclaration de classe
} 

Avant d’utiliser ce mixin, nous pouvons y apporter une petite amélioration. Le nom de la couche (objet, composant, utilitaire, etc.) est quelque chose que nous pouvons prédire en toute sécurité, nous avons donc la possibilité de rationaliser un peu les choses.

Avant la déclaration de rendu du mélange, créez une variable appelée $ layer et supprimez la variable du même nom dans les paramètres de mixins. Comme ceci:

src / scss / tools / _render.scss
 $ layer: null! Default;
@mixin render ($ name) {
   @if (index (map-get ($ imports, $ layer), $ name)) {
      @contenu;
   }
} 

Maintenant, dans les _core.scss partiels où objets, composants et utilitaires @imports sont situés, redéclarez ces variables aux valeurs suivantes; représentant le type des classes CSS importées.

src / scss / objects / _core.scss
 $ layer: 'object';

@import 'box';
@import 'conteneur';
@import 'layout'; 
src / scss / components / _core.scss
 $ layer: 'composant';

@import 'button';
@import 'expander';
@import 'typographie'; 
src / scss / utilities / _core.scss
 $ layer: 'utility';

@import 'alignements';
@import 'widths'; 

De cette façon, lorsque nous utilisons le mixage de render () tout ce que nous avons à faire est de déclarer le nom partiel.

Enveloppez le render de (19459012) () [) mixin autour de chaque déclaration d'objet, de composant et de classe d'utilitaires, comme indiqué ci-dessous. Cela vous donnera une utilisation de mixage de rendu par partiel.

Par exemple:

src / scss / object / _layout.scss
 @include render ('button') {
   .c-button {
      // styles et al
   }

   // toute autre déclaration de classe
} 
src / scss / components / _button.scss
 @include render ('button') {
   .c-button {
      // styles et al
   }

   // toute autre déclaration de classe
} 

Note: Pour utilitaires / _widths.scss en enveloppant la fonction render () autour de tout le partiel, une erreur de compilation, comme dans SASS, ne peut pas imbriquer des déclarations mixin dans des appels mixin. Au lieu de cela, enroulez simplement le render () mixin autour du create-widths () comme ci-dessous:

 @include render ('widths') {

// GÉNÉRER DES LARGES STANDARD
// ---------------------------------------------------- ---------------------

// Exemple: .u-width-1/3
@include create-widths ($ utility-widths-sets);

// GÉNÉRER DES LARGES RESPONSABLES
// ---------------------------------------------------- ---------------------

// Créer des variantes sensibles à l'aide de settings.breakpoints
// Change la largeur lorsque le point d'arrêt est atteint
// Exemple: .u-width-1/3 @ md

@each $ bp-name, $ bp-value dans $ mq-breakpoints {
   @include mq (# {$ bp-name}) {
      @include create-widths ($ utility-widths-sets,  @, # {$ bp-name});
   }
}

// Fin du rendu
} 

Ceci étant en place, seuls les partiels référencés dans $ imports seront compilés.

Mélangez les éléments qui sont commentés dans $ imports et exécutez npm exécutez build dans le terminal pour l'essayer.

Carte des dépendances

Nous importons maintenant des partiels par programmation, nous pouvons commencer à implémenter la logique de dépendance.

Dans . ] src / scss / settings créez un nouveau partiel appelé _dependencies.scss @import dans settings / _core.scss mais assurez-vous c'est après _imports.scss . Ensuite, créez la carte SCSS suivante:

src / scss / settings / _dependencies.scss
 $ dépendances: (
   élargisseur: (
      objet: (
         'boîte',
         'disposition'
    ),
    composant: (
         'bouton',
         'typographie'
    ),
    utilité: (
         «alignements»,
         'largeurs'
    )
   )
); 

Ici, nous déclarons des dépendances pour le composant expand car il nécessite des styles provenant d’autres partiels pour être restitués correctement, comme indiqué dans dist / index.html .

Cette liste permet d’écrire logique qui voudrait dire que ces dépendances seraient toujours compilées avec leurs composants dépendants, quel que soit l'état de la variable $ imports .

Au-dessous de $ dépendances créez un mixin appelé dependency-setup () . Ici, nous ferons les actions suivantes:

1. Parcourez la carte des dépendances.
 @mixin dependency-setup () {
   @each $ composantKey, $ composantValue dans $ dépendances {
    
   }
} 
2. Si le composant se trouve dans $ importations parcourez sa liste de dépendances.
 @mixin dependency-setup () {
   Composants $: map-get ($ imports, composant);
   @each $ composantKey, $ composantValue dans $ dépendances {
      @if (index ($ components, $ composantKey)) {
         @each $ layerKey, $ layerValue dans $ composantValue {

         }
      }
   }
} 
3. Si la dépendance n’est pas dans $ imports ajoutez-la.
 @mixin dependency-setup () {
   Composants $: map-get ($ imports, composant);
   @each $ composantKey, $ composantValue dans $ dépendances {
       @if (index ($ components, $ composantKey)) {
           @each $ layerKey, $ layerValue dans $ composantValue {
               @ chaque $ partKey, $ partValue dans $ layerValue {
                   @if not index (map-get ($ imports, $ layerKey), $ partKey) {
                       $ imports: fusion de cartes ($ imports, (
                           $ layerKey: append (map-get ($ imports, $ layerKey), '# {$ partKey}')
                       )) !global;
                   }
               }
           }
       }
   }
} 

Y compris le drapeau ! Global indique au SASS de rechercher la variable $ imports dans la portée globale, plutôt que dans la portée locale de mixin.

4. Ensuite, il suffit d’appeler le mixin.
 @mixin dependency-setup () {
   ...
}
@include dependency-setup (); 

Nous avons donc maintenant un système d'importation partielle amélioré qui permet à un développeur qui importe un composant d'importer manuellement chacun de ses différents partiels de dépendance. 19659003] Configurez la variable $ importations de sorte que seul le composant expandeur soit importé, puis exécutez npm run build . Vous devriez voir dans le CSS compilé les classes de développement et toutes ses dépendances.

Cependant, cela n'apporte rien de nouveau au tableau en termes de filtrage des CSS non utilisés, car la même quantité de SASS est toujours en cours. importé, programmatique ou non. Améliorons cela.

Importation améliorée de dépendances

Un composant peut ne nécessiter qu'une seule classe à partir d'une dépendance, alors continuer et importer toutes les classes de cette dépendance entraîne simplement le même fardeau inutile que nous essayons de faire.

Nous pouvons affiner le système pour permettre un filtrage plus granulaire classe par classe, afin de nous assurer que les composants sont compilés avec uniquement les classes de dépendance dont ils ont besoin.

Avec la plupart des modèles de conception, qu'ils soient décorés ou non, il existe Il existe un nombre minimum de classes qui doivent être présentes dans la feuille de style pour que le modèle soit correctement affiché.

Pour les noms de classe utilisant une convention de dénomination établie, telle que BEM généralement "Block" et "Element". Les classes nommées sont au minimum obligatoires, les «modificateurs» étant généralement facultatifs.

Note: Les classes utilitaires ne suivraient généralement pas la route BEM, car elles sont de nature isolée foc

Par exemple, jetez un œil à cet objet multimédia, qui est probablement l’exemple le plus connu de CSS orienté objet:

 Image
Oh!    

Si un composant a cet ensemble comme dépendance, il est logique de toujours compiler .o-media .o-media__image et . media__text car c’est la quantité minimale de CSS requise pour que le modèle fonctionne. Cependant, avec .o-media-spacing-small étant un modificateur facultatif, il ne doit être compilé que si nous le disons explicitement, car son utilisation peut ne pas être cohérente avec toutes les instances d'objet multimédia.

Nous allons modifier la structure de la carte $ dependencies pour nous permettre d'importer ces classes facultatives, tout en incluant un moyen d'importer uniquement du bloc et de l'élément si aucun modificateur n'est requis.

Pour commencer, vérifiez le code HTML d'expander dans dist / index.html et notez les classes de dépendance utilisées. Notez-les sur la carte $ dépendances comme suit:

src / scss / settings / _dependencies.scss
 $ dépendances: (
   élargisseur: (
       objet: (
           boîte: (
               'o-box - spacing-small'
           ),
           disposition: (
               'o-layout - fit'
           )
       ),
       composant: (
           bouton: vrai,
           typographie: (
               'c-type-echo',
           )
       ),
       utilité: (
           alignements: (
               'u-flex-middle',
               'u-align-center'
           ),
           largeurs: (
               'u-width-grow',
               'u-width-shrink'
           )
       )
   )
); 

Lorsqu'une valeur est définie sur true, nous la traduisons en "Compilez uniquement les classes au niveau des blocs et des éléments, sans modificateurs!".

L'étape suivante consiste à créer une variable de liste blanche pour stocker ces classes, et toute autre classe (non dépendante) que nous souhaitons importer manuellement. Dans /src/scss/settings/imports.scss après $ imports créez une nouvelle liste SASS intitulée $ global-filter .

. ] src / scss / settings / _imports.scss
 $ global-filter: (); 

Le principe de base de $ global-filter est que toutes les classes stockées ici seront compilées sur build tant que le partiel auquel ils appartiennent est importé via $ imports .

Ces noms de classe peuvent être ajoutés par programme s'il s'agit d'une dépendance de composant, ou manuellement si la variable est déclarée. , comme dans l'exemple ci-dessous:

Exemple de filtre global
 $ global-filter: (
   'o-box - spacing-regular @ md',
   'U-align-center',
   'u-width-6/12 @ lg'
); 

Ensuite, nous devons ajouter un peu plus de logique au mixin @ dependency-setup donc toutes les classes référencées dans $ dependencies sont automatiquement ajoutées à notre . $ global-filter liste blanche.

Au-dessous de ce bloc:

src / scss / settings / _dependencies.scss
 @if not index (map-get ($ imports, $ layerKey), $ partKey) {

} 

… ajoutez l'extrait suivant.

src / scss / settings / _dependencies.scss
 @each $ class in $ partValue {
   $ global-filter: append ($ global-filter, '# {$ class}', 'virgule')! global;
} 

Ceci parcourt toutes les classes de dépendance et les ajoute à la liste blanche de $ global-filter .

À ce stade, ajoutez une instruction @debug sous le [ dependency-setup () mixin pour imprimer le contenu de $ global-filter dans le terminal:

 @debug $ global-filter; 

… vous devriez voir quelque chose comme ceci sur la construction:

 DEBUG: "o-box - spacing-small", "o-layout - fit", "c-box - arrondi", "true", "true", "u -flex-middle "," u-align-center "," u-width-grow "," u-width-shrink "

Nous avons maintenant une liste blanche de classe, nous devons l'appliquer à tous les niveaux. différents objets, composants et utilitaires partiels.

Créez un nouveau partiel appelé _filter.scss dans src / scss / tools et ajoutez un @import aux outils Fichier _core.scss de la couche.

Dans cette nouvelle partie, nous allons créer un mixin appelé filter () . Nous utiliserons cela pour appliquer la logique, ce qui signifie que les classes ne seront compilées que si elles sont incluses dans la variable $ global-filter .

A partir de rien, créez un mixin qui accepte un seul paramètre – le . ] $ class contrôlé par le filtre. Ensuite, si la $ classe est incluse dans la liste blanche $ global-filter autorisez sa compilation.

src / scss / tools / _filter.scss
 Filtre @mixin ($ class) {
   @if (index ($ global-filter, $ class)) {
      @contenu;
   }
} 

Dans une partie, nous enroulerions le mixin autour d'une classe optionnelle, comme suit:

 @include filter ('o-myobject - modificateur') {
   .o-myobject - modificateur {
      Couleur jaune;
   }
} 

Cela signifie que la classe .o-myobject - modificateur ne sera compilée que si elle est incluse dans le $ global-filter qui peut être définie directement ou indirectement via ce qui est défini dans $ dépendances .

Parcourez le référentiel et appliquez le mélange filter () à toutes les classes de modificateurs facultatives des couches d'objet et de composant. Lorsque vous manipulez le composant de typographie ou la couche utilitaires, chaque classe étant indépendante de la suivante, il serait logique de les rendre entièrement facultatives, afin que nous puissions alors simplement activer les classes selon nos besoins. [19659003] Voici quelques exemples:

src / scss / objects / _layout.scss
 Filtre @include ('o-layout__item - fit-height') {
    .o-layout__item - fit-height {
        align-self: étirer;
    }
} 
src / scss / utilities / _alignments.scss
 // Modifie l'alignement lorsque le point d'arrêt est atteint
// Exemple: .u-align-left @ md
@each $ bp-name, $ bp-value dans $ mq-breakpoints {
    @include mq (# {$ bp-name}) {
        Filtre @include ('u-align-left @ # {$ bp-name}') {
            .u-align-left  @ # {$ bp-name} {
                text-align: left! important;
            }
        }

        @include filter ('u-align-center @ # {$ bp-name}') {
            .u-align-center  @ # {$ bp-name} {
                text-align: center! important;
            }
        }

        Filtre @include ('u-align-right @ # {$ bp-name}') {
            .u-align-right  @ # {$ bp-name} {
                text-align: right! important;
            }
        }
    }
} 

Note: Lorsque vous ajoutez les noms de classe du suffixe sensible au mixin filter () vous n'avez pas à échapper du symbole '@' avec un ''. 19659003] Au cours de ce processus, tout en appliquant le mixin filter () à des partiels, vous avez peut-être (ou ne pouvez pas) remarqué certaines choses.

Classes groupées

Certaines classes de la base de code sont regroupées. ensemble et partager les mêmes styles, par exemple:

src / scss / objects / _box.scss
 .o-box - spacing-disable-left,
.o-box - spacing-horizontal {
    Rembourrage à gauche: 0;
} 

Comme le filtre n'accepte qu'une seule classe, il ne tient pas compte de la possibilité qu'un bloc de déclaration de style puisse appartenir à plus d'une classe.

Pour en tenir compte, nous étendrons le . filter () mixin, donc en plus d'une classe unique, il est capable d'accepter une arglist SASS contenant de nombreuses classes. Comme ceci:

src / scss / objects / _box.scss
 filtre @include ('o-box - espacement-désactiver-gauche', 'o-box - espacement-horizontal') {
    .o-box - spacing-disable-disable,
    .o-box - spacing-horizontal {
        Rembourrage à gauche: 0;
    }
} 

Nous devons donc indiquer au filtre filter () que si l'une de ces classes figure dans le $ global-filter vous êtes autorisé à les compiler. [19659003] Cela impliquera une logique supplémentaire pour vérifier les arguments de l'argument $ class du mixin, en répondant par une boucle si un argument est passé pour vérifier si chaque élément est dans la variable $ global-filter .

src / scss / tools / _filter.scss
 Filtre @mixin ($ class ...) {
    @if (type-of ($ class) == 'arglist') {
        @each $ item in $ class {
            @if (index ($ global-filter, $ item)) {
                @contenu;
            }
        }
    }
    @else if (index ($ filtre global, $ classe)) {
        @contenu;
    }
} 

Il suffit ensuite de revenir aux partiels suivants pour appliquer correctement le filtre filter () mix::

  • objets / _box.scss
  • objets / _layout.scss
  • utilities / _alignments.scss

À ce stade, revenons à $ imports et n'activez que le composant expandeur. Dans la feuille de style compilée, à côté des styles des couches générique et des éléments, vous ne devriez voir que les éléments suivants:

  • Les classes de bloc et d'élément appartenant au composant expandeur, mais pas son modificateur.
  • Les classes de bloc et d'élément appartenant aux dépendances de l'expandeur.
  • Toute classe de modificateur appartenant aux dépendances de l'expandeur explicitement déclarée dans la variable $ dependencies .

Théoriquement, si vous décidiez d'inclure plus de classes dans la feuille de style compilée , comme le modificateur de composants expander, il suffit de l’ajouter à la variable $ global-filter au point de déclaration, ou de l’ajouter à un autre point de la base de code (tant qu’il est avant le point où le modificateur lui-même est déclaré).

Tout activer

Nous avons donc maintenant un système assez complet, qui vous permet d'importer des objets, des composants et des utilitaires jusqu'à l'individu c lasses dans ces partiels.

Pendant le développement, pour une raison quelconque, vous souhaiterez peut-être tout activer en une fois. Pour permettre cela, nous allons créer une nouvelle variable appelée $ enable-all-classes puis ajouter une logique supplémentaire. Ainsi, si cette propriété est définie sur true, tout est compilé, quel que soit l'état du fichier. $ variables et $ variables globales de filtre .

Premièrement, déclarez la variable dans notre fichier manifeste principal:

src / scss / main.scss
 $ enable-all-classes: false;

@import 'settings/core';
@import 'tools/core';
@import 'generic/core';
@import 'elements/core';
@import 'objects/core';
@import 'components/core';
@import 'utilities/core';

Then we just need to make a few minor edits to our filter() and render() mixins to add some override logic for when the $enable-all-classes variable is set to true.

First up, the filter() mixin. Before any existing checks, we’ll add an @if statement to see if $enable-all-classes is set to true, and if so, render the @contentno questions asked.

src/scss/tools/_filter.scss
@mixin filter($class...) {
    @if($enable-all-classes) {
        @content;
    }
    @else if(type-of($class) == 'arglist') {
        @each $item in $class {
            @if(index($global-filter, $item)) {
                @content;
            }
        }
    }
    @else if(index($global-filter, $class)) {
        @content;
    }
}

Next in the render() mixin, we just need to do a check to see if the $enable-all-classes variable is truthy, and if so, skip any further checks.

src/scss/tools/_render.scss
$layer: null !default;
@mixin render($name) {
    @if($enable-all-classes or index(map-get($imports, $layer), $name)) {
        @content;
    }
}

So now, if you were to set the $enable-all-classes variable to true and rebuild, every optional class would be compiled, saving you quite a bit of time in the process.

Comparisons

To see what type of gains this technique is giving us, let’s run some comparisons and see what the filesize differences are.

To make sure the comparison is a fair one, we ought to add the box and container objects in $importsand then add the box’s o-box--spacing-regular modifier to the $global-filterlike so:

src/scss/settings/_imports.scss
$imports: (
     object: (
        'box',
        'container'
        // 'layout'
     ),
     component: (
        // 'button',
        'expander'
        // 'typography'
     ),
     utility: (
        // 'alignments',
        // 'widths'
     )
)

$global-filter: (
    'o-box--spacing-regular'
);

This makes sure styles for the expander’s parent elements are being compiled like they would be if there were no filtering taking place.

Original vs Filtered Stylesheets

Let’s compare the original stylesheet with all classes compiled, against the filtered stylesheet where only CSS required by the expander component has been compiled.

Standard
StylesheetSize (kb)Size (gzip)
Original54.6kb6.98kb
Filtered15.34kb (72% smaller)4.91kb (29% smaller)

You may think that the gzip percentage savings mean this isn’t worth the effort, as there’s not much difference between the original and filtered stylesheets.

It’s worth highlighting that gzip compression works better with larger and more repetitive files. Because the filtered stylesheet is the only proof-of-concept, and only contains CSS for the expander component, there isn’t as much to compress as there would be in a real-life project.

If we were to scale up each stylesheet by a factor of 10 to sizes more typical of a website’s CSS bundle size, the difference in gzip file sizes are much more impressive.

10x Size
StylesheetSize (kb)Size (gzip)
Original (10x)892.07kb75.70kb
Filtered (10x)209.45kb (77% smaller)19.47kb (74% smaller)

Filtered Stylesheet vs UNCSS

Here’s a comparison between the filtered stylesheet and a stylesheet which has been run through the UNCSS tool.

Filtered vs UNCSS
StylesheetSize (kb)Size (gzip)
Filtered15.34kb4.91kb
UNCSS12.89kb (16% smaller)4.25kb (13% smaller)

The UNCSS tool wins here marginally, as it’s filtering out CSS in the generic and elements directories.

It’s possible that on a real website, with a larger variety of HTML elements in use, the difference between the 2 methods would be negligible.

Wrapping Up

So we’ve seen how — using just SASS — you are able to gain more control over what CSS classes are being compiled on build. This reduces the amount of unused CSS in the final stylesheet and speeds up the critical rendering path.

At the start of the article, I listed some drawbacks of existing solutions such as UNCSS. It’s only fair to critique this SASS-oriented solution in the same way, so all the facts are on the table before you decide which approach is better for you:

Pros

  • No additional dependencies required, so you don’t have to rely on somebody else’s code.
  • Less build time required than Node.js based alternatives, as you don’t have to run headless browsers to audit your code. This is especially useful with continuous integration as you may be less likely to see a queue of builds.
  • Results in similar file size when compared to automated tools.
  • Out of the box, you have complete control over what code is being filtered, regardless of how those CSS classes are used in your code. With Node.js based alternatives, you often have to maintain a separate whitelist so CSS classes belonging to dynamically injected HTML aren’t filtered out.

Cons

  • The SASS-oriented solution is definitely more hands-on, in the sense that you have to keep on top of the $imports and $global-filter variables. Beyond the initial setup, the Node.js alternatives we’ve looked at are largely automated.
  • If you add CSS classes to $global-filter and then later remove them from your HTML, you need to remember to update the variable, otherwise you’ll be compiling CSS you don’t need. With large projects being worked on by multiple devs at any one time, this may not be easy to manage unless you properly plan for it.
  • I wouldn’t recommend bolting this system onto any existing CSS codebase, as you’d have to spend quite a bit of time piecing together dependencies and applying the render() mixin to a LOT of classes. It’s a system much easier to implement with new builds, where you don’t have existing code to contend with.

Hopefully you’ve found this as interesting to read as I’ve found it interesting to put together. If you have any suggestions, ideas to improve this approach, or want to point out some fatal flaw that I’ve missed entirely, be sure to post in the comments below.

Smashing Editorial(dm, yk, il)




Source link