I. Introduction

Le temps de chargement d'une page est un facteur important dans l'évaluation des performances d'un site Web. Il a un impact non négligeable sur l'expérience utilisateur et même sur le référencement naturel. Plus les pages de votre site se chargent rapidement, plus l'expérience de navigation est fluide et plus les utilisateurs sont contents.

Pour améliorer les performances d'une application, les développeurs ont recours à plusieurs techniques d'optimisation côté client et côté serveur. Parmi ces techniques, on peut citer entre autres la mise en cache, l'optimisation des images, la compression et la minification du CSS et JavaScript, le regroupement des fichiers JavaScript et CSS, etc.

Dans cet article, nous verrons comment améliorer les performances d'une application Web ASP.NET MVC en utilisant le regroupement et la minification du CSS et JavaScript.

II. Prérequis

Pour cet article, vous devez avoir des connaissances de base en ASP.NET MVC et C#.

III. Outils utilisés

Pour les exemples disponibles dans cet article, vous pouvez utiliser :

  • ASP.NET MVC 4 RC (ou ASP.NET MVC 3);
  • Visual Studio 2012 RC (ou Visual Studio 2010)

Pour la mesure des performances, nous aurons recours aux outils suivants :

  • l'extension Yslow;
  • Fiddler2.

IV. Description de la minification

Les fichiers JavaScript et CSS font partie des éléments d'une application ASP.NET MVC qui sont interprétés par le navigateur. Lors de l'exécution d'une page Web, ces fichiers sont téléchargés et exécutés côté client. La taille de ces fichiers est grandement influencée par des commentaires, des espaces, des sauts de lignes, etc. qui ont pour objectif de rendre ces fichiers plus lisibles et facilement compréhensibles par le développeur.

La minification est un procédé qui vise à réduire la taille d'un fichier JavaScript ou CSS en supprimant les caractères inutiles pour l'interpréteur de scripts du navigateur comme les commentaires, les sauts de lignes, etc.

Exemple :

Soit les lignes de code JavaScript suivantes :

 
Sélectionnez

AddAltToImg = function (imageTagAndImageID, imageContext) {
    ///<signature>
    ///<summary> Adds an alt tab to the image
    // </summary>
    //<param name="imgElement" type="String">The image selector.</param>
    //<param name="ContextForImage" type="String">The image context.</param>
    ///</signature>
    var imageElement = $(imageTagAndImageID, imageContext);
    imageElement.attr('alt', imageElement.attr('id').replace(/ID/, ''));
}

Si on applique la minification à ce code, on obtient le résultat suivant :

 
Sélectionnez

AddAltToImg = function (n, t) { var i = $(n, t); i.attr("alt", i.attr("id").replace(/ID/, "")) }

V. Description du regroupement

Il est plus pratique, d'un point de vue développement et maintenance, de disposer de plusieurs fichiers de petite taille. Dans les applications ASP.NET MVC, vous avez un nombre important de fichiers JavaScript et CSS qui sont utilisés dans l'application.

Image non disponible

Selon le fonctionnement du protocole HTTP, pour chaque fichier appelé depuis une page (JavaScript, CSS, images, etc.), le navigateur va faire une demande au serveur et attendre une réponse. Bien que les navigateurs autorisent des connexions simultanées sur un même domaine pour réduire le temps de latence, des limites sont néanmoins imposées par ceux-ci. Voici les limites pour quelques navigateurs :

  • Internet Explorer 9 : six connexions simultanées par domaine ;
  • Firefox 13 : six connexions simultanées par domaine ;
  • Chrome : six connexions simultanées par domaine ;
  • Safari : quatre connexions simultanées par domaine.

Si vous disposez par exemple de treize fichiers CSS dans votre page et que vous utilisez Internet Explorer, votre navigateur va bloquer trois fois, chargeant simultanément six fichiers et mettant les autres en attente.

La capture ci-dessous illustre comment Internet Explorer procède lors du chargement des éléments d'une page Web.

Image non disponible

La barre grise est le temps d'attente que doit passer un fichier avant d'être téléchargé, parce que d'autres requêtes sont en cours par le navigateur.

Il devient donc important de réduire le nombre de requêtes du navigateur vers le serveur pour améliorer la vitesse de chargement de la page. Le regroupement est une technique qui permet de combiner plusieurs fichiers JavaScript et CSS en un seul fichier, pour réduire le nombre d'éléments à télécharger, et de ce fait le nombre de requêtes HTTP du navigateur.

VI. Utiliser le regroupement et la minification dans une application ASP.NET MVC

Au vu du nombre de bibliothèques JavaScript (jQuery, jQueryUi, knockout, etc.) et des fichiers CSS qui sont désormais référencés dans une application ASP.NET MVC (voir image ci-dessus), le temps de chargement des pages est considérablement altéré par ceux-ci. Il devient donc primordial d'avoir recours à une technique de regroupement et de minification pour améliorer les performances du site.

