La compression JS & CSS ? Rien de plus simple en ASP.Net MVC 4

zip

L’une des grandes nouveautés qu’ASP.Net MVC 4 a apporté est l’unification et la compression automatisée des fichiers JS & CSS. Nous allons voir ensemble comment ça fonctionne, comment l’utiliser et ce qu’il est possible de faire grâce à ça.

L’unification et la compression de fichiers JS & CSS, qu’est ce c’est ?

Lorsqu’on rédige du code JavaScript ou des feuilles de styles CSS, notre code est écrit pour être lisible et compréhensible par les navigateurs des utilisateurs du site ET par nous.

Sauf que les navigateurs n’ont pas besoin d’avoir un contenu facile à lire pour un être humain, bien au contraire… Si vous supprimez tous les caractères inutiles (espaces, retours à la ligne, etc.) de vos fichiers, l’interprétation sera plus rapide !

De même, les navigateurs n’ont pas besoin d’avoir du JavaScript ou du CSS découpé en plusieurs fichiers. Moins il y aura de fichiers, moins il y aura de sous-requêtes pour notre site web, et plus le chargement de la page se fera rapidement. (Surtout sachant que les navigateurs limitent le nombre de sous-requêtes simultanées pour un domaine donné.)

La compression (ou minification) est donc le fait de rendre ces fichiers les plus rapides à lire et à interpréter pour un navigateur, en supprimant les éléments inutiles pour l’interpréteur.

L’unification, quant à elle, consiste à créer, à partir d’une liste de fichiers JS ou CSS, un bundle. Il s’agit d’un fichier JS ou CSS qui contiendra le contenu de tous les fichiers de cette liste.

Faut-il le faire à la main ?!

Non, bien sûr que non. Il existe de nombreux moteurs de compression JavaScript et/ou CSS. On peut par exemple citer Google Closure Compiler, YUI Compressor ou UglifyJS.

Malheureusement, pour une application ASP.Net, il fallait auparavant passer par un plugin pour par des scripts afin d’unifier et de minimiser nos fichiers JS & CSS. Mais cela a changé avec ASP.Net MVC 4.

ASP.Net MVC 4

Dans ASP.Net MVC 4, Microsoft a décidé d’implémenter directement un moteur d’unification et de compression de fichiers JS & CSS, WebGrease, afin de simplifier au maximum cette étape pour les développeurs. Il s’agit d’une très bonne chose, considérant le fait qu’il y a de plus en plus de JavaScript sur nos sites, et qu’ASP.Net MVC 4 à tendance à inclure beaucoup de fichiers JS et CSS par défaut :

filesaspnetmvc4

Il est à noter que tous les exemples donnés ici sont basés sur une application ASP.Net MVC 4 Internet.

Les bundles

Un Bundle ASP.Net MVC 4 est défini par :

  • un chemin virtuel (le nom de notre bundle sous forme de chemin)
  • les chemins des fichiers ou des dossiers (contenant les fichiers) à unifier
  • un filtre à appliquer pour choisir les bons fichiers (par exemple *.js)
  • la possibilité de chercher récursivement dans les dossiers inclus
  • une transformation (par exemple la minification, implémentation de IBundleTransform)

Par défaut, les ScriptBundle et les StyleBundle vont compresser tous les fichiers JS et CSS spécifiés. Il est néanmoins possible de ne pas minifier les fichiers par défaut en utilisant l’objet Bundle.

Il est aussi possible de ne pas s’occuper des numéros de versions à l’aide du mot-clé “{version}”. Cela nous permet d’utiliser NuGet pour mettre à jour jQuery sans avoir à changer à la main le nom des fichiers JS à utiliser.

Vous pouvez par exemple voir les bundles définis par défaut pour ASP.Net MVC 4 dans App_Start/BundleConfig.cs :

bundlesaspnetmvc4

Vous pouvez ensuite implémenter un de ces bundles dans votre page, en appelant simplement le code qu’on peut voir, par exemple, dans _Layout :

 @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") 

Mais ça ne marche pas !

En debug, par défaut, il n’y a aucune minification ou compression des js. Afin de l’activer, vous devez désactiver le debug en allant supprimer la ligne suivante dans votre web.config :

   <compilation debug="true" /> 

Il est toutefois possible d’activer la minification / compression en debug à l’aide du code suivant :

 BundleTable.EnableOptimizations = true; 

Une fois ceci fait, vous verrez apparaitre dans votre HTML une balise script contenant tous vos JS :

<script src="/bundles/jquery?v=JzhfglzUfmVF2qo-weTo-kvXJ9AJvIRBLmu11PgpbVY1"></script>

Gérer différents environnements

Lorsqu’on travaille avec beaucoup de fichiers JS ou CSS, il peut arriver que nous ayons des versions debug ou compressées de nos fichiers… Et bien rassurez vous, ASP.Net MVC 4 les gère ! Et oui, il cherchera certains types de fichiers en priorité :

  • En DEBUG il cherchera d’abord les fichiers en .debug.js (ou .debug.css), puis, s’il n’en trouve pas, il prendra la version .js (ou .css)
  • En RELEASE il cherchera en priorité des fichiers minifiés en .min.js (ou .min.css), puis, s’il n’en trouve pas, il prendra la version .js (ou .css).

Par défaut, il ignorera tous les fichiers qui contiennent “-vsdoc”, car utilisés uniquement par Visual Studio.

Syntaxe et création de ses propres bundles

Il existe de nombreuses façons de créer des bundles. Voici différents exemples expliqués :

