Ajout de feuilles de style (CSS) et de JavaScript (JS) dans un thème Drupal 8
Cette documentation est destinée aux thèmes. Pour les informations concernant les modules, voir la section Ajouter des feuilles de style (CSS) et JavaScript (JS) dans un module Drupal 8.
Dans Drupal 8, les feuilles de style (CSS) et JavaScript (JS) sont chargées via le même système pour les modules (code) et les thèmes, appelé : bibliothèques de ressources.
Pour être clair, ces instructions sont destinées UNIQUEMENT à une utilisation dans les thèmes et ne s’appliquent pas aux modules.
Drupal utilise un principe de haut niveau : les ressources (CSS ou JS) ne sont chargées que si vous dites à Drupal de les charger. Drupal ne charge pas toutes les ressources sur chaque page car cela réduirait les performances de l’interface.
Différences avec Drupal 7
Voici six différences importantes par rapport à Drupal 7 pour les amateurs :
- Le fichier THEME.info.yml a remplacé le fichier THEME.info (avec les mêmes données).
- La propriété stylesheets (pour ajouter du CSS) dans THEME.info a été supprimée et remplacée par *.libraries.yml, où * est le nom du thème ou du module.
- La propriété scripts (pour ajouter du JS) dans THEME.info a également été supprimée et remplacée par *.libraries.yml, où * est le nom du thème ou du module.
- Seuls le CSS et le JS nécessaires sont chargés sur la page. Par exemple, jQuery n’est plus chargé automatiquement sauf si explicitement déclaré dans *.libraries.yml. Si votre thème a besoin de jQuery ou d’autres ressources à charger sur toutes les pages, ajoutez-les dans *.libraries.yml puis incluez la bibliothèque dans THEME.info.yml.
- Dans Drupal 7, les bibliothèques étaient définies via hook_library_info(). Cela a été remplacé par le fichier *.libraries.yml.
- Dans Drupal 8, drupal_add_css(), drupal_add_js() et drupal_add_library() ont été supprimés au profit de #attached
Processus
Pour charger des ressources CSS ou JS :
- Enregistrez le CSS ou JS dans un fichier, en suivant les conventions de nommage et organisation des fichiers.
- Définissez une « bibliothèque » qui enregistre ces fichiers CSS/JS dans votre thème.
- « Attachez » la bibliothèque à toutes les pages, à certains templates Twig ou à certaines pages via un élément de rendu dans une fonction de prétraitement.
Définition d’une bibliothèque
Définissez toutes vos bibliothèques de ressources dans un fichier *.libraries.yml dans le dossier de votre thème. Si votre thème s’appelle fluffiness, le fichier doit s’appeler fluffiness.libraries.yml. Chaque « bibliothèque » dans ce fichier détaille les fichiers CSS et JS (ressources), par exemple :
# fluffiness.libraries.yml cuddly-slider: version: 1.x css: theme: css/cuddly-slider.css: {} js: js/cuddly-slider.js: {}
Dans cet exemple, les fichiers JavaScript cuddly-slider.js et CSS cuddly-slider.css se trouvent dans les dossiers js et css respectivement, dans votre répertoire de thème.
Notez que même si cet exemple montre l’ajout d’un seul fichier CSS et JS plus jQuery, la définition des bibliothèques offre beaucoup plus d’options, que vous pouvez consulter dans la section Définition des bibliothèques : options et détails.
Inclusion de jQuery dans votre bibliothèque
Sachez que Drupal 8 ne charge plus jQuery automatiquement sur toutes les pages, donc, par exemple, si cuddly-slider a besoin de jQuery, vous devez déclarer une dépendance à la bibliothèque de base qui contient jQuery (fournie par le core Drupal, pas par un module ou thème). Déclarez la dépendance sous la forme core/jquery. Si une autre bibliothèque dépend de cuddly-slider, elle déclarera fluffiness/cuddly-slider, où fluffiness est le nom du thème et cuddly-slider le nom de la bibliothèque. Vous ne pouvez pas déclarer un fichier isolé comme dépendance, uniquement une bibliothèque.
Ainsi, pour rendre jQuery disponible à cuddly-slider, on met à jour l’exemple précédent :
# fluffiness.libraries.yml cuddly-slider: version: 1.x css: theme: css/cuddly-slider.css: {} js: js/cuddly-slider.js: {} dependencies: - core/jquery
Déclaration des dépendances
Pour déclarer une dépendance, la bibliothèque nécessaire est mentionnée sous la forme ressource/bibliothèque. Pour les bibliothèques du core, la ressource est core, pour les autres c’est le nom du module ou du thème. Ainsi, si new_library dépend de jQuery du core, et de my_library défini dans my_theme et my_module, les dépendances doivent être :
# fluffiness.libraries.yml new_library: js: js/new_libary.js: {} dependencies: - core/jquery - my_module/my_library - my_theme/my_library
Les noms des modules et thèmes assurent un espace de noms pour les bibliothèques portant le même nom.
Attacher une bibliothèque à toutes les pages
La plupart des thèmes utilisent une bibliothèque globale global-styling pour les styles CSS qui doivent être chargés sur chaque page où le thème est actif. Il est aussi possible de faire de même avec le JS via une bibliothèque global-scripts :
# fluffiness.libraries.yml (plusieurs bibliothèques peuvent être ajoutées dans ce fichier, en plus de cuddly-slider) global-styling: version: 1.x css: theme: css/layout.css: {} css/style.css: {} css/colors.css: {} global-scripts: version: 1.x js: js/navmenu.js: {}
Pour qu’elles soient disponibles globalement dans le thème, les bibliothèques global-styling/global-scripts doivent être ajoutées dans le fichier info.yml de votre thème (ici fluffiness.info.yml)
# fluffiness.info.yml name: Fluffiness type: theme description: 'Un thème tout doux qui offre un supplément de moelleux.' core: 8.x # en ajoutant global-styling et global-scripts ici, les fichiers css/js de la bibliothèque deviennent # disponibles sur chaque page affichée par le thème libraries: - fluffiness/global-styling - fluffiness/global-scripts base theme: classy regions: header: Entête content: Contenu sidebar_first: 'Première barre latérale' footer: Pied de page
Attacher une bibliothèque via un template Twig
Vous pouvez attacher une bibliothèque de ressources dans un template Twig en utilisant la fonction attach_library() dans n’importe quel fichier *.html.twig, comme ceci :
{{ attach_library('fluffiness/cuddly-slider') }} <div>Un peu de contenu tout doux {{ message }}</div>
Attacher une bibliothèque à un sous-ensemble de pages
Parfois, vous ne voulez pas que votre bibliothèque soit active sur toutes les pages, mais seulement sur un sous-ensemble, par exemple quand un certain bloc est affiché ou un certain type de contenu.
Le thème peut faire cela en implémentant une fonction THEME_preprocess_HOOK() dans le fichier .theme, où THEME est le nom machine de votre thème, et HOOK le nom machine du hook de thème.
Par exemple, pour attacher un JavaScript à la page de maintenance, HOOK est « maintenance_page », la fonction ressemblera à :
function fluffiness_preprocess_maintenance_page(&$variables) { $variables['#attached']['library'][] = 'fluffiness/cuddly-slider'; }
Vous pouvez faire pareil pour d’autres hooks de thème, et bien sûr votre fonction peut contenir une logique, par exemple pour déterminer quel bloc est pré-traité dans le hook « block », quel type de nœud pour le hook « node », etc.
Note importante ! Dans ce cas, vous devez indiquer les métadonnées de cache correspondant à votre état ! L’exemple ci-dessus fonctionne sans ambiguïté, donc pas besoin de métadonnées de cache. Le cas d’usage le plus fréquent est d’attacher une bibliothèque selon la route courante :
function fluffiness_preprocess_page(&$variables) { $variables['page']['#cache']['contexts'][] = 'route'; $route = "entity.node.preview"; if (\Drupal::routeMatch()->getRouteName() === $route) { $variables['#attached']['library'][] = 'fluffiness/node-preview'; } }
Définition des bibliothèques : options et détails
Ajout de propriétés aux fichiers CSS/JS inclus
Les propriétés sont ajoutées entre accolades après chaque fichier ajouté dans le fichier THEMENAME.libraries.yml de votre thème.
Propriétés CSS
Les propriétés suivantes sont optionnelles et s’appliquent à chaque ressource CSS.
attributes | Attributs optionnels. Exemple d’utilisation connu : Bootstrap CDN. |
{ attributes: { crossorigin: anonymous } } |
browsers | Charge la ressource conditionnellement selon le navigateur. Notez que cette méthode utilise des commentaires conditionnels, non supportés dans IE10 et versions supérieures. |
{ browsers: { IE: 'lte IE 9', '!IE': false } } |
group | Les ressources sont agrégées par groupes. Par défaut : groupe SMACSS auquel la ressource appartient. |
Rarement utilisé |
media | Type de média. |
{ media: print } |
minified | Indique si la ressource est déjà minifiée. Par défaut : false |
{ type: external, minified: true } |
preprocess | Indique si la ressource doit être agrégée. Par défaut : true |
{ preprocess: false } |
type | Source de la ressource. Par défaut : fichier |
{ type: external, minified: true } |
weight | Ajuste l’ordre par rapport aux autres ressources (dans le même groupe SMACSS). Par défaut : 0. Utilisez une valeur numérique entre -50 et +50. |
{ weight: 1 } |
Propriétés JS
Les propriétés suivantes sont optionnelles et s’appliquent à chaque ressource JS.
attributes | Attributs additionnels pour la balise script. |
{ type: external, attributes: { async: true } } |
browsers | Charge la ressource conditionnellement selon le navigateur. Notez que cette méthode utilise des commentaires conditionnels, non supportés dans IE10 et versions supérieures. |
{ browsers: { IE: 'lte IE 9', '!IE': false } } |
preprocess | Indique si la ressource doit être agrégée. Par défaut : true |
{ preprocess: false } |
type | Source de la ressource. Par défaut : fichier |
{ type: external, minified: true } |
weight | Non recommandé, préférez les dépendances. Ajuste l’ordre par rapport aux autres ressources. Doit être négatif. |
{ weight: -1 } |
Remplacement et extension des bibliothèques
Pour remplacer des bibliothèques définies dans *.libraries.yml, vous devez passer par *.info.yml. Elles peuvent être soit remplacées, soit étendues via des remplacements ou extensions de bibliothèques. Les remplacements ajoutés dans *.info.yml seront hérités par les sous-thèmes.
La propriété stylesheets-remove, utilisée dans les fichiers *.info.yml, est obsolète et sera supprimée dans Drupal 9.0.x. La propriété stylesheets-override est déjà supprimée.
libraries-override
Voici la logique à utiliser pour créer des remplacements :
- Utilisez l’espace de noms original du module (ou du core) pour le nom de la bibliothèque.
- Utilisez le chemin du dernier remplacement comme clé.
- Ce chemin doit être le chemin complet vers le fichier.
Par exemple :
libraries-override: contextual/drupal.contextual-links: css: component: /core/themes/stable/css/contextual/contextual.module.css: false
Ici, contextual/drupal.contextual-links est l’espace de noms de la bibliothèque de base, et /core/themes/stable/css/contextual/contextual.module.css est le chemin complet vers le dernier remplacement, désactivé ici (false).
Notez que seule la dernière partie est un chemin de fichier réel, les autres sont des espaces de noms. Les lignes css: et component: reflètent la structure de la bibliothèque à remplacer.
Gardez à l’esprit que dépendre d’un chemin complet peut casser si la structure du site change. C’est pourquoi il existe une problématique pour supprimer la dépendance au chemin complet via les packagers stream.
Voici quelques exemples d’utilisation de libraries-override pour retirer ou remplacer des ressources CSS ou JS, ou des bibliothèques entières héritées par votre thème depuis des modules ou thèmes :
libraries-override: # Remplacer une bibliothèque entière. core/drupal.collapse: mytheme/collapse # Remplacer une ressource par une autre. subtheme/library: css: theme: css/layout.css: css/my-layout.css # Remplacer un fichier de remplacement depuis stable. contextual/drupal.contextual-toolbar: css: component: core/themes/stable/css/contextual/contextual.toolbar.css: css/contextual.toolbar.css # Remplacer un fichier JavaScript d’un module core. toolbar/toolbar: js: js/views/BodyVisualView.js: js/views/BodyVisualView.js # Supprimer une ressource. drupal/dialog: css: theme: dialog.theme.css: false # Supprimer une bibliothèque entière. core/modernizr: false # Remplacer des ressources très spécifiques dans une bibliothèque d’un module contrib. # Note : Les bibliothèques des modules contrib disponibles pour remplacement sont dans *.libraries.yml du module. Exemple : /modules/contrib/webform/webform.libraries.yml webform/webform.element.location.places: css: component: css/webform.element.location.places.css: css/my-themes-replacement-file.css js: js/webform.element.location.places.js: js/my-themes-replacement-file.js
libraries-extend
libraries-extend permet aux thèmes de modifier les ressources d’une bibliothèque en ajoutant des dépendances supplémentaires spécifiques au thème chaque fois que cette bibliothèque est chargée.
libraries-extend est défini en étendant une bibliothèque par un ou plusieurs autres.
Idéal pour styliser certains composants différemment dans votre thème sans ajouter du CSS global. Ainsi, vous pouvez modifier l’apparence d’un composant sans charger du CSS sur chaque page.
# Étendre drupal.user : ajouter des assets des bibliothèques user de classy. libraries-extend: core/drupal.user: - classy/user1 - classy/user2
Personnalisation supplémentaire du JavaScript
Ordre de chargement des ressources
Comme attendu, l’ordre dans lequel les fichiers sont listés est l’ordre de leur chargement. Par défaut, toutes les ressources JS sont chargées dans le pied de page. Les JS critiques nécessaires à l’interface utilisateur peuvent être chargés dans l’en-tête si besoin, ainsi :
js-header: header: true js: header.js: {} js-footer: js: footer.js: {}
Définissez la propriété header à true pour indiquer que le JavaScript de cette bibliothèque est dans le « chemin critique » et doit être chargé dans l’en-tête. Toutes les dépendances directes ou indirectes seront aussi chargées automatiquement dans l’en-tête sans devoir les déclarer individuellement. C’est le sens de « chemin critique » : la ressource et ses dépendances doivent être chargées en premier.
Attacher un JavaScript personnalisé :
Parfois, vous voulez ajouter du JavaScript à une page qui dépend d’informations PHP calculées.
Dans ce cas, créez un fichier JavaScript, définissez et attachez la bibliothèque comme d’habitude, puis attachez aussi des réglages JavaScript via drupalSettings (successeur de Drupal 7 Drupal.settings). Pour que drupalSettings soit accessible dans votre JS, vous devez aussi déclarer sa dépendance.
Par exemple :
cuddly-slider: version: 1.x js: js/cuddly-slider.js: {} dependencies: - core/jquery - core/drupalSettings
et
function fluffiness_page_attachments_alter(&$page) { $page['#attached']['library'][] = 'fluffiness/cuddly-slider'; $page['#attached']['drupalSettings']['fluffiness']['cuddlySlider']['foo'] = 'bar'; }
où 'bar' est une valeur calculée. (Notez que les métadonnées de cache sont aussi nécessaires !)
Le fichier cuddly-slider.js pourra alors accéder à settings.fluffiness.cuddlySlider.foo (qui vaudra 'bar') :
(function ($, Drupal, drupalSettings) { 'use strict'; Drupal.behaviors.mybehavior = { attach: function (context, settings) { console.log(settings.fluffiness.cuddlySlider.foo); } }; })(jQuery, Drupal, drupalSettings);
Ajout d’attributs aux éléments script
Pour ajouter des attributs à une balise script, ajoutez une clé attributes dans l’objet JSON après l’URL du script. À l’intérieur, mettez le nom de l’attribut comme clé, et sa valeur comme valeur. Si la valeur est true, l’attribut sera affiché sans valeur.
Par exemple :
https://maps.googleapis.com/maps/api/js?key=myownapikey&signed_in=true&libraries=drawing&callback=initMap: type: external attributes: defer: true async: true data-test: map-link
Cela génère :
<script src="https://maps.googleapis.com/maps/api/js?key=myownapikey&signed_in=true&libraries=drawing&callback=initMap" async defer data-test="map-link"></script>
JavaScript inline
Le JavaScript inline est fortement déconseillé. Il est recommandé de placer le JavaScript dans un fichier JS pour permettre la mise en cache côté client. Cela permet aussi que le code soit lisible et modifiable.
JavaScript inline qui génère du markup
C’est déconseillé et généralement inutile. Mettez ce JavaScript dans un fichier. Exemples : publicités, boutons de partage social, widgets sociaux. Ce sont juste des types de contenu/markup qui ne servent pas à décorer le contenu ou à l’interactivité du site, mais à extraire du contenu externe via JavaScript.
Vous pouvez les placer dans un bloc personnalisé ou directement dans un template Twig.
Exemple :
<script type="text/javascript"><!-- ad_client_id = "some identifier" ad_width = 160; ad_height = 90; //--></script> <script type="text/javascript" src="http://adserver.com/ad.js"></script> <a class="twitter-timeline" href="https://twitter.com/wimleers" data-widget-id="307116909013368833">Tweets by @wimleers</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
JavaScript inline affectant toute la page
L’utilisation de JavaScript inline est fortement déconseillée. Exemples : analytics (Google Analytics), services de polices hébergées. Le JavaScript inline affectant toute la page appartient soit au front-end/style, soit à la logique.
Dans le cas du front-end/style (ex. : polices hébergées), le JS appartient au thème. Placez-le directement dans votre fichier html.html.twig. Cela permet aussi de bien positionner le JS pour éviter le FOUT (Flash Of Unstyled Text) pendant le chargement des polices (les polices chargées via JS doivent être listées dans le <HEAD> avant le CSS).
(Pour plus d’infos, voir l’excellent article « Async Typekit & Micro-FOUT ».)
Dans l’autre cas, le JS appartient au module, et pour cela, voir la section Ajouter des feuilles de style (CSS) et JavaScript (JS) dans un module Drupal 8.
JavaScript inline dans un module d’intégration
Le JavaScript inline est fortement déconseillé. Si vous pouvez utiliser l’un des exemples ci-dessus, préférez-les.
Deux points à considérer pour un champ acceptant du JavaScript inline fourni par un utilisateur :
1. Le champ, formulaire ou page acceptant ce JavaScript doit avoir la permission correspondante.
Exemple : MODULE.routing.yml
MODULE.settings: path: /admin/config/services/MODULE defaults: _title: 'Paramètres MODULE' _form: \Drupal\MODULE\Form\MODULESettings requirements: _permission: 'administer site configuration'
2. La valeur stockée dans un objet de configuration doit notifier le système de rendu de ses métadonnées CacheableMetadata, afin que le cache soit vidé/invalidé lors d’un changement.
Exemple : MODULE.module
<?php /** * @file * Intègre MODULE dans un site Drupal. */ use Drupal\Core\Render\Markup; /** * Implémente hook_page_bottom(). */ function MODULE_page_bottom(array &$page_bottom) { $settings = \Drupal::config('MODULE.settings'); $user = \Drupal::currentUser(); $page_bottom['MODULE'] = [ '#markup' => Markup::create($settings->get('js_code')), '#cache' => [ 'contexts' => ['user'], 'tags' => ['user:' . $user->id()], ], ]; // Ajout des métadonnées de cache pour les settings de config. /** @var Drupal\Core\Render\Renderer $renderer */ $renderer = \Drupal::service('renderer'); $renderer->addCacheableDependency($page_bottom['MODULE'], $settings); }
CDN / bibliothèques externes
Vous pouvez vouloir utiliser du JavaScript hébergé sur un CDN (Content Delivery Network), par exemple les polices web sont souvent accessibles uniquement via une URL externe. Ceci se fait en déclarant une bibliothèque externe (avec type: external). Il est aussi utile d’ajouter des informations sur la bibliothèque externe.
(Notez que charger des bibliothèques via CDN n’est pas une bonne pratique en général ; cela crée des points de défaillance supplémentaires, affecte la performance et la stabilité, nécessite plus de connexions TCP/IP et ne profite pas au cache du navigateur. De plus, les bibliothèques tierces ne doivent pas être hébergées sur Drupal.org comme partie de votre dépôt — voir la politique des bibliothèques tierces sur Drupal.org pour plus de détails.)
angular.angularjs: remote: https://github.com/angular version: 1.4.4 license: name: MIT url: https://github.com/angular/angular.js/blob/master/LICENSE gpl-compatible: true js: https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js: { type: external, minified: true }
Si vous souhaitez que votre fichier externe soit appelé avec le même protocole que la page, utilisez une URL relative au protocole :
js: //ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js: { type: external, minified: true }
Ou, pour ajouter du CSS, voici un exemple d’intégration de Font Awesome :
font-awesome: remote: https://fortawesome.github.io/Font-Awesome/ version: 4.5.0 license: name: MIT url: https://fortawesome.github.io/Font-Awesome/license/ gpl-compatible: true css: theme: https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css: { type: external, minified: true }
Exemple pour Bootstrap CDN CSS avec attributs personnalisés.
bootstrap-cdn: remote: getbootstrap.com version: 4.0 license: name: MIT url: https://github.com/twbs/bootstrap/blob/master/LICENSE css: theme: 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css': type: external minified: true attributes: crossorigin: anonymous integrity: "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"