ASP.NET MVC 4 introduit l'espace de nom System.Web.Optimization qui regroupe des fonctionnalités permettant de mettre en œuvre assez aisément et simplement le regroupement et la minification.

Vous allez créer une nouvelle application ASP.NET MVC 4 afin de pouvoir mettre en œuvre cette fonctionnalité et analyser son impact sur les performances de celle-ci.

Lancez votre environnement de développement, cliquez sur fichiers, ensuite sur nouveau projet. Dans la fenêtre qui s'affiche, sélectionnez le type de projet « Application Web ASP.NET MVC 4 », donnez le nom BundlingApp et cliquez sur OK. Par la suite, sélectionnez le type de projet « Application Internet » et cliquez sur OK. Visual Studio va procéder à la création de votre nouvelle application.

VI-A. Détails sur la mise en œuvre du regroupement et de la minification dans ASP.NET MVC 4.

Dans le modèle de projet « Application Internet », le regroupement et la minification sont par défaut configurés.

Si vous ouvrez le fichier Global.asax.cs, vous trouverez les lignes de code suivantes dans la procédure Application_Start

 
Sélectionnez

protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

        }
    }

Vous remarquez certainement cette nouvelle ligne :

 
Sélectionnez

BundleConfig.RegisterBundles(BundleTable.Bundles);

Elle permet de définir le comportement par défaut du regroupement et la minification pour votre application.

Le code source de la procédure BundleConfig est disponible dans le fichier BundleConfig.cs du dossier App_Start. Dans ce fichier, vous avez les lignes de code suivantes :

 
Sélectionnez

public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-1.*"));

            bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                        "~/Scripts/jquery-ui*"));

            bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.unobtrusive*",
                        "~/Scripts/jquery.validate*"));

            bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                        "~/Scripts/modernizr-*"));

            bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));

            bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
                        "~/Content/themes/base/jquery.ui.core.css",
                        "~/Content/themes/base/jquery.ui.resizable.css",
                        "~/Content/themes/base/jquery.ui.selectable.css",
                        "~/Content/themes/base/jquery.ui.accordion.css",
                        "~/Content/themes/base/jquery.ui.autocomplete.css",
                        "~/Content/themes/base/jquery.ui.button.css",
                        "~/Content/themes/base/jquery.ui.dialog.css",
                        "~/Content/themes/base/jquery.ui.slider.css",
                        "~/Content/themes/base/jquery.ui.tabs.css",
                        "~/Content/themes/base/jquery.ui.datepicker.css",
                        "~/Content/themes/base/jquery.ui.progressbar.css",
                        "~/Content/themes/base/jquery.ui.theme.css"));
        }

La procédure Bundles.add permet d'ajouter à l'objet BundleCollection un ou plusieurs fichiers qui seront minifiés et regroupés en un seul élément (Bundle). Elle prend en paramètre un objet de type Bundle, qui permet de définir le chemin des fichiers qui seront regroupés et minifiés, ainsi que le nouveau répertoire virtuel qui sera désormais utilisé pour appeler ces fichiers.

 
Sélectionnez

   bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-1.*"));

Le code ci-dessus permet donc d'ajouter dans l'objet BundleCollection, l'ensemble des fichiers du dossier Scripts commençant par jquery-1, qui seront désormais accessibles comme un seul élément via le répertoire virtuel "~/bundles/jquery". Il s'agit du fichier jquery-1.6.2.js lorsque vous êtes en mode Debug et du fichier jquery-1.6.2.min.js en mode Release. Le fichier jquery-1.6.2-vsdoc.js n'est pas pris en compte.

Le code ci-dessous illustre comment vous pouvez appeler le Bunndle dans votre vue.

 
Sélectionnez

@Scripts.Render("~/bundles/jquery")

Pour spécifier plusieurs fichiers différents qui seront regroupés au sein d'un seul Bundle de votre choix, vous allez séparer les chaines contenant le chemin virtuel vers chaque ressource par des virgules dans la procédure Include(), comme l'illustre le code ci-dessous :

 
Sélectionnez

bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
                        "~/Content/themes/base/jquery.ui.core.css",
                        "~/Content/themes/base/jquery.ui.resizable.css",
                        "~/Content/themes/base/jquery.ui.selectable.css",
                        "~/Content/themes/base/jquery.ui.accordion.css",
                        "~/Content/themes/base/jquery.ui.autocomplete.css",
                        "~/Content/themes/base/jquery.ui.button.css",
                        "~/Content/themes/base/jquery.ui.dialog.css",
                        "~/Content/themes/base/jquery.ui.slider.css",
                        "~/Content/themes/base/jquery.ui.tabs.css",
                        "~/Content/themes/base/jquery.ui.datepicker.css",
                        "~/Content/themes/base/jquery.ui.progressbar.css",
                        "~/Content/themes/base/jquery.ui.theme.css"));
        }

