Utiliser Assetic pour gérer les codes CSS et JS de votre site

La gestion des ressources CSS et JavaScript dans un site internet est très importante et n'est pas si évidente ! Leur chargement est très souvent le point le plus lent pour l'affichage de la page à vos visiteurs, ce n'est donc pas quelque chose à négliger.

Pour vous aider à gérer ces ressources efficacement, Symfony2 intègre un bundle nommé Assetic qui va s'occuper de tout cela à votre place. Il va vous permettre d'optimiser au maximum le chargement de ces ressources pour vos visiteurs. Vous verrez, ce bundle est presque magique !

Théorie : entre vitesse et lisibilité, pourquoi choisir ?

À propos du nombre de requêtes HTTP d'une page web

Vous devez sûrement vous demander ce que vient faire la vitesse d'une page dans une section qui traite de code CSS. C'est une bonne question et je vais y répondre. Le temps de chargement ressenti d'une page par un visiteur comprend tout le processus du clic au rendu de la page par le navigateur. Ainsi, on y inclut :

  • Le temps d'envoi de la requête au serveur lors du clic. On ne peut pas y faire grand-chose, malheureusement.
  • Le temps d'exécution de la page côté serveur, le temps PHP, donc. Pour cela, il faut bien penser son script et essayer de l'optimiser un peu.
  • Le temps d'envoi du code HTML par le serveur vers le navigateur. On ne peut pas y faire grand-chose non plus.
  • Mais là ce n'est pas tout : à partir de cette page HTML que le navigateur reçoit, ce dernier doit tout recommencer pour chaque fichier CSS, chaque JavaScript et chaque image !

Donc si votre page contient 5 fichiers CSS, 3 JavaScript et 15 images, cela fait un total de 23 requêtes HTTP à traiter par votre navigateur pour vous afficher l'intégralité de la page ! Et pour ces 23 requêtes, il y a les temps d'envoi et de réception qui sont incompressibles et qui prennent du temps.

Au final, s'il faut bien sûr optimiser le code PHP côté serveur, la partie front-end qui comprend codes HTML, CSS et JavaScript ainsi que les fichiers images est bien celle qui prend le plus de temps à se charger, vu du visiteur.

Comment optimiser le front-end ?

L'idée est de réduire les temps incompressibles. Comme ils sont justement incompressibles, il faut que l'on en diminue le nombre. La seule solution est donc de grouper ces fichiers. L'idée est que, au lieu d'avoir cinq fichiers CSS différents, on va mettre tout notre code CSS dans un seul fichier. Comme cela, on aura une seule requête au lieu de cinq. Super !

Mais le problème, c'est que si l'on avait trois fichiers et non un seul, ce n'était pas pour rien. Chaque fichier concernait une partie de votre site, c'était bien plus lisible. Tout regrouper vous gênerait dans le développement de vos fichiers CSS (idem pour les fichiers JavaScript, bien sûr).

C'est là qu'Assetic va intervenir : il va grouper lui-même les fichiers et va vous permettre de garder votre séparation !

Il est aussi possible d'améliorer le temps de chargement !

En effet, transmettre votre unique fichier CSS de plusieurs centaines de lignes, cela prend du temps (temps qui varie en fonction de la connexion de votre serveur, de celle du visiteur, etc.). On peut améliorer ce temps en diminuant simplement la taille du fichier.

C'est possible grâce à un outil Java appelé YUI Compressor, un outil développé par Yahoo!. Cet outil permet de diminuer la taille de vos fichiers CSS, mais surtout de vos fichiers JavaScript, en supprimant les commentaires, les espaces, en raccourcissant le nom des variables, etc. On dit qu'il « minifie » les fichiers (il ne les compresse pas comme un fichier zip). Le code devient bien sûr complètement illisible ! Mais c'est là qu'Assetic intervient de nouveau : il vous permet de garder votre version claire lorsque vous développez, mais « minifie » les fichiers pour vos visiteurs (en mode prod) !

Conclusion

Grâce à Assetic, on peut optimiser très facilement nos scripts CSS/JS. Par exemple, nous pouvons passer de nos huit requêtes pour 500 Ko à seulement deux requêtes (1 CSS + 1 JS) pour 200 Ko. Le temps d'affichage de la page pour nos visiteurs sera donc bien plus court, et on conserve la lisibilité du code côté développeur !

Pratique : Assetic à la rescousse !

