Bundles et minimisation

par Rick Anderson

Le regroupement et la minification sont deux techniques que vous pouvez utiliser dans ASP.NET 4,5 pour améliorer le temps de chargement des requêtes. Le regroupement et la minification améliorent le temps de chargement en réduisant le nombre de requêtes adressées au serveur et en réduisant la taille des ressources demandées (par exemple, CSS et JavaScript).)

La plupart des principaux navigateurs actuels limitent le nombre de connexions simultanées par nom d’hôte à six. Cela signifie que pendant que six requêtes sont traitées, des demandes supplémentaires pour les ressources d’un hôte sont mises en file d’attente par le navigateur. Dans l’image ci-dessous, les onglets réseau des outils de développement IE F12 affichent le minutage des ressources requises par l’affichage À propos d’un exemple d’application.

B/M

Les barres grises indiquent l’heure à laquelle la requête est mise en file d’attente par le navigateur en attente sur la limite de six connexions. La barre jaune est l’heure de la requête pour la première octet, autrement dit le temps nécessaire pour envoyer la requête et recevoir la première réponse du serveur. Les barres bleues indiquent le temps nécessaire pour recevoir les données de réponse du serveur. Vous pouvez double-cliquer sur une ressource pour obtenir des informations de minutage détaillées. Par exemple, l’image suivante montre les détails de minutage du chargement du fichier /Scripts/MyScripts/JavaScript6.js .

Capture d’écran montrant l’onglet réseau des outils de développement A S P dot NET avec des URL de demande de ressource sur la colonne de gauche et leurs minutages sur la colonne de droite.

L’image précédente montre l’événement Start , qui donne l’heure à laquelle la demande a été mise en file d’attente en raison du nombre de connexions simultanées limitées par le navigateur. Dans ce cas, la demande a été mise en file d’attente pendant 46 millisecondes en attendant qu’une autre demande soit terminée.

Regroupement

Le regroupement est une nouvelle fonctionnalité de ASP.NET 4.5 qui facilite la combinaison ou le regroupement de plusieurs fichiers dans un seul fichier. Vous pouvez créer des ensembles CSS, JavaScript et autres. Moins de fichiers signifient moins de requêtes HTTP et qui peuvent améliorer les performances de chargement de première page.

L’image suivante montre la même vue de minutage de l’affichage About affichée précédemment, mais cette fois avec regroupement et minification activés.

Capture d’écran montrant l’onglet Détails de minutage d’une ressource dans les outils de développement I E F 12. L’événement Start est mis en surbrillance.

Minimisation

La minification effectue une variété d’optimisations de code différentes pour les scripts ou css, telles que la suppression d’espaces blancs et de commentaires inutiles et le raccourcissement des noms de variables à un caractère. Considérez la fonction JavaScript suivante.

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/, ''));
}

Après la minification, la fonction est réduite à ce qui suit :

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

En plus de supprimer les commentaires et les espaces blancs inutiles, les paramètres et noms de variables suivants ont été renommés (raccourcis) comme suit :

D’origine Renommé
imageTagAndImageID n
imageContext t
imageElement i

Impact du regroupement et de la minification

Le tableau suivant présente plusieurs différences importantes entre la liste de toutes les ressources individuellement et l’utilisation d’un regroupement et d’une minification (B/M) dans l’exemple de programme.

Utilisation de B/M Sans B/M Changer
Demandes de fichiers 9 34 256%
Ko envoyé 3.26 11.92 266%
Ko reçu 388.51 530 36 %
Temps de chargement 510 MS 780 MS 53 %

Les octets envoyés ont eu une réduction significative du regroupement, car les navigateurs sont assez détaillés avec les en-têtes HTTP qu’ils appliquent sur les requêtes. La réduction des octets reçus n’est pas aussi importante, car les fichiers les plus volumineux (Scripts\jquery-ui-1.8.11.min.js et Scripts\jquery-1.7.1.min.js) sont déjà minifiés. Remarque : Les minutages sur l’exemple de programme utilisaient l’outil Fiddler pour simuler un réseau lent. (À partir de Fiddler Menu Règles , sélectionnez Performances , puis Simuler les vitesses du modem.)

Débogage groupé et minified JavaScript

