Quelles sont les lunettes de prescription non http://belgiquepharmacie.be/ Unie de liberté de santé oxford

Développeur et intégrateur chez Clever Age, mon quotidien est de poser l’architecture de projets Drupal puis de les concrétiser en développant les modules nécessaires tout en effectuant l’intégration des maquettes directement dans un thème créé sur-mesure.

Afin de gagner en productivité mais aussi pour assurer la pérennité et la maintenance de mes projets d’intégration, j’ai pris l’habitude de fonctionner comme présenté dans ce billet depuis maintenant plus de trois ans. Cette méthodologie est en production depuis autant d’années chez mes clients sans que je n’ai eu à rencontrer de problème. Par ailleurs, j’interviens encore aujourd’hui en TMA sur les premiers sites qui en ont bénéficié.

À noter qu’il n’est pas uniquement question de méthodologie puisque j’en avais profité pour améliorer les performances de chargement des feuilles de styles. Histoire de faire d’une pierre deux coup ;-)

TD;LR

La méthodologie se résume en trois points :

  1. Éliminer les styles parasites de Drupal pour se concentrer sur son intégration.
  2. S’assurer que le fichier de remise à zéro des styles soit chargé en premier.
  3. Privilégier une convention de nom de classes qui permet une distinction sans équivoque avec les éventuelles classes ajoutées par les modules Drupal.

Se concentrer sur son intégration

Il est primordial de s’assurer que notre travail d’intégration, et lui seul, intervienne sur les pages du site. En effet, il est fréquent qu’un module Drupal ajoute sa propre feuille de styles. Styles qui ne suivent aucune convention particulière puisque les développeurs ont toute latitude pour rédiger leurs CSS comme bon leur semble. La conséquence directe est de voir notre intégration être surchargée ou, pire, nous sommes dans l’obligation de surcharger leurs mises en forme de composants de la page, ce qui entraîne de fait un surcoût non négligeable : je vous laisse imaginer le travail si vous avez à annuler les styles introduits par une vingtaine de modules.

C’est pourquoi j’ai le réflexe d’empêcher le chargement des feuilles de styles des modules Drupal, voire celles du core, afin de ne conserver que celles de mon thème et celles qui ont une réelle plus-value lorsque l’utilisateur est identifié sur le site.

Pour ce faire, pour chacune des feuilles de styles à supprimer, il suffit d’ajouter une ligne au format stylesheets[MEDIA][] = kill/MODULE/FILE.css dans le fichier .info du thème (ex : ppm.info), où :

  • MEDIA est la valeur de l’attribut media de la balise HTML <link/> (ex : all) ;
  • MODULE correspond au nom du module original (ex : ctools) ;
  • FILE est le nom de fichier de la feuille de style du module (ex : ctools).

Cela est possible grâce au mécanisme de recherche de feuilles de styles de Drupal. Avant de charger la feuille de styles d’un module (ou du core), le CMS va vérifier s’il n’existe pas un fichier de même nom dans la déclaration des styles du fichier .info du thème. Si correspondance il y a, Drupal va privilégier la déclaration du thème et ignorer la feuille de style originale.
Je parle bien ici de nom de fichier uniquement : le chemin d’accès importe peu, et c’est pourquoi j’utilise ici le chemin kill/MODULE/ :

  • Un répertoire kill/ qui n’existe pas dans mon thème qui m’assure qu’aucune feuille de styles ne sera chargée une fois la déclaration traitée par Drupal.
  • MODULE afin de mémoriser le module ciblé. Malgré le fait qu’il soit conseillé de nommer la feuille de styles d’un module par le nom de ce dernier, il arrive que des modules en déclarent plusieurs, augmentant ainsi le risque de conflit de nom avec des CSS issus d’autres modules. Ce qui aurait pour conséquence de nous faire supprimer une feuille de styles à tord. De plus, préciser le nom du module facilitera la maintenance de notre thème lorsqu’une autre personne reprendra le flambeau.

Et si j’ai besoin des styles introduits par certains modules ? À vrai dire, je préfère les recopier dans mon projet Sass afin de conserver la maitrise totale de mon projet et assurer une intégration cohérente. Cet exercice est rapide car ponctuel et finalement peu fréquent.