// bundle sans minification
bundles.Add(new Bundle("~/bundles/demo").Include("~/Scripts/demo/demo1.js", "~/Scripts/demo/demo2.js"));

// bundle avec minification
bundles.Add(new Bundle("~/bundles/demo", new JsMinify()).Include("~/Scripts/demo/demo1.js", "~/Scripts/demo/demo2.js"));

// bundle avec minification en passant par ScriptBundle
bundles.Add(new ScriptBundle("~/bundles/demo").Include("~/Scripts/demo/demo1.js", "~/Scripts/demo/demo2.js"));

// bundle qui va inclure tous les fichiers js d'un dossier (sans recherche récursive dans les sous dossiers)
bundles.Add(new Bundle("~/bundles/demo").IncludeDirectory("~/Scripts/demo/", "*.js", false));

// bundle qui va inclure tous les fichiers js d'un dossier (AVEC recherche récursive dans les sous dossiers)
bundles.Add(new Bundle("~/bundles/demo").IncludeDirectory("~/Scripts/demo/", "*.js", true));

// bundle qui va inclure et minifier tous les fichiers js d'un dossier (AVEC recherche récursive dans les sous dossiers)
bundles.Add(new ScriptBundle("~/bundles/demo").IncludeDirectory("~/Scripts/demo/", "*.js", true));

La syntaxe est la même pour les fichiers CSS, seules deux choses changent :

  • ScriptBundle devient StyleBundle
  • JsMinify() devient CssMinify().

Attention cependant : lorsqu’on inclue tous les fichiers d’un dossier dans notre bundle, Visual Studio prendra ces dossiers par ordre alphabétique. Si cela peut poser problème, il est possible de créer une implémentation d’IBundleOrderer pour trier nous-même nos fichiers comme on l’entend. IBundleOrderer n’oblige l’implémentation que d’une méthode :

 IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo>
    files) 

Nous connecteront ensuite notre trieur de bundle personnalisé à notre bundle de la façon suivante :

 myBundle.Orderer = new MyCustomBundleOrderer(); 

A noter également, il est tout à fait possible d’utiliser un chemin absolu pour aller chercher un fichier stocké sur un autre domaine :

 bundles.Add(new ScriptBundle("~/bundles/jquery", jqueryCdnPath).Include( "http://www.domain.com/scripts/js/jquery-{version}.js"));
    

Mise en cache

Les bundles sont mis en cache côté client pour une durée d’un an. Ainsi, tant que la clé de version (ou token) ne change pas, le navigateur du client réutilisera sa version locale du bundle. Ce token change automatiquement lorsqu’un des fichiers du bundle a été modifié.

Aller plus loin ?

Le framework d’unification et de compression peut permettre d’assembler et de compresser d’autres types de fichiers que les classiques JS et CSS. Par exemple des fichiers SCSS, Sass, LESS ou encore Coffeescript.

Nous allons pour cela devoir implémenter IBundleTransform. Par exemple, si on désire unifier et compresser des fichiers LESS :

using System.Web.Optimization;

public class LessTransform : IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response)
    {
        response.Content = dotless.Core.Less.Parse(response.Content);
        response.ContentType = "text/css";
    }
}

Puis, nous allons créer notre bundle à l’aide du LessTransform et de CssMinify :

var lessBundle = new Bundle("~/Style/Less").IncludeDirectory("~/Style", "*.less");
lessBundle.Transforms.Add(new LessTransform());
lessBundle.Transforms.Add(new CssMinify());
bundles.Add(lessBundle);

Enfin, nous n’avons plus qu’à appeler dans notre vue notre Styles.Render :

 @Styles.Render("~/Style/Less") 

Astuces et pièges à éviter

  • Il est fortement recommandé de préfixer les chemins de ses bundles (afin d’éviter d’éventuels problèmes de routing)
  • A chaque modification d’un fichier d’un de vos bundles, un nouveau token sera généré pour votre bundle et le navigateur de chacun de vos clients devra retélécharger le bundle. Ce téléchargement sera donc forcément plus lourd que le téléchargement du simple fichier JS ou CSS modifié.
  • L’utilisation de bundles améliore surtout les temps de chargement lors du premier chargement. Etant donné que les navigateurs mettent en cache les fichiers JS et CSS, vous ne verrez pas forcément la différence de performances lors des chargements suivants.
  • Si vous avez de nombreux fichiers à télécharger pour le bon fonctionnement de votre page, n’hésitez pas à utiliser d’autres domaines pour les héberger, ce qui vous permettra de contourner la limitation navigateur des 6 sous-requêtes simultanées par domaine.
  • N’hésitez pas à créer vos bundles en fonction des besoins de vos pages (tous vos fichiers JS et CSS ne sont pas utiles sur toutes vos pages).
  • Pensez à activer la minification avant la MEP ! 😉

Sources : source 1, source 2, source 3

Nombre de vue : 401

COMMENTAIRES 3 commentaires

  1. Vincent dit :

    Il y a aussi un problème à éviter avec les bundles: Si, dans un environnement avec plusieurs webfarms, pendant une phase de livraison où toutes les webfarms n’ont pas les mêmes versions fichiers, de mauvaises versions risquent d’être mises dans le cache du client pour une durée de 1 an.

  2. TiTi dit :

    AjaxMin en tâche de build ce n’est pas mal aussi.
    http://ajaxmin.codeplex.com/

  3. Merci pour cet article détaillé sur les bundles.

    J’adore cette fonctionnalité et j’ai convaincu plusieurs clients de migrer de mvc 3 vers 4 juste pour ça.
    C’est pratique et ça permet d’optimiser les contenus côté client.

AJOUTER UN COMMENTAIRE