Assetic est déjà activé par défaut dans Symfony2, donc vous n'avez rien à faire de ce côté, vous pouvez l'utiliser directement. L'objectif d'Assetic est de regrouper nos fichiers CSS et JavaScript comme nous venons d'en parler.

Servir des ressources

Assetic peut servir au navigateur les ressources que vous lui demandez.

Servir une seule ressource

Allez donc dans la vue du layout, nous allons y déclarer nos fichiers CSS et JavaScript. Voici comment on déclarait nos fichiers CSS jusqu'à maintenant :

1
2
3
{# app/Resources/views/layout.html.twig #}

<link rel="stylesheet" href="{{ asset('bundles/sdzblog/css/main.css') }}" type="text/css" />

Et voici comment faire pour décharger cette responsabilité à Assetic :

1
2
3
4
5
{# app/Resources/views/layout.html.twig #}

{% stylesheets '@SdzBlogBundle/Resources/public/css/main.css' %}
  <link rel="stylesheet" href="{{ asset_url }}" type="text/css" />
{% endstylesheets %}

Au sein de la balise stylesheets, la variable {{ asset_url }} est définie, et vaut l'URL à laquelle le navigateur peut récupérer le CSS.

Et voici le HTML qu'Assetic a généré avec cette balise :

1
<link rel="stylesheet" href="/Symfony/web/app_dev.php/css/519c4f6_main_1.css" type="text/css" />

Pas convaincant ? C'est parce que nous sommes en mode dev ! Nous verrons plus loin comment nous occuper du mode prod, qui demande un peu d'effort.

En attendant, essayons de comprendre ce code généré. En mode dev, Assetic génère à la volée les ressources, d'où une URL vers un fichier CSS qui passe par le contrôleur frontal app_dev.php. En réalité, c'est bien un contrôleur d'Assetic qui s'exécute, car le fichier app_dev.php/css/519c4f6_main_1.css n'existe évidemment pas. Ce contrôleur va chercher le contenu du fichier qu'on lui a indiqué, puis le retransmet. Pour l'instant il le retransmet tel quel, mais il sera bien sûr possible d'appliquer des modifications, nous le verrons par la suite.

Et bien sûr, le mécanisme est exactement le même pour vos fichiers JavaScript :

1
2
3
4
5
{# app/Resources/views/layout.html.twig #}

{% javascripts '@SdzBlogBundle/Resources/public/js/main.js' %}
  <script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}

Pensez bien à mettre les fichiers JavaScript en toute fin du code HTML. C'est parce que lorsque le navigateur intercepte une déclaration de fichier JavaScript, il arrête le rendu de la page, charge le fichier, l'exécute, et ensuite seulement continue le rendu de la page. En effet, le navigateur ne peut pas savoir d'avance si le script JavaScript veut changer quelque chose à l'apparence de la page, il est donc obligé de tout bloquer avant de continuer. Si vos scripts JavaScript sont bien faits, vous pouvez sans problème les déclarer en fin de page. Ainsi, le navigateur se bloquera pour vos fichiers JavaScript une fois que le rendu de la page sera terminé ! Ce qui fait que c'est transparent pour vos visiteurs : ils peuvent déjà profiter de la page pendant que les scripts JS se chargent en arrière-plan.

Servir plusieurs ressources regroupées en une

Cela devient déjà un peu plus intéressant. En plus du fichier CSS main.css (ou tout autre fichier, adaptez au code que vous avez bien sûr), on va charger le fichier bootstrap.css qui est directement dans web/css. Avec l'ancienne méthode, on aurait écrit une deuxième balise <link>, mais voici comment faire avec Assetic :

1
2
3
4
5
6
7
{# app/Resources/views/layout.html.twig #}

{% stylesheets
  '@SdzBlogBundle/Resources/public/css/main.css'
  'css/bootstrap.css' %}
  <link rel="stylesheet" href="{{ asset_url }}" type="text/css" />
{% endstylesheets %}

On a simplement rajouté la deuxième ressource à charger dans la balise stylesheets. Et voici le rendu HTML :

1
2
<link rel="stylesheet" href="/Symfony2/web/app_dev.php/css/03b7e21_main_1.css" type="text/css" />
<link rel="stylesheet" href="/Symfony2/web/app_dev.php/css/03b7e21_bootstrap_2.css" type="text/css" />

Mais il n'était pas censé regrouper les deux ressources en une ?

Si bien sûr… mais en mode prod ! Encore une fois, nous sommes en mode de développement, il est donc inutile de regrouper les ressources (on se fiche un peu de la rapidité), Assetic ne le fait donc pas.

Si vous avez plusieurs fichiers CSS dans le répertoire des CSS de votre bundle, il est également possible d'utiliser un joker pour les charger tous. Ainsi, au lieu de préciser les fichiers exacts :

1
2
3
4
5
{# app/Resources/views/layout.html.twig #}

{% stylesheets
  '@SdzBlogBundle/Resources/public/css/main.css'
  '@SdzBlogBundle/Resources/public/css/autre.css' %}

Vous pouvez utiliser le joker « * », comme ceci :

1
2
3
{# app/Resources/views/layout.html.twig #}

{% stylesheets '@SdzBlogBundle/Resources/public/css/*' %}

Ce qui chargera tous les fichiers qui sont dans le répertoire, pratique !

Avec cette méthode, vous constatez qu'Assetic va chercher les ressources directement dans les répertoires du bundle. Vous n'avez donc plus à publier les ressources publiques de votre bundle dans le répertoire web. ;)

Modifier les ressources servies

En servant les ressources depuis un contrôleur PHP, Assetic a la possibilité de modifier à la volée tout ce qu'il sert. Cela est possible grâce aux filtres, que l'on peut définir directement dans les balises stylesheets ou javascripts.

Voyons quelques filtres intéressants.

Le filtre cssrewrite

Si vous avez exécuté le code précédent, vous avez dû vous rendre compte qu'il se pose un petit problème : les images utilisées par le CSS de bootstrap ont disparu. En effet, le fichier CSS de bootstrap fait référence aux images via le chemin relatif ../img/exemple.png.

Lorsque le fichier CSS était placé dans web/css, ce chemin relatif pointait bien vers web/img, là où sont nos images. Or maintenant, du point de vue du navigateur, le fichier CSS est dans app_dev.php/css, du coup le chemin relatif vers les images n'est plus bon !

C'est ici qu'intervient le filtre cssrewrite. Voici la seule modification à apporter côté vue Twig :

1
2
3
4
5
6
7
{# app/Resources/views/layout.html.twig #}

{% stylesheets filter='cssrewrite'
  '@SdzBlogBundle/Resources/public/css/main.css'
  'css/bootstrap.css' %}
  <link rel="stylesheet" href="{{ asset_url }}" type="text/css" />
{% endstylesheets %}

On a juste précisé l'attribut filter à la balise. Ce filtre permet de réécrire tous les chemins relatifs contenus dans les fichiers CSS, afin de prendre en compte la modification du répertoire du CSS. Actualisez votre page, vous verrez que cela fonctionne très bien ! Le chemin relatif d'accès aux images est devenu : ../../img/exemple.png, ce qui est bon.

Les filtres yui_css et yui_js

Ces filtres sont très utiles, ce sont ceux qui « minifient » les fichiers avec YUI Compressor.

Pour utiliser l'outil YUI Compressor, il faut que vous le téléchargiez manuellement. Copiez le fichier (version 2.4.8-pre à l'heure où j'écris ces lignes) dans le répertoire app/Resources/java, par exemple. Maintenant, direction la configuration de notre application, il y a une section sur Assetic pour lui dire où nous avons mis Yui Compressor. Modifiez la partie assetic: comme suit :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# app/config/config.yml

# Assetic Configuration
assetic:
    debug:          %kernel.debug%
    use_controller: false
    # java: /usr/bin/java
    filters:
        cssrewrite: ~
        yui_js:
            jar: %kernel.root_dir%/Resources/java/yuicompressor.jar
        yui_css:
            jar: %kernel.root_dir%/Resources/java/yuicompressor.jar

Le %kernel.root_dir% représente le répertoire de votre application, à savoir le répertoire app.

Si vous êtes sous Windows ou que votre exécutable Java ne se trouve pas dans /usr/bin/java, il faut alors décommenter le paramètre java dans la configuration d'Assetic. Changez sa valeur en C:\Program Files (x86)\Java\jre7\bin\java.exe (valeur par défaut pour Windows 8 64 bits ), ou toute autre valeur qui convient à votre configuration (à vous de voir où est votre fichier java.exe).

Voilà, nous venons d'activer les filtres yui_js et yui_css, on peut maintenant les utiliser depuis nos vues. Ajoutez ce filtre dans vos balises :

1
2
3
4
{# app/Resources/views/layout.html.twig #}

{% stylesheets  filter='cssrewrite, yui_css'
  ... %}

Et de même pour les fichiers JavaScript :

1
2
3
4
{# app/Resources/views/layout.html.twig #}

{% javascripts filter='yui_js'
  ... %}

Testez le rendu !

Mais… on est toujours en mode dev et les fichiers sont devenus illisibles pour un éventuel débogage ! Heureusement, vous avez la possibilité de dire qu'un filtre ne s'applique pas en mode dev. Il suffit de mettre un point d'interrogation devant :

1
2
3
4
{# app/Resources/views/layout.html.twig #}

{% stylesheets filter='?yui_css'
  ... %}

Ainsi, le filtre ne s'appliquera qu'en mode prod, tout comme le groupement des fichiers en un seul.

Au final, notre mode dev n'a pas changé d'un poil, on garde nos différents fichiers et ces derniers sont lisibles, mais le mode prod a reçu toutes les optimisations : regroupement des fichiers ainsi que « minification ».

Gestion du mode prod

Si vous n'avez pas encore testé le rendu en mode prod, faites-le. Cela ne fonctionne pas ? Vos fichiers CSS et JS ne sont pas chargés ? C'est normal. :p Nous n'avons pas fini notre mise en place.

Comprendre Assetic

Pour comprendre pourquoi la gestion du mode prod demande un effort supplémentaire, vous devez comprendre la manière dont Assetic fonctionne. Lorsque l'on utilise les balises {% stylesheets %} ou {% javascripts %}, le code HTML généré en mode prod est le suivant (regardez la source de vos pages HTML) :

1
<link rel="stylesheet" href="/Symfony2/web/css/cd91cad.css" type="text/css" />

Or ce fichier n'existe pas du tout !

Lors du mode dev, on l'a vu, Assetic passe directement par un contrôleur pour générer à la volée nos ressources. Mais évidemment, « minifier » et regrouper des fichiers à la volée et ce pour chaque requête, cela prend beaucoup de temps. Si en mode dev on peut se le permettre, on ne le peut pas en mode prod !

Du coup, l'astuce pour le mode prod est d'exporter en dur, une bonne fois pour toutes, les fichiers CSS et JS dont on a besoin. Ainsi, en mode prod, le fichier /css/cd91cad.css (dans mon cas) existera en dur, Assetic n'interceptera pas l'URL, et votre serveur web (souvent Apache) va envoyer directement le contenu du fichier à vos visiteurs. Plus rapide, on ne peut pas !

Vous notez que, maintenant que nous sommes en mode prod, Assetic a bien regroupé toutes nos ressources en une seule.

Exporter ses fichiers CSS et JS

Pour faire cet export en dur, il faut utiliser une simple commande d'Assetic :

1
php app/console assetic:dump --env=prod

Cette commande devrait vous sortir un résultat de ce type :

1
2
3
4
5
C:\wamp\www\Symfony>php app/console assetic:dump --env=prod
Dumping all prod assets.
Debug mode is off.

16:13:30 [file+] C:/wamp/www/Symfony/app/../web/css/cd91cad.css

Cette commande va lire toutes nos vues pour y trouver les balises {% stylesheets %} et {% javascripts %}, puis va exporter en dur dans les fichiers /web/css/XXX.css et /web/js/XXX.js.

Et voilà, maintenant, nos fichiers existent réellement. Testez à nouveau le rendu en mode prod : c'est bon !

Attention : exporter nos fichiers en dur est une action ponctuelle. Ainsi, à chaque fois que vous modifiez vos fichiers d'origine, vous devez exécuter la commande assetic:dump pour mettre à jour vos fichiers pour la production ! Prenez donc l'habitude, à chaque fois que vous déployez en production, de vider le cache et d'exporter les ressources Assetic.

Et bien plus encore…

Assetic, c'est une bibliothèque complète qui permet beaucoup de choses. Vous pouvez également optimiser vos images, et construire une configuration plus poussée. Bref, n'hésitez pas à vous renseigner sur la documentation officielle.


  • Le chargement des fichiers CSS et JS prend beaucoup de temps dans le rendu d'une page HTML sur le navigateur de vos visiteurs ;
  • Assetic permet de regrouper tous vos fichiers CSS ainsi que tous vos fichiers JS, afin de réduire le nombre de requêtes HTTP que doivent faire vos visiteurs pour afficher une page ;
  • Assetic permet également de minifier vos fichiers, afin de diminuer leur taille et donc accélérer leur chargement pour vos visiteurs ;
  • Enfin, l'utilisation d'Assetic permet de garder votre confort de développement en local, vos fichiers ne sont ni regroupés, ni minifiés : indispensable pour déboguer !