Ci-après, un exemple de suppression des feuilles de styles d’un projet. Bien évidemment, charge au développeur de mettre à jour cette liste à chaque ajout de module.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
;--------------------- kill other styles -------------------
;"kill" directory must not exist!
stylesheets[all][] = kill/addessfield/addessfield.css
stylesheets[all][] = kill/ctools/css/ctools.css
stylesheets[all][] = kill/dataTables/demo_table.css
stylesheets[all][] = kill/date/date_api/date.css
stylesheets[all][] = kill/date/date_popup/themes/datepicker.1.7.css
stylesheets[all][] = kill/field/theme/field.css
stylesheets[all][] = kill/field_collection/field_collection.theme.css
stylesheets[all][] = kill/field_group/field_group.css
stylesheets[all][] = kill/jquery_update/replace/ui/jquery.ui.core.css
stylesheets[all][] = kill/jquery_update/replace/ui/jquery.ui.core.min.css
stylesheets[all][] = kill/jquery_update/replace/ui/jquery.ui.tabs.css
stylesheets[all][] = kill/jquery_update/replace/ui/jquery.ui.tabs.min.css
stylesheets[all][] = kill/jquery_update/replace/ui/jquery.ui.theme.css
stylesheets[all][] = kill/jquery_update/replace/ui/jquery.ui.theme.min.css
stylesheets[all][] = kill/lang_dropdown/lang_dropdown.css
stylesheets[all][] = kill/libraries/chosen/chosen.css
stylesheets[all][] = kill/locale/locale.css
stylesheets[all][] = kill/node/node.css
stylesheets[all][] = kill/node_embed/plugins/node_embed/node_embed.css
stylesheets[all][] = kill/search/search.css
stylesheets[all][] = kill/system.theme.css
stylesheets[all][] = kill/system/system.base.css
stylesheets[all][] = kill/system/system.menus.css
stylesheets[all][] = kill/system/system.messages.css
stylesheets[all][] = kill/system/system.theme.css
stylesheets[all][] = kill/tableofcontents/tableofcontents.css
stylesheets[all][] = kill/taxonomy/taxonomy.css
stylesheets[all][] = kill/user.css
stylesheets[all][] = kill/video_filter/video_filter.css
stylesheets[all][] = kill/webform/webform.css
stylesheets[all][] = kill/views/css/views.css

Le projet étant nettoyé de tout code parasite, il est temps de déclarer les feuilles de styles de mon thème, toujours dans le .info :

1
2
3
4
5
;--------------------- styles ------------------------------
stylesheets[all][] = assets/css/fonts.css
stylesheets[all][] = assets/css/reset.css
stylesheets[all][] = assets/css/styles.css
stylesheets[all][] = assets/css/custom.css

Comme vous pouvez l’observer, je déclare trois à quatre feuilles de styles, la dernière étant optionnelle selon les besoins du client :

  • fonts.css : sans équivoque, elle accueille les déclarations des webfonts. Isoler ces déclarations dans un fichier dédié à l’avantage de pouvoir facilement les charges sur une partie du site dépourvue des autres styles, comme sur une landing page.
  • reset.css : mon fichier de remise à zéro des styles du navigateur. Comme je le présenterai dans la prochaine section de l’article, il est important de créer une feuille reset indépendante des autres styles du thème.
  • styles.css : les styles du thème.
  • custom.css : optionnelle, cette feuille de styles est destinée au webmaster qui reprendra le projet. En effet, tout le monde n’est pas à l’aise avec Sass ; je lui laisse ainsi la possibilité d’intervenir sur les styles du site sans pour autant dénaturer le projet Sass (dont les CSS sont livrées compactées). Alternativement, lorsque le webmaster n’a pas accès au serveur, et à la demande du client, je peux leur fournir une interface de contribution de styles personnalisés que je chargerai en toute fin de processus pour assurer une bonne gestion de la cascade CSS.

Un reset bien placé en vaut deux

Je soupire à chaque fois que j’ouvre un thème Drupal et que j’y trouve un fichier de remise à zéro ou de normalisation des styles. Pourquoi ? Parce qu’il n’est qu’à moitié utile. Il y a de fortes chances pour que des éléments de base soient déjà personnalisés par des modules à l’aide d’un sélecteur complexe, rendant inefficace l’action du reset. C’est ainsi qu’apparaisses des régressions sur l’intégration et que le développeur demande à l’intégrateur de revoir sa copie de travail, pourtant correcte.

