Accueil Nos publications Blog [ASP.NET Core] Le bundling et la minification

[ASP.NET Core] Le bundling et la minification

ASP.NET logoDepuis la version 4 d’ASP.NET MVC, Microsoft a mis à disposition des développeurs une fonctionnalité permettant une approche simplifiée de la gestion du contenu CSS ou JavaScript devant être téléchargé par les navigateurs des clients. Avec ASP.NET Core, l’approche change à nouveau et les développeurs vont devoir se mettre à niveau.

Coup d’oeil à l’approche historique

Depuis ASP.NET MVC 4 donc, un fichier BundleConfig.cs avait fait son apparition dans nos projets. Celui-ci permettait l’instanciation d’une collection de bundles.

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

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

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

        bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
                  "~/Scripts/bootstrap.js",
                  "~/Scripts/respond.js"));

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

Attention, on parle de bundles mais techniquement, le traitement effectué n’était pas que du bundling. En effet, ce terme désigne le regroupement de plusieurs fichiers afin de ne faire descendre qu’une seule ressource chez le client. Cela permet de diminuer le nombre de requêtes HTTP entre le client et le serveur. En pratique, une étape supplémentaire était réalisée par le mécanisme d’ASP.NET MVC : la transformation. Dans le cas des fichiers CSS ou JS, la partie transformation se résumait généralement à un processus de minification (suppression des commentaires et réduction du code en enlevant tous les éléments inutiles).

A noter que le mécanisme est tel qu’il est entièrement extensible. Cela signifie qu’il est possible de créer de nouveaux types de bundles – avec leur propre processus de transformation. Un cas fréquent étant la gestion des fichiers LESS – la transformation vers du CSS étant alors prise en charge par le serveur afin de transmettre du contenu compréhensible par les navigateurs des clients.

L’utilisation d’un bundle se faisant alors simplement dans une vue via les méthodes Styles.Render ou Scripts.Render selon s’il s’agit d’un bundle de CSS ou JavaScript.

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>

Ensuite, ASP.NET MVC va automatiquement regrouper les ressources sur le site s’il est lancé sans être en mode debug. A l’inverse, si le mode debug est actif, alors l’optimisation des bundles n’est pas utilisée et les ressources sont téléchargées de manière isolée par les clients et aucune minification n’y est apportée.

Dans le cas où l’optimisation est activée, l’exemple précédent génère la sortie ci-dessous. Notez qu’il n’y a bien qu’une seule ligne d’inclusion par bundle, que chaque bundle est adressée par une URL composée du nom du bundle et d’une clé calculée par un hash du contenu du bundle, et qui permet d’invalider le cache des navigateurs des clients.

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home Page - My ASP.NET Application</title>
    <link href="/Content/css?v=MDbdFKJHBa_ctS5x4He1bMV0_RjRq8jpcIAvPpKiN6U1" rel="stylesheet"/>
    <script src="/bundles/modernizr?v=wBEWDufH_8Md-Pbioxomt90vm6tJN2Pyy9u9zHtWsPo1"></script>
</head>

Les nouveaux outils d’ASP.NET Core

Avec ASP.NET Core, Microsoft a pris un nouveau virage en adoptant des outils existants et déjà très populaires dans le monde du développement Web, et ce bien au-delà des frontières de .NET. Rien n’oblige à utiliser ces outils, mais Microsoft les a intégrés de manière native à Visual Studio et les développeurs gagneront donc forcément à les utiliser – même si cela nécessite une phase de découverte et de prise en main.

A noter que dans le cas du bundling, la solution présentée en introduction de ce billet n’existe plus dans ASP.NET MVC 6. Il faudra donc lui préférer une solution basée sur ces nouveaux outils.

Parmi ces derniers, les deux ci-dessous sont ceux qui vont nous permettre de mettre en place une nouvelle solution pour le bundling de nos CSS / JavaScript :

  • npm / Node Package Manager. Il permet de référencer et de récupérer automatiquement des modules fonctionnant avec le Framework NodeJS. A noter que le dnpm (anciennement kpm) d’ASP.NET Core fonctionne exactement sur le même principe. Dans les deux cas, on trouvera donc un fichier json qui liste tous les paquets qui doivent être téléchargés.
  • Gulp / Grunt. Il s’agit de task runners. C’est à dire d’outils permettant d’automatiser des tâches répétitives. Les deux peuvent être utilisés avec Visual Studio, le choix de l’un ou de l’autre reste donc une question de préférence, ou des tâches que vous souhaitez utiliser dans vos projets.

Les outils Gulp et Grunt ont en fait besoin de NPM pour pouvoir être récupérés et pour pouvoir fonctionner. C’est le fichier package.json qui permet d’exprimer les dépendances qui doivent être récupérées au travers de NPM.

{
  "name": "ASP.NET",
  "version": "0.0.0",
  "devDependencies": {
    "gulp": "3.8.11",
    "gulp-concat": "2.5.2",
    "gulp-cssmin": "0.1.7",
    "gulp-uglify": "1.2.0",
    "rimraf": "2.2.8"
  }
}

Les trois tâches qui vont nous permettre de remplacer les bundles sont les tâches gulp-concat, gulp-cssmin et gulp-uglify. Dès que la sauvegarde du fichier est faite, Visual Studio va automatiquement récupérer ces paquets. Ils sont visibles sous le noeud Dependencies / NPM dans l’explorateur de solutions.