L'ensemble des fichiers référencés dans la procédure Include seront minifiés et regroupés au sein d'un seul élément qui sera accessible via le chemin "~/Content/themes/base/css".

Pour utiliser ce regroupement dans votre page Web, vous allez simplement procéder comme suit :

 
Sélectionnez

@Styles.Render("~/Content/themes/base/css")

Si vous souhaitez combiner toutes les ressources d'un répertoire en un seul élément, vous pouvez utiliser la méthode IncludeDirectory de la classe Bundle.

 
Sélectionnez

public Bundle IncludeDirectory(
     string directoryVirtualPath,  // Chemin virtuel.
     string searchPattern)         // filtre.

Exemple :

 
Sélectionnez

bundles.Add(new StyleBundle("~/bundles/Script").IncludeDirectory("~/Scripts","*.js"));

La ligne de code ci-dessus va permettre de regrouper et minifier l'ensemble des fichiers JavaScript du dossier Scripts en un seul élément. Les sous-dossiers de ce dossier ne sont pas pris en compte. Pour les prendre en compte, vous allez simplement procéder comme suit :

 
Sélectionnez

bundles.Add(new StyleBundle("~/bundles/Script").IncludeDirectory("~/Scripts","*.js", true));

la classe ScriptBundle est utilisée pour créer un bundle lorsque l'on souhaite regrouper et minifier des fichiers JavaScript. Pour référencer ce bundle dans la vue, la procédure Scripts.Render est utilisée.

Pour les fichiers CSS, c'est la classe StyleBundle qui est utilisée. Dans ce cas, le Bundle est référencé dans la vue en utilisant la procédure Styles.Render().

La capture ci-dessous illustre comment les Bundles sont appelés dans le fichier Layout.cshtml.

Image non disponible

Vous pouvez vous inspirer de cet exemple pour créer vos propres bundle personnalisés en fonction des fichiers JavaScript et CSS que vous utilisez dans vos pages.

VI-B. Mesure des performances

Nous avons vu comment mettre en œuvre et configurer le regroupement et la minification dans notre application ASP.NET MVC. Quels sont les impacts réels de cette technique sur le temps de chargement de notre application ?

Pour répondre à cette question, nous allons dans cette section utiliser l'outil Yslow pour mesurer et comparer les performances de notre application sans recours à la minification et le regroupement, et lorsque ces méthodes sont utilisées.

Exécutez votre application. Après affichage de celle-ci, lancez Yslow et démarrez le test pour la page Login. Après analyse de la page par Yslow, vous aurez le résultat suivant :

Image non disponible

Selon le graphique de Yslow, cinq fichiers JavaScript ont été chargés pour une taille totale de 103,3 Ko ainsi que treize fichiers CSS pour une taille de 15,4 ko. Bizarre qu'autant de fichiers soient chargés et que les tailles de bases soient conservées ! En effet, en environnement de développement, le Bundling n'est pas activé par défaut.

Déboguer un fichier JavaScript ou CSS qui a été minifié est extrêmement difficile. Le passage de votre application en production active automatiquement la minification et le regroupement, car les paramètres de configuration vont passer du mode Debug à Release.

Si vous souhaitez activer le Bundle en environnement de développement, il vous suffit d'éditer le fichier Web.config de votre application et faire passer le paramètre debug à False dans la balise "compilation" de la section "system.web".

 
Sélectionnez

  <system.web>
    <compilation debug="true" targetFramework="4.5" />
</system.web>

À défaut, vous pouvez simplement ajouter la ligne de code suivante dans la procédure Application-Start() du fichier Global.asax.cs, avant la ligne BundleConfig.RegisterBundles(BundleTable.Bundles) :

 
Sélectionnez

  BundleTable.EnableOptimizations = true;

Exécutez de nouveau votre application après avoir activé le Bundling. Vous allez obtenir le résultat suivant après analyse avec Yslow :

Image non disponible

En comparant les deux images, vous vous rendez compte que le nombre de requêtes HTTP sur votre page est passé de 23 à 10. Les cinq fichiers JavaScript ont été regroupés en trois fichiers et les treize feuilles de styles ont été combinées en 2 fichiers.

De même, la taille des fichiers JavaScript est passée de 103,5 Ko à 55,6 Ko soit une réduction de presque 50 %. Pareil pour les fichiers JavaScript.

VI-C. Le Bundle et la mise en cache