Il est facile de déboguer votre Code JavaScript dans un environnement de développement (où l’élément de compilation du fichier Web.config est défini debug="true" sur ) car les fichiers JavaScript ne sont pas groupés ou minifiés. Vous pouvez également déboguer une build de mise en production où vos fichiers JavaScript sont groupés et minifiés. À l’aide des outils de développement IE F12, vous déboguez une fonction JavaScript incluse dans un bundle minified à l’aide de l’approche suivante :

  1. Sélectionnez l’onglet Script , puis sélectionnez le bouton Démarrer le débogage .
  2. Sélectionnez le bundle contenant la fonction JavaScript que vous souhaitez déboguer à l’aide du bouton ressources.
    Capture d’écran montrant l’onglet Script de l’outil développeur I E F 12. La zone d’entrée de script de recherche, un bundle et une fonction de script Java sont mises en surbrillance.
  3. Mettez en forme le code JavaScript minifié en sélectionnant le boutonImage montrant l’icône du bouton Configuration. Configuration, puis en sélectionnant Format JavaScript.
  4. Dans la zone d’entrée de script de recherche, sélectionnez le nom de la fonction à déboguer. Dans l’image suivante, AddAltToImg a été entré dans la zone d’entrée de script de recherche.
    Capture d’écran montrant l’onglet Script de l’outil développeur I E F 12. La zone d’entrée de script de recherche avec Ajout de alt à lmg entrée dans celle-ci est mise en surbrillance.

Pour plus d’informations sur le débogage avec les outils de développement F12, consultez l’article MSDN Utilisation des outils de développement F12 pour déboguer des erreurs JavaScript.

Contrôle du regroupement et de la minification

Le regroupement et la minification sont activés ou désactivés en définissant la valeur de l’attribut de débogage dans l’élément de compilation dans le fichier Web.config. Dans le code XML suivant, debug la valeur true est définie pour que le regroupement et la minification soient désactivés.

<system.web>
    <compilation debug="true" />
    <!-- Lines removed for clarity. -->
</system.web>

Pour activer le regroupement et la minification, définissez la debug valeur sur « false ». Vous pouvez remplacer le paramètre Web.config par la EnableOptimizations propriété de la BundleTable classe. Le code suivant active le regroupement et la minification et remplace n’importe quel paramètre dans le fichier Web.config .

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

    // Code removed for clarity.
    BundleTable.EnableOptimizations = true;
}

Remarque

Sauf si EnableOptimizations l’attribut de débogage ou l’attribut true de débogage dans l’élément de compilation du fichier Web.config est défini falsesur , les fichiers ne seront pas regroupés ou minifiés. En outre, la version .min des fichiers ne sera pas utilisée, les versions de débogage complètes seront sélectionnées. EnableOptimizationsremplace l’attribut de débogage dans l’élément de compilation dans le fichier Web.config

Utilisation d’un regroupement et d’une minification avec ASP.NET Web Forms et pages web

Utilisation du regroupement et de la minification avec ASP.NET MVC

Dans cette section, nous allons créer un projet ASP.NET MVC pour examiner le regroupement et la minification. Tout d’abord, créez un projet Internet MVC ASP.NET nommé MvcBM sans modifier les valeurs par défaut.

Ouvrez le fichier App\_Start\BundleConfig.cs et examinez la RegisterBundles méthode utilisée pour créer, inscrire et configurer des bundles. Le code suivant montre une partie de la RegisterBundles méthode.

public static void RegisterBundles(BundleCollection bundles)
{
     bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                 "~/Scripts/jquery-{version}.js"));
         // Code removed for clarity.
}

Le code précédent crée un bundle JavaScript nommé ~/bundles/jquery qui inclut tous les éléments appropriés (c’est-à-dire déboguer ou minifié, mais pas .fichiers vsdoc) dans le dossier Scripts qui correspondent à la chaîne de caractères génériques « ~/Scripts/jquery-{version}.js ». Pour ASP.NET MVC 4, cela signifie qu’avec une configuration de débogage, le fichier jquery-1.7.1.js sera ajouté au bundle. Dans une configuration de mise en production, jquery-1.7.1.min.js sera ajouté. Le framework de regroupement suit plusieurs conventions courantes telles que :

  • Sélection du fichier .min » pour la mise en production lorsque FileX.min.js et FileX.js existent.
  • Sélection de la version non « .min » pour le débogage.
  • Ignorer les fichiers « -vsdoc » (tels que jquery-1.7.1-vsdoc.js), qui sont utilisés uniquement par IntelliSense.

La {version} correspondance de caractères génériques indiquée ci-dessus est utilisée pour créer automatiquement un bundle jQuery avec la version appropriée de jQuery dans votre dossier Scripts . Dans cet exemple, l’utilisation d’une carte générique offre les avantages suivants :

  • Vous permet d’utiliser NuGet pour effectuer une mise à jour vers une version jQuery plus récente sans modifier le code de regroupement précédent ou les références jQuery dans vos pages d’affichage.
  • Sélectionne automatiquement la version complète pour les configurations de débogage et la version .min » pour les builds de mise en production.