Pour éviter cette situation je m’assure de charger mon reset avant tout autre style : du CMS, des modules que j’aurai éventuellement conservés et du thème lui-même. Pour ce faire, je m’appuie sur la fonction utilitaire _ppm_utilities_order_css() dont le code est disponible en fin d’article. La fonction va automatiquement remonter le fichier reset.css en haut de la pile d’inclusion des feuilles de styles. D’où l’importance de produire une feuille individuelle de remise à zéro ou de normalisation des styles.

La fonction est à appeler depuis une implémentation du hook_css_alter :

1
2
3
4
5
6
7
8
/**
 * Implements hook_css_alter().
 */
function MYTHEME_css_alter(&$css) {
  if (function_exists('_ppm_utilities_order_css')) {
    _ppm_utilities_order_css($css);
  }
}

Focus sur le projet Sass

Rien de particulier, vous êtes libre de vous organisez comme bon vous semble, en veillant toutefois à produire une feuille reset.css.
Je vous conseille de créer un dossier drupal/ qui accueillera vos propres implémentations des feuilles de styles supprimées dans le .info du thème. Typiquement il s’agit des feuilles de styles drupal/system/_base.scss (= system.base.css), drupal/system/_menus.scss (= system.menus.css) et drupal/system/_messages.scss (= system.messages.css).

Il est par exemple intéressant d’implémenter son propre fichier drupal/system/_menus.scss pour donner un style cohérent avec le thème aux onglets d’actions sur les nodes présentés en page (liens Voir, Modifier, Cloner, Log, etc, affichés en début du contenu). Ça se passe du côté du sélecteur ul.primary (que l’on ne rencontre qu’ici généralement).

Impacts sur l’équipe du projet

Grâce à cette méthode, le travail en équipe intégrateur / développeur est simplifié :

  • L’intégrateur a la certitude que son fichier de reset sera inclus comme il se doit dans le projet Drupal.
  • L’intégrateur sait que son travail ne sera pas pollué par de tierces feuilles de styles et n’aura pas à faire d’incessants aller-retours pour adapter son travail une fois intégré dans un thème Drupal.
  • Le développeur n’a plus à s’enquiquiner à supprimer les classes CSS injectées par Drupal et les modules du projet. En effet, puisque l’intégrateur suit de bonnes pratiques, les classes CSS du thème seront préfixées par un nom d’organisation (ex : ca- pour Clever Age) ce qui assure qu’aucun autre composant ne soit stylisé. Qu’importe si un module ajoute la classe .content à un élément puisque la classe utilisée dans le thème se nomme .ca-content. Puis, de toute manière, puisque le développeur aura inhibé le chargement des feuilles de styles des modules concernés, il n’y aura aucun effet de bord ne sera à craindre.

Place à l’optimisation

J’introduisais dans l’article que j’avais profité de l’écriture de la fonction _ppm_utilities_order_css() pour améliorer les performances de chargement des feuilles de styles de mes projets Drupal. Avant d’aller plus loin, il est important de revenir sur la manière dont Drupal 7 manipule les feuilles de styles.

État des lieux

Dans ses options de performance, le CMS met à disposition un système de concaténation des feuilles de styles afin de réduire le nombre de fichiers à charger par le navigateur. Avec la multitude de modules installés sur un projet, il est en effet monnaie courante d’obtenir une vingtaine de feuilles de styles. Il est de fait indéniable que réduire ce nombre de ressources aura un impact positif sur les performances du site.

Cependant, contre toute attente, le système ne les regroupe pas en un unique fichier. En effet, l’agrégation est divisée en trois groupes :

  • CSS_SYSTEM pour les fichiers du core et pour toute feuille de styles déclarée dans ce groupe via une implémentation du hook_library ou un appel à drupal_add_js().
  • CSS_DEFAULT pour les feuilles de styles des modules.
  • CSS_THEME pour les feuilles de styles du thème.

En plus de ces trois groupes, l’agrégation distingue le type de média (all, screen, print, etc) des feuilles de styles (une CSS print ne doit pas être regroupée avec une CSS screen) et permet également de regrouper les feuilles de styles selon qu’elles doivent être chargées sur toutes les pages ou non, via l’option every_page.

Prenons le résultat typique du résultat du système d’agrégation de Drupal 7 qui suit :