L’exemple ci-dessous est un script gulp qui va prendre tous les fichiers JS présents dans le répertoire js (ou les répertoires de plus bas niveaux). Il va les concaténer (tâche concat) puis les minifier (tâche uglify). Le tout sera mis dans un fichier site.min.js à la racine du répertoire js.

Notez que j’y déclare une tâche nommée bundle et que c’est la fonction qui lui est associée qui va faire les différents traitements. Notez également la syntaxe de gulp qui prend des fichiers en entrée puis les fait passer dans les différentes tâches via la méthode pipe.

Avec Visual Studio 2015, la définition des tâches gulp se fait dans un fichier gulpfile.js.

var gulp = require("gulp"),
    concat = require("gulp-concat"),
    cssmin = require("gulp-cssmin"),
    uglify = require("gulp-uglify"),
    project = require("./project.json");

var paths = {
    webroot: "./" + project.webroot + "/"
};

paths.js = paths.webroot + "js/**/*.js";
paths.minJs = paths.webroot + "js/**/*.min.js";
paths.concatJsDest = paths.webroot + "js/site.min.js";

gulp.task("bundle", function () {
    gulp.src([paths.js, "!" + paths.minJs], { base: "." })
        .pipe(concat(paths.concatJsDest))
        .pipe(uglify())
        .pipe(gulp.dest("."));
});

Visual Studio possède une nouvelle fenêtre pour lancer l’exécution des tâches ou pour les lier à des événements particuliers du cycle de développement (pre build, post build, clean, etc.). Le fonctionnement est assez similaire aux points d’accroche que l’on connait avec MSBuild.

La fenêtre en question s’appelle Task Runner Explorer. Elle est visible sur la capture d’écran ci-dessous.

A gauche, les différentes tâches présentes dans le projet sont listées. Pour en exécuter une, il suffit simplement de faire un clic droit sur le nom de la tâche et de cliquer sur Run. Pour la lier à un événement, il de faire un clic droit sur la tâche, puis de cliquer sur Bindings et enfin de choisir l’événement désiré.

Le fichier gulpfile.js est alors modifié et c’est simplement le commentaire ajouté en tout début de fichier qui va faire le binding.

/// <binding BeforeBuild='bundle' />

var gulp = require("gulp"),
    concat = require("gulp-concat"),
    cssmin = require("gulp-cssmin"),
    uglify = require("gulp-uglify"),
    project = require("./project.json");

var paths = {
    webroot: "./" + project.webroot + "/"
};

paths.js = paths.webroot + "js/**/*.js";
paths.minJs = paths.webroot + "js/**/*.min.js";
paths.concatJsDest = paths.webroot + "js/site.min.js";

gulp.task("bundle", function () {
    gulp.src([paths.js, "!" + paths.minJs], { base: "." })
        .pipe(concat(paths.concatJsDest))
        .pipe(uglify())
        .pipe(gulp.dest("."));
});

Il ne reste plus qu’à introduire le script généré en sortie sur les pages de notre site.

<script src="~/js/site.min.js"></script>

Gérer une collection d’environnements

Les bundles tels qu’ils sont utilisables avec ASP.NET MVC 5 permettent de gérer deux profils d’exécution. L’un où l’optimisation est active, c’est celui qui est généralement utilisé en production. Un second profil où l’optimisation n’est pas active, c’est celui qui est généralement utilisé sur les postes de développement car il facilite le debugage des scripts. Le passage de l’un des profils à l’autre se faisant en fonction de la valeur de l’attribut debug du noeud compilation du web.config.

Avec les nouvelles technologies que nous venons de découvrir, la gestion de ces profils d’exécution est un peu altérée. Il est n’est plus possible d’utiliser le noeud compilation du fichier web.config. Cependant, il est possible d’utiliser les variables d’environnement, ASP.NET Core les rendant simples d’accès.

En se rendant dans les propriétés du projet web, puis en cliquant sur l’onglet Debug, l’écran ci-dessous devient visible. C’est la variable ASPNET_ENV qui nous intéresse. Elle a ici la valeur “Development”, mais on pourrait imaginer qu’elle prenne pour valeur “Staging” ou encore “Production”.

Puis, il est possible d’utiliser le tag helper environnement afin de conditionner l’inclusion des scripts js dans nos pages. Ce tag se branche alors directement sur la variable d’environnement ASPNET_ENV. On retrouve ici les deux profils, celui de développement qui inclut tous les scripts de manière unitaire, et celui de production qui n’inclut que le script transformé.

<environment names="Development">
    <script src="~/js/a.js"></script>
    <script src="~/js/b.js"></script>
</environment>
<environment names="Staging,Production">
    <script src="~/js/site.min.js"></script>
</environment>

Conclusion

Ce billet risque d’en effrayer plus d’un. Il est toujours difficile d’admettre qu’après avoir passé du temps à appréhender et connaître une technologie, celle-ci évolue et change profondément. C’est le cas d’ASP.NET Core et le bundling n’est qu’un exemple parmi toutes les fonctionnalités qui doivent être repensées. Certes, cette nouvelle approche risque de troubler les développeurs .NET purs et durs. Cependant, il faut voir le positif, l’intégration de tasks runners tels que Gulp et Grunt permet de s’ouvrir à une communauté de développeurs bien plus grande, et à un ensemble de tâches réutilisables bien plus grand que ce qui était mis à notre disposition jusqu’à présent.