Utilisation d’un CDN

Le code suivant remplace le bundle jQuery local par un bundle jQuery CDN.

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

    bundles.UseCdn = true;   //enable CDN support

    //add link to jquery on the CDN
    var jqueryCdnPath = "https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.min.js";

    bundles.Add(new ScriptBundle("~/bundles/jquery",
                jqueryCdnPath).Include(
                "~/Scripts/jquery-{version}.js"));

    // Code removed for clarity.
}

Dans le code ci-dessus, jQuery sera demandé à partir du CDN alors qu’en mode mise en production et la version de débogage de jQuery sera récupérée localement en mode débogage. Lorsque vous utilisez un CDN, vous devez disposer d’un mécanisme de secours en cas d’échec de la requête CDN. Le fragment de balisage suivant à partir de la fin du fichier de disposition affiche le script ajouté à la demande jQuery si le CDN échoue.

</footer>

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

        <script type="text/javascript">
            if (typeof jQuery == 'undefined') {
                var e = document.createElement('script');
                e.src = '@Url.Content("~/Scripts/jquery-1.7.1.js")';
                e.type = 'text/javascript';
                document.getElementsByTagName("head")[0].appendChild(e);

            }
        </script> 

        @RenderSection("scripts", required: false)
    </body>
</html>

Création d’un bundle

La méthode de classe Include Bundle prend un tableau de chaînes, où chaque chaîne est un chemin d’accès virtuel à la ressource. Le code suivant de la RegisterBundles méthode dans le fichier App\_Start\BundleConfig.cs montre comment plusieurs fichiers sont ajoutés à un bundle :

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 méthode de classe IncludeDirectory Bundle est fournie pour ajouter tous les fichiers d’un répertoire (et éventuellement tous les sous-répertoires) qui correspondent à un modèle de recherche. L’API de classe IncludeDirectory Bundle est illustrée ci-dessous :

public Bundle IncludeDirectory(
    string directoryVirtualPath,  // The Virtual Path for the directory.
    string searchPattern)         // The search pattern.

public Bundle IncludeDirectory(
    string directoryVirtualPath,  // The Virtual Path for the directory.
    string searchPattern,         // The search pattern.
    bool searchSubdirectories)    // true to search subdirectories.

Les bundles sont référencés dans les vues à l’aide de la méthode Render (Styles.Render pour CSS et Scripts.Render Pour JavaScript). Le balisage suivant à partir du fichier Views\Shared\_Layout.cshtml montre comment les vues de projet Internet ASP.NET référencent les bundles CSS et JavaScript par défaut.