L'activation du Bundle entraine automatiquement la mise en cache des fichiers JavaScript et CSS. La mise en cache est également une technique d'optimisation du temps de chargement d'une page en évitant d'exécuter plusieurs fois certaines opérations (chargement des données, lecture d'une information dans la base de données, etc.), ou de charger du contenu statique qui varie très peu (fichiers JavaScript, CSS, images, etc.), en stockant celui-ci en mémoire chez le client ou sur le serveur.

Dans les résultats affichés par Yslow, le second graphique représente l'état du site lorsque la mise en cache est utilisée. En observant ce graphique pour le premier test lorsque le Bundling n'est pas actif, vous vous rendez compte qu'aucune donnée n'est mise en cache, et tous les éléments de la page sont chargés.

Image non disponible

Pour le second test (après activation du Bundling), vous pouvez remarquer que les fichiers JavaScript et CSS ont été mis en cache.

Image non disponible

Avec la mise en cache, le navigateur n'a plus besoin d'effectuer des requêtes pour charger ces fichiers qui sont directement accessibles dans le cache.

Le Bundling permet donc au final de réduire le nombre de requêtes effectuées par votre navigateur pour cette page de 23 à 5 et la taille totale des données qui sont chargées de 154 ko à 35,1 ko.

Les fichiers JavaScript et CSS variant très peu dans une application, le Bundling configure la mise en cache de ces éléments pour environ un an. S'il arrive que vous modifiiez vos fichiers entre temps, comment procéder ? Vous n'avez rien à faire. À chaque Bundle, est généré automatiquement un numéro de version unique, comme l'illustre la capture suivante avec l'outil Fiddler :

Image non disponible

Chaque fois qu'un fichier est modifié, le numéro de version est automatiquement changé. Cela permet au navigateur de savoir s'il doit utiliser les données qui sont en cache ou charger de nouveau celles-ci depuis le serveur.

VII. Activer le Bundling dans une application ASP.NET MVC 3

Pas mal le gain de performance du regroupement et de la minification sur une application ASP.NET MVC 4. Mais, si je suis encore sur MVC 3 et que je désire profiter de cette nouveauté sans forcement migrer ma solution, comment procéder ?

L'ensemble des fonctions permettant le regroupement et la minification du CSS et JavaScript sur ASP.NET MVC 4 ont été regroupées au sein du package NuGet Microsoft.Web.Optimization.

La version stable 0.1 peut-être téléchargée via l'interface graphique de gestion des packages, mais elle manque d'un nombre important de fonctionnalités qui sont disponibles dans la RC d'ASP.NET MVC 4, dont la mise en cache.

Image non disponible

Une bêta 2 de la version 1.0.0 du package est disponible avec toutes les features décrites ci-dessus. Vous pouvez télécharger cette version en utilisant la console NuGet. Il suffit pour cela de cliquer sur le menu Outils, ensuite « Gestionnaire de package de bibliothèques » et enfin sur « Console du Gestionnaire de package ». Dans la fenêtre qui va s'afficher, tapez la commande « Install-Package Microsoft.Web.Optimization -Pre » et validez sur entrer.

Image non disponible

Après installation, vous pouvez vous inspirer des exemples plus hauts pour mettre en œuvre le Bundle dans votre application. À titre d'exemple, vous pouvez utiliser le Bundle personnalisé suivant :

 
Sélectionnez

public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-1.*"));

            bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                        "~/Scripts/jquery-ui*"));

            bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.unobtrusive*",
                        "~/Scripts/jquery.validate*"));

            bundles.Add(new ScriptBundle("~/bundles/microsoft").Include(
                        "~/Scripts/Micrsoft*"));

            bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));

        }

Vous allez copier ce code dans le fichier Global.asax.cs de votre application, après avoir bien évidemment ajouté une référence à System.Web.Optimization. Vous n'aurez plus qu'à activer le Bundle dans la procédure Application_Start :

 
Sélectionnez

BundleTable.EnableOptimizations = true;
RegisterBundles(BundleTable.Bundles);

Pour utiliser vos Bundles personnalisés dans les vues en utilisant les procédures Scripts.Render() et Styles.Render(), vous devez également ajouter une référence à System.Web.Optimization dans votre vue.

Pour éviter d'avoir à le faire pour chaque vue, vous pouvez simplement éditer le fichier Web.Config du dossier Views et ajoutez cette référence dans la section "pages".

 
Sélectionnez

<system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization" />
        <add namespace="System.Web.Routing" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

Pour information, Razor utilise System.Web.Mvc.WebViewPage comme classe de base. Toutes les vues Razor héritent automatiquement de cette classe.

VIII. Conclusion

Avec cette nouvelle fonctionnalité introduite dans ASP.NET MVC 4 permettant de mettre en œuvre avec souplesse et facilité le regroupement et la minification, vos pages Web vont bénéficier d'un temps de chargement optimisé, rendant la navigation sur votre application fluide et agréable.

IX. Remerciements

Je tiens à remercier Chtulus et Torgar pour leurs relectures et corrections orthographiques.

X. Liens