1
2
3
4
5
<link type="text/css" rel="stylesheet" href="/sites/default/files/css/css_Ojoe8PXEJuPXLULSuHGvjE6Cnnkwxb_CG3OxB2XGpY4_238_1.css" media="all"/>
<link type="text/css" rel="stylesheet" href="/sites/default/files/css/css_PlzT_JgUc-YTydWkzrA_Qw96Yn2l3HNiVHExFd7wH-o_238_1.css" media="all"/>
<link type="text/css" rel="stylesheet" href="/sites/default/files/css/css_6VP-mhf3bxYSQCaSuqwoH3nW_yr1xOa7tXmTWzYNoGA_238_1.css" media="all"/>
<link type="text/css" rel="stylesheet" href="/sites/default/files/css/css_90_3bu-0YN-gByu9BAO_J6ujFhixop5zEOImQ9yhTEI_238_1.css" media="all"/>
<link type="text/css" rel="stylesheet" href="/sites/default/files/css/css_s0_PKpDxuvYaAmTQmzv2vcMt5-0NvP0D0USZaqxDXvY_238_1.css" media="print"/>

Dans l’extrait précédent :

  1. La première balise link charge les styles CSS qui appartiennent au groupe CSS_SYSTEM.
  2. La deuxième inclusion regroupe les feuilles de styles de type CSS_DEFAULT, avec la propriété every_page à true.
  3. La suivante regroupe elle aussi les feuilles de styles de type CSS_DEFAULT dont la propriété every_page vaut false.
  4. La dernière CSS dédiée au type de média all contient finalement les feuilles de styles déclarées au niveau du thème, définies à l’aide de la constante CSS_THEME.
  5. Enfin, la dernière prend en charge les CSS de l’impression.

Le nombre de feuilles de styles est bien réduit : le navigateur n’a plus qu’une poignée de fichiers à charger au lieu de la vingtaine habituelle. C’est bien ! Mais tout n’est pas rose pour autant.

Réduire le nombre de fichiers CSS générés

Nous pouvons lire de-ci, de-là, que cette atomicité est une bonne chose pour réduire l’usage de bande passante. Mais Drupal 7 pouvant ainsi générer jusqu’à six fichiers pour un même type de média (deux par groupe suivant la valeur de l’option every_page) cette méthode devient contre-performante.

Bien que les navigateurs modernes aient augmenté le nombre de connexions persistantes et supportent le pipelining HTTP, celui-ci est généralement limité :

Navigateur Connexions par domaine
Chrome 6
Firefox 6
IE 6
IE Mobile 6
Nintendo 3DS 7
PlayStation 4
Safari 6
Wii 2

De fait, cette atomicité va généralement occuper la totalité d’un pipeline au chargement des CSS, là où généralement le navigateur aurait pu charger d’autres ressources en sus, telles que des images ou des fichiers JavaScript.

Effet encore plus rotor, les feuilles de styles qui sont incluses uniquement sur certaines pages (every_page = false) provoquent une nouvelle agrégation du groupe auxquelles elles appartiennent lorsque les pages concernées sont visitées. Cela afin de servir au visiteur une version concaténée des fichiers du groupe et des styles spécifiques. Au final, en passant d’une page à une autre, le serveur devra calculer les nouveaux styles ralentissant d’autant son temps de réponse et le navigateur sera forcé de télécharger des dizaines si ce n’est des centaines de kilo-octets de CSS au lieu d’exploiter son cache, puisque le fichier résultat de la nouvelle agrégation sera pourvue d’un nouveau nom.

Pour ceux d’entre-vous qui se pencheront sur le code de la fonction _ppm_utilities_order_css(), vous constaterez que je force la valeur true à l’option every_page afin de réduire le nombre de feuilles de styles générées et pour garantir un minimum de différences entre deux types de pages. Bien évidemment, une intégration modulaire facilite la personnalisation d’un type de page donné.

Certes, les fichiers qui en résultent seront légèrement plus lourds que d’habitude puisqu’ils incluent l’ensemble des CSS du site (par média), mais sans pour autant pénaliser les utilisateurs. Au contraire, la navigation sur le site sera perçue comme plus rapide.

Une fonction utilitaire pour optimiser son intégration

Pour finir, voici le code de la fonction _ppm_utilities_order_css() . N’hésitez pas à me faire un retour si vous rencontrez des difficultés à l’utiliser ou si vous rencontrez un bug !

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/**
 * Promote the theme stylesheets.
 *
 * Implements hook_css_alter().
 *
 * @param array &$css
 *   An array of all CSS items (files and inline CSS) being requested on the page.
 * @param array $options
 *   (optional) An associative array containing:
 *     - replace: (optional) an associative array of media type from => to.
 *     - reset: (optional) an array list of reset files to be moved at the
 *       beginning of output stack. Default to `array('reset')`.
 *     - unset_dir: (optional) A directory name used in the `.info` file to
 *       remove CSS inserted by the core and modules. Default to `'kill'`.
 *   Example usage:
 *   @code
 *     _ppm_utilities_order_css($css, array(
 *       'replace' => array('all' => 'screen, projection'),
 *       'reset'   => array('reset', 'reset-custom')
 *     ));
 *   @endcode
 *
 *  @link http://pioupioum.fr/
 */