<!DOCTYPE html>
<html lang="en">
<head>
    @* Markup removed for clarity.*@    
    @Styles.Render("~/Content/themes/base/css", "~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    @* Markup removed for clarity.*@
   
   @Scripts.Render("~/bundles/jquery")
   @RenderSection("scripts", required: false)
</body>
</html>

Notez que les méthodes Render acceptent un tableau de chaînes. Vous pouvez donc ajouter plusieurs bundles dans une seule ligne de code. Vous souhaiterez généralement utiliser les méthodes Render qui créent le code HTML nécessaire pour référencer la ressource. Vous pouvez utiliser la Url méthode pour générer l’URL de la ressource sans le balisage nécessaire pour référencer la ressource. Supposons que vous vouliez utiliser le nouvel attribut asynchrone HTML5. Le code suivant montre comment référencer la modernisation à l’aide de la Url méthode.

<head>
    @*Markup removed for clarity*@
    <meta charset="utf-8" />
    <title>@ViewBag.Title - MVC 4 B/M</title>
    <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    <meta name="viewport" content="width=device-width" />
    @Styles.Render("~/Content/css")

   @* @Scripts.Render("~/bundles/modernizr")*@

    <script src='@Scripts.Url("~/bundles/modernizr")' async> </script>
</head>

Utilisation du caractère générique « * » pour sélectionner des fichiers

Le chemin d’accès virtuel spécifié dans la Include méthode et le modèle de recherche de la IncludeDirectory méthode peuvent accepter un caractère générique « * » comme préfixe ou suffixe dans le dernier segment de chemin d’accès. La chaîne de recherche ne respecte pas la casse. La IncludeDirectory méthode a la possibilité de rechercher des sous-répertoires.

Considérez un projet avec les fichiers JavaScript suivants :

  • Scripts\Common\AddAltToImg.js
  • Scripts\Common\ToggleDiv.js
  • Scripts\Common\ToggleImg.js
  • Scripts\Common\Sub1\ToggleLinks.js

dir imag

Le tableau suivant montre les fichiers ajoutés à un bundle à l’aide du caractère générique, comme indiqué ci-dessous :

Appeler Fichiers ajoutés ou exception déclenchés
Include(« ~/Scripts/Common/*.js ») AddAltToImg.js, ToggleDiv.js, ToggleImg.js
Include(« ~/Scripts/Common/T*.js ») Exception de modèle non valide. Le caractère générique n’est autorisé que sur le préfixe ou le suffixe.
Include(« ~/Scripts/Common/*og.* ») Exception de modèle non valide. Un seul caractère générique est autorisé.
Include(« ~/Scripts/Common/T* ») ToggleDiv.js, ToggleImg.js
Include(« ~/Scripts/Common/* ») Exception de modèle non valide. Un segment générique pur n’est pas valide.
IncludeDirectory(« ~/Scripts/Common », « T* ») ToggleDiv.js, ToggleImg.js
IncludeDirectory(« ~/Scripts/Common », « T* », true) ToggleDiv.js, ToggleImg.js, ToggleLinks.js

L’ajout explicite de chaque fichier à un bundle est généralement le choix par rapport au chargement générique de fichiers pour les raisons suivantes :

  • L’ajout de scripts par défaut par caractère générique pour les charger par ordre alphabétique, ce qui n’est généralement pas ce que vous voulez. Les fichiers CSS et JavaScript doivent souvent être ajoutés dans un ordre spécifique (non alphabétique). Vous pouvez atténuer ce risque en ajoutant une implémentation IBundleOrderer personnalisée, mais l’ajout explicite de chaque fichier est moins sujette à des erreurs. Par exemple, vous pouvez ajouter de nouvelles ressources à un dossier à l’avenir, ce qui peut vous obliger à modifier votre implémentation IBundleOrderer .

  • Afficher des fichiers spécifiques ajoutés à un répertoire à l’aide du chargement de caractères génériques peut être inclus dans toutes les vues référençant ce bundle. Si le script spécifique d’affichage est ajouté à un bundle, vous pouvez obtenir une erreur JavaScript sur d’autres vues qui référencent le bundle.

  • Les fichiers CSS qui importent d’autres fichiers entraînent le chargement des fichiers importés deux fois. Par exemple, le code suivant crée un bundle avec la plupart des fichiers CSS du thème de l’interface utilisateur jQuery chargés deux fois.

    bundles.Add(new StyleBundle("~/jQueryUI/themes/baseAll")
        .IncludeDirectory("~/Content/themes/base", "*.css"));
    

    Le sélecteur de caractères génériques « *.css » introduit chaque fichier CSS dans le dossier, y compris le fichier Content\themes\base\jquery.ui.all.css . Le fichier jquery.ui.all.css importe d’autres fichiers CSS.

Mise en cache de bundle

Les bundles définissent l’en-tête HTTP Expire un an à partir de la création de l’offre groupée. Si vous accédez à une page précédemment affichée, Fiddler affiche IE ne fait pas de demande conditionnelle pour le bundle, autrement dit, il n’existe aucune requête HTTP GET d’Internet Explorer pour les bundles et aucune réponse HTTP 304 du serveur. Vous pouvez forcer Internet Explorer à effectuer une demande conditionnelle pour chaque bundle avec la clé F5 (ce qui entraîne une réponse HTTP 304 pour chaque bundle). Vous pouvez forcer une actualisation complète à l’aide de ^F5 (ce qui entraîne une réponse HTTP 200 pour chaque bundle.)

L’image suivante montre l’onglet Mise en cache du volet réponse Fiddler :

Image de mise en cache fiddler

Requête.
http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81
est destiné à l’ensemble AllMyScripts et contient une paire de chaînes de requête v=r0sLDicvP58AIXN\_mc3QdyVvVj5euZNzdsa2N1PKvb81. La chaîne de requête v a un jeton de valeur qui est un identificateur unique utilisé pour la mise en cache. Tant que l’offre groupée ne change pas, l’application ASP.NET demande le bundle AllMyScripts à l’aide de ce jeton. Si un fichier dans le bundle change, l’infrastructure d’optimisation ASP.NET génère un nouveau jeton, garantissant que les demandes de navigateur pour le bundle obtiennent le dernier bundle.

Si vous exécutez les outils de développement IE9 F12 et accédez à une page précédemment chargée, Internet Explorer affiche incorrectement les requêtes GET conditionnelles adressées à chaque bundle et le serveur retournant HTTP 304. Vous pouvez lire pourquoi IE9 a des problèmes pour déterminer si une demande conditionnelle a été effectuée dans l’entrée de blog à l’aide des CDN et expire pour améliorer les performances du site web.

LESS, CoffeeScript, SCSS, Sass Bundling.

L’infrastructure de regroupement et de minification fournit un mécanisme permettant de traiter des langages intermédiaires tels que SCSS, Sass, LESS ou Coffeescript, et d’appliquer des transformations telles que la minification au bundle obtenu. Par exemple, pour ajouter des fichiers .moins à votre projet MVC 4 :

  1. Créez un dossier pour votre contenu LESS. L’exemple suivant utilise le dossier Content\MyLess .

  2. Ajoutez le package NuGet .moins sans point à votre projet.
    Installation sans points NuGet

  3. Ajoutez une classe qui implémente l’interface IBundleTransform . Pour la transformation .moins, ajoutez le code suivant à votre projet.

    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";
        }
    }
    
  4. Créez un ensemble de fichiers LESS avec la LessTransform transformation CssMinify et la transformation CssMinify . Ajoutez le code suivant à la RegisterBundles méthode dans le fichier App\_Start\BundleConfig.cs .

    var lessBundle = new Bundle("~/My/Less").IncludeDirectory("~/My", "*.less");
    lessBundle.Transforms.Add(new LessTransform());
    lessBundle.Transforms.Add(new CssMinify());
    bundles.Add(lessBundle);
    
  5. Ajoutez le code suivant à toutes les vues qui référencent le bundle LESS.

    @Styles.Render("~/My/Less");
    

Considérations relatives à l’offre groupée

Une bonne convention à suivre lors de la création d’offres groupées consiste à inclure « bundle » comme préfixe dans le nom de l’offre groupée. Cela empêche un conflit de routage possible.

Une fois que vous avez mis à jour un fichier dans un bundle, un nouveau jeton est généré pour le paramètre de chaîne de requête groupée et le bundle complet doit être téléchargé la prochaine fois qu’un client demande une page contenant le bundle. Dans le balisage traditionnel où chaque ressource est répertoriée individuellement, seul le fichier modifié serait téléchargé. Les ressources qui changent fréquemment peuvent ne pas être de bons candidats à un regroupement.

Le regroupement et la minimisation améliorent principalement le temps de chargement de la demande de la première page. Une fois qu’une page web a été demandée, le navigateur met en cache les ressources (JavaScript, CSS et images) afin que le regroupement et la minification ne fournissent aucune amélioration des performances lors de la demande de la même page, ou des pages sur le même site demandant les mêmes ressources. Si vous ne définissez pas correctement l’en-tête expire sur vos ressources et que vous n’utilisez pas de regroupement et de minification, les navigateurs marquent l’heuristique d’actualisation des ressources après quelques jours et le navigateur nécessite une demande de validation pour chaque ressource. Dans ce cas, le regroupement et la minification fournissent une augmentation des performances après la première demande de page. Pour plus d’informations, consultez le blog Using CDN and Expires to Improve Web Site Performance.

La limitation du navigateur de six connexions simultanées par nom d’hôte peut être atténuée à l’aide d’un CDN. Étant donné que le CDN aura un nom d’hôte différent de celui de votre site d’hébergement, les demandes de ressources provenant du CDN ne sont pas comptabilisées par rapport aux six connexions simultanées limitées à votre environnement d’hébergement. Un CDN peut également fournir des avantages courants de mise en cache des packages et de mise en cache de périphérie.

Les bundles doivent être partitionnés par des pages qui en ont besoin. Par exemple, le modèle MVC par défaut ASP.NET pour une application Internet crée un bundle de validation jQuery distinct de jQuery. Étant donné que les vues par défaut créées n’ont aucune entrée et ne publient pas de valeurs, elles n’incluent pas le bundle de validation.

L’espace System.Web.Optimization de noms est implémenté dans System.Web.Optimization.dll. Il tire parti de la bibliothèque WebGrease (WebGrease.dll) pour les fonctionnalités de minification, qui utilisent à leur tour Antlr3.Runtime.dll.

J’utilise Twitter pour créer des billets rapides et partager des liens. Mon handle Twitter est: @RickAndMSFT

Ressources supplémentaires

Contributeurs