function _ppm_utilities_order_css(&$css, $options = array()) {
  $theme    = array();
  $stack    = array();
  $defaults = array(
    'replace'   => array(),
    'reset'     => array('reset'),
    'unset_dir' => 'kill',
  );
  $options += $defaults;
 
  // Checks all reset CSS files and adds right to left should be included
  // by the module Locale in {@link locale_css_alter()}.
  if (count($options['reset'])) {
    $reset_css = $options['reset'];
    array_walk($reset_css, function ($name) use (&$options) {
      if (FALSE !== strpos($name, '-rtl')) {
        $options['reset'][] = $name . '-rtl';
      }
    });
    unset($reset_css);
  }
 
  /**
   * Cleanup media type.
   *
   * @param string $media
   *   The input media type to clean.
   *
   * @return string
   */
  $sanitize_media = function ($media) {
    if (FALSE !== strpos($media, ',')) {
      $media = explode(',', $media);
      array_walk($media, function (&$v) { $v = strtolower(trim($v)); });
      natsort($media);
      $media = implode(',', $media);
    }
 
    return $media;
  };
 
  // Why consume CPU cycles to process stylesheets that we do not want?
  // Let's remove them from the stack maintained by Drupal.
  $killed = array();
  $css = array_filter($css, function ($stylesheet) use (&$killed, $options) {
    if (FALSE !== strpos($stylesheet['data'], '/' . $options['unset_dir'] . '/')) {
      $killed[] = basename($stylesheet['data']);
      return FALSE;
    }
    return TRUE;
  });
  $css = array_filter($css, function ($stylesheet) use ($killed) {
    return !in_array(basename($stylesheet['data']), $killed);
  });
  unset($killed, $stylesheet);
 
  // Flattening substitutions of media types.
  if (count($options['replace'])) {
    $from = array_map($sanitize_media, array_keys($options['replace']));
    $to   = array_map($sanitize_media, array_values($options['replace']));
    $options['replace'] = array_combine($from, $to);
    unset($from, $to);
  }
 
  foreach ($css as $delta => $stylesheet) {
    // Never deal with external resources.
    if ('external' === $stylesheet['type']) {
      $stack[$delta] = $stylesheet;
      continue;
    }
 
    // We force the loading of CSS on every page to limit the number
    // of aggregate files.
    if (!$stylesheet['every_page']) {
      $stylesheet['every_page'] = TRUE;
    }
 
    // Standardize media types and make substitutions if necessary.
    $media = $sanitize_media($stylesheet['media']);
    if (isset($options['replace'][$media])) {
      $media = $options['replace'][$media];
    }
    $stylesheet['media'] = $media;
 
    // The stylesheet has been declared by the theme, we store the key
    // to increase their weight at the end of the process.
    if (CSS_THEME === $stylesheet['group']) {
      $theme[$delta] = $stylesheet;
    }
    // Otherwise, it is a stylesheet added by the core or a module, we move it
    // in the theme group to group 'theme' into a single file after aggregation.
    else {
      $stylesheet['group'] = CSS_THEME;
      $stack[$delta]       = $stylesheet;
    }
  }
 
  // We force the loading of CSS declared by the theme to the end of the stack.
  $i = 0;
  if (!empty($stack)) {
    foreach (array_keys($stack) as $delta) {
      $stack[$delta]['weight'] = (++$i) / 1000;
    }
    $i = $stack[$delta]['weight'];
  }
 
  foreach (array_keys($theme) as $delta) {
    // If it exists, we ensure that the reset stylesheets is loaded first.
    if (in_array(basename($delta, '.css'), $options['reset'])) {
      $theme[$delta]['weight'] = -2 + $theme[$delta]['weight'];
    }
    else {
      $i += 0.001;
      $theme[$delta]['weight'] = $i;
    }
  }
 
  $css = array_merge($stack, $theme);
}

Voir sur Gist.

publicité (chargement)

Ajouter un commentaire


Syndication

Réseaux sociaux