Concaténer et réduire JavaScript à la volée ou au moment de la construction - ASP.NET MVC

Comme une extension de cette question ici reliant les bibliothèques JavaScript dans les contrôles utilisateur j'étais après quelques exemples de la façon dont les gens concaténent et minimisent JavaScript à la volée ou au moment de la construction. Je voudrais également voir comment cela fonctionne ensuite dans vos pages maîtres.

Cela ne me dérange pas que les fichiers spécifiques à la page soient minifiés et liés inidividuellement tels qu'ils sont actuellement (voir ci-dessous) mais tous les fichiers JavaScript sur la page principale principale (j'ai environ 5 ou 6) je voudrais concaténé et minifié.

Points Bonus pour toute personne qui intègre également la concaténation et la minification CSS! :-)

Page maître actuelle avec les fichiers JavaScript courants que je voudrais concaténés et minifiés:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<head runat="server">
    ... BLAH ...
    <asp:ContentPlaceHolder ID="AdditionalHead" runat="server" />
    ... BLAH ...
    <%= Html.CSSBlock("/styles/site.css") %>
    <%= Html.CSSBlock("/styles/jquery-ui-1.7.1.css") %>
    <%= Html.CSSBlock("/styles/jquery.lightbox-0.5.css") %>
    <%= Html.CSSBlock("/styles/ie6.css", 6) %>
    <%= Html.CSSBlock("/styles/ie7.css", 7) %>
    <asp:ContentPlaceHolder ID="AdditionalCSS" runat="server" />
</head>
<body>
    ... BLAH ...
    <%= Html.JSBlock("/scripts/jquery-1.3.2.js", "/scripts/jquery-1.3.2.min.js") %>
    <%= Html.JSBlock("/scripts/jquery-ui-1.7.1.js", "/scripts/jquery-ui-1.7.1.min.js") %>
    <%= Html.JSBlock("/scripts/jquery.validate.js", "/scripts/jquery.validate.min.js") %>
    <%= Html.JSBlock("/scripts/jquery.lightbox-0.5.js", "/scripts/jquery.lightbox-0.5.min.js") %>
    <%= Html.JSBlock("/scripts/global.js", "/scripts/global.min.js") %>
    <asp:ContentPlaceHolder ID="AdditionalJS" runat="server" />
</body>

Utilisé dans une page comme celle-ci (dont je suis content):

<asp:Content ID="signUpContent" ContentPlaceHolderID="AdditionalJS" runat="server">
    <%= Html.JSBlock("/scripts/pages/account.signup.js", "/scripts/pages/account.signup.min.js") %>
</asp:Content>


Mise à JOUR: Recommandations pour l'instant (fin 2013):

Je voudrais regarder Microsoft ASP.NET il est construit en groupage et Minification .

45
demandé sur Community 2009-05-21 02:21:33

8 réponses

Dans l'annexe de Professionnel ASP.NET 3.5 Scott Hanselman parle de Packer pour. NET. cela s'intégrera avec MSBuild et pack fichiers javascript pour les déploiements de production, etc.

7
répondu RedWolves 2009-05-20 22:41:01

, Essayez ceci:

J'ai récemment terminé un peu de recherche et de développement conséquent au travail qui va assez loin pour améliorer les performances du front-end de notre application web. Je pensais partager la solution de base ici.

La première chose évidente à faire est de comparer votre site en utilisant YSlow de Yahoo et PageSpeed de Google. Ceux-ci mettront en évidence les améliorations de performance des "fruits à faible pendaison" à faire. Sauf si vous l'avez déjà fait, les suggestions seront presque certainement inclure la combinaison, la réduction et la gzipping de votre contenu statique.

Les étapes que nous allons effectuer sont:

Écrivez un HTTPHandler personnalisé pour combiner et réduire CSS. Écrivez un HTTPHandler personnalisé pour combiner et réduire JS. Inclure un mécanisme pour s'assurer que ce qui précède ne fait leur magie que lorsque l'application n'est pas en mode débogage. Écrivez un contrôle Web côté serveur personnalisé pour maintenir facilement l'inclusion de fichiers css/js. Activer GZIP de certains types de contenu sur IIS 6. À droite, nous allons commencer avec CSSHandler.asax qui implémente L'interface. net IHttpHandler:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;

namespace WebApplication1
{
    public class CssHandler : IHttpHandler
    {
        public bool IsReusable { get { return true; } }

        public void ProcessRequest(HttpContext context)
        {
            string[] cssFiles = context.Request.QueryString["cssfiles"].Split(',');

            List<string> files = new List<string>();
            StringBuilder response = new StringBuilder();
            foreach (string cssFile in cssFiles)
            {
                if (!cssFile.EndsWith(".css", StringComparison.OrdinalIgnoreCase))
                {
                    //log custom exception
                    context.Response.StatusCode = 403;
                    return;
                }

                try
                {
                    string filePath = context.Server.MapPath(cssFile);
                    string css = File.ReadAllText(filePath);
                    string compressedCss = Yahoo.Yui.Compressor.CssCompressor.Compress(css);
                    response.Append(compressedCss);
                }
                catch (Exception ex)
                {
                    //log exception
                    context.Response.StatusCode = 500;
                    return;
                }
            }

            context.Response.Write(response.ToString());

            string version = "1.0"; //your dynamic version number 

            context.Response.ContentType = "text/css";
            context.Response.AddFileDependencies(files.ToArray());
            HttpCachePolicy cache = context.Response.Cache;
            cache.SetCacheability(HttpCacheability.Public);
            cache.VaryByParams["cssfiles"] = true;
            cache.SetETag(version);
            cache.SetLastModifiedFromFileDependencies();
            cache.SetMaxAge(TimeSpan.FromDays(14));
            cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
        }
    }
}

Ok, maintenant quelques explications:

Propriété IsReUsable:

Nous ne traitons rien de spécifique à une instance, ce qui signifie que nous pouvons réutiliser en toute sécurité la même instance du gestionnaire pour traiter plusieurs requêtes, car notre ProcessRequest est threadsafe. Plus d'info.

Méthode ProcessRequest:

Il ne se passe rien de trop mouvementé ici. Nous parcourons en boucle les fichiers CSS qui nous sont donnés (voir le CSSControl ci-dessous pour savoir comment ils entrent) et compresser chacun, en utilisant un port. net de YUICompressor de Yahoo, avant d'ajouter le contenu au flux de réponse sortant.

Le reste de la méthode traite de la configuration de certaines propriétés de mise en cache HTTP pour optimiser davantage la façon dont le client du navigateur télécharge (ou non, selon le cas) le contenu.

Nous avons mis Etags dans le code afin qu'ils puissent être les mêmes sur toutes les machines de notre batterie de serveurs. Nous définissons la réponse et le Cache dépendances sur nos fichiers réels donc, s'ils sont remplacés, le cache sera invalidé. Nous définissons la Cacheability telle que les proxies peuvent mettre en cache. Nous VaryByParams en utilisant notre attribut cssfiles, de sorte que nous pouvons mettre en cache par groupe de fichiers CSS soumis via le gestionnaire. Et voici le CSSControl, un contrôle côté serveur personnalisé héritant du. Net LiteralControl.

Avant:

<customcontrols:csscontrol id="cssControl" runat="server">
  <CustomControls:Stylesheet File="main.css" />
  <CustomControls:Stylesheet File="layout.css" />
  <CustomControls:Stylesheet File="formatting.css" />
</customcontrols:csscontrol>

Retour:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Linq;
using TTC.iTropics.Utilities;

namespace WebApplication1
{
    [DefaultProperty("Stylesheets")]
    [ParseChildren(true, "Stylesheets")]
    public class CssControl : LiteralControl
    {
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public List<Stylesheet> Stylesheets { get; set; }

        public CssControl()
        {
            Stylesheets = new List<Stylesheet>();
        }

        protected override void Render(HtmlTextWriter output)
        {
            if (HttpContext.Current.IsDebuggingEnabled)
            {
                const string format = "<link rel=\"Stylesheet\" href=\"stylesheets/{0}\"></link>";

                foreach (Stylesheet sheet in Stylesheets)
                    output.Write(format, sheet.File);
            }
            else
            {
                const string format = "<link type=\"text/css\" rel=\"Stylesheet\" href=\"stylesheets/CssHandler.ashx?cssfiles={0}&version={1}\"/>";
                IEnumerable<string> stylesheetsArray = Stylesheets.Select(s => s.File);
                string stylesheets = String.Join(",", stylesheetsArray.ToArray());
                string version = "1.00" //your version number

                output.Write(format, stylesheets, version);
            }

        }
    }

    public class Stylesheet
    {
        public string File { get; set; }
    }
}

HttpContext.Actuel.Isdebuggenabled est connecté au paramètre suivant dans votre Web.configuration:

<system.web>
  <compilation debug="false">
</system.web>

Donc, fondamentalement, si votre site est en mode débogage, vous obtenez un balisage HTML comme ceci:

<link rel="Stylesheet" href="stylesheets/formatting.css"></link>
<link rel="Stylesheet" href="stylesheets/layout.css"></link
<link rel="Stylesheet" href="stylesheets/main.css"></link>

Mais si vous êtes en mode production (debug = false), Vous obtiendrez un balisage comme ceci:

<link type="text/css" rel="Stylesheet" href="CssHandler.ashx?cssfiles=main.css,layout.css,formatting.css&version=1.0"/>

Ce dernier invoquera alors évidemment le CSSHandler, qui s'occupera de combiner, de réduire et de préparer le cache de votre contenu CSS statique.

Tout ce qui précède peut alors également être dupliqué pour votre JavaScript statique contenu:

" JSHandler.ashx:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;

namespace WebApplication1
{
    public class JSHandler : IHttpHandler
    {
        public bool IsReusable { get { return true; } }

        public void ProcessRequest(HttpContext context)
        {
            string[] jsFiles = context.Request.QueryString["jsfiles"].Split(',');

            List<string> files = new List<string>();
            StringBuilder response = new StringBuilder();

            foreach (string jsFile in jsFiles)
            {
                if (!jsFile.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
                {
                    //log custom exception
                    context.Response.StatusCode = 403;
                    return;
                }

                try
                {
                    string filePath = context.Server.MapPath(jsFile);
                    files.Add(filePath);
                    string js = File.ReadAllText(filePath);
                    string compressedJS = Yahoo.Yui.Compressor.JavaScriptCompressor.Compress(js);
                    response.Append(compressedJS);
                }
                catch (Exception ex)
                {
                    //log exception
                    context.Response.StatusCode = 500;
                    return;
                }
            }

            context.Response.Write(response.ToString());

            string version = "1.0"; //your dynamic version number here

            context.Response.ContentType = "application/javascript";
            context.Response.AddFileDependencies(files.ToArray());
            HttpCachePolicy cache = context.Response.Cache;
            cache.SetCacheability(HttpCacheability.Public);
            cache.VaryByParams["jsfiles"] = true;
            cache.VaryByParams["version"] = true;
            cache.SetETag(version);
            cache.SetLastModifiedFromFileDependencies();
            cache.SetMaxAge(TimeSpan.FromDays(14));
            cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
        }
    }
}

Et son jscontrol d'accompagnement:

Avant:

<customcontrols:JSControl ID="jsControl" runat="server">
  <customcontrols:Script File="jquery/jquery-1.3.2.js" />
  <customcontrols:Script File="main.js" />
  <customcontrols:Script File="creditcardpayments.js" />
</customcontrols:JSControl>

Retour:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Linq;

namespace WebApplication1
{
    [DefaultProperty("Scripts")]
    [ParseChildren(true, "Scripts")]
    public class JSControl : LiteralControl
    {
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public List<Script> Scripts { get; set; }

        public JSControl()
        {
            Scripts = new List<Script>();
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (HttpContext.Current.IsDebuggingEnabled)
            {
                const string format = "<script src=\"scripts\\{0}\"></script>";

                foreach (Script script in Scripts)
                    writer.Write(format, script.File);
            }
            else
            {
                IEnumerable<string> scriptsArray = Scripts.Select(s => s.File);
                string scripts = String.Join(",", scriptsArray.ToArray());
                string version = "1.0" //your dynamic version number
                const string format = "<script src=\"scripts/JsHandler.ashx?jsfiles={0}&version={1}\"></script>";

                writer.Write(format, scripts, version);
            }
        }
    }

    public class Script
    {
        public string File { get; set; }
    }
}

Activation de GZIP:

Comme le dit Jeff Atwood, l'activation de Gzip sur le serveur de votre site web est une évidence. Après quelques recherches, j'ai décidé d'activer Gzip sur les types de fichiers suivants:

.CSS .js .axd (fichiers Microsoft Javascript) .aspx (habituel ASP.NET contenu des formulaires Web) .ashx (nos gestionnaires) Pour activer la Compression HTTP sur votre site Web IIS 6.0 serveur:

Ouvrez IIS, cliquez avec le bouton droit sur Sites Web, onglet Services, activez compresser les fichiers D'Application et compresser les fichiers statiques Stop IIS Ouvrez la métabase IIS dans le bloc-notes (C:\WINDOWS\system32\inetsrv\MetaBase.xml) - et faites une sauvegarde si vous êtes nerveux à propos de ces choses Localisez et écrasez les deux éléments IIsCompressionScheme et un iiscompressionschemes avec les éléments suivants:

Et c'est tout! Cela nous a sauvé des tas de bande passante et a donné lieu à un plus réactif application web tout au long.

Profitez-en!

40
répondu Mark Gibaud 2013-09-13 18:32:25

Pourquoi ne pas utiliser le ScriptManager? Voici un MVCScriptManager qui va combiner et écraser.

14
répondu Scott Hanselman 2009-05-26 22:07:40

Utilisez un compresseur YUI ou un compresseur Dojo. Ils utilisent tous les deux le moteur D'analyse JS de Rhino qui tokenise votre code,et ne fonctionnera donc que si le code est valide. S'il y a une erreur, ils vous le feront savoir (ce qui est un joli bonus IMO!) Packer d'autre part, emballera votre code même s'il contient des erreurs.

J'utilise YUI dans tous mes projets via des scripts de construction. Ne le faites jamais à la volée, cela prend trop de temps pour faire la compression. YUI et Dojo sont tous deux basés sur Java (Ala Rhino) et si vous le faites à la volée, vous allez générer des processus d'arrière - plan pour générer la sortie-ce qui n'est pas bon pour les performances. Toujours le faire au moment de la construction.

6
répondu Bryan Migliorisi 2009-05-27 03:17:44

Rajeunissant est un grand nouveau minifier pour ASP.NET ça fait beaucoup de bruit: http://rejuice.me

Il est configuré comme un module HTTP et effectue la minification au moment de l'exécution (une fois) et met en cache la sortie.

, Il:

  • a une interface fluide pour la configuration
  • vous permet de spécifier des fichiers à réduire avec des règles génériques
  • Fonctionne sous Windows Azure
  • un peu comme par magie s'éteint dans les environnements de développement, de sorte que vous pouvez déboguer votre code javascript original (non minifié).

La configuration (faite sur ApplicationStart dans global.asax.cs) est aussi simple que:

OnRequest.ForJs("~/Combined.js")
            .Compact
            .FilesIn("~/Scripts/")
              .Matching("*.js")
            .Cache
            .Configure();
4
répondu Sam 2011-07-06 10:48:43

Voici ce que j'ai utilisé pour concaténer, compresser et mettre en cache les fichiers CSS et JS: http://gist.github.com/130913

Il faut juste Yahoo.Yui.Compresseur.dll dans le répertoire bin. Il ne se compresse pas au moment de la compilation, mais les fichiers sont mis en cache avec une dépendance de fichier, de sorte qu'ils ne sont chargés qu'une seule fois, jusqu'à ce qu'ils soient modifiés.

Ensuite, j'ajoute simplement ce code dans la :

<link rel="stylesheet" type="text/css" href="/YuiCompressor.ashx?css=reset,style,etc" />

Et ceci juste avant le

:

<script type="text/javascript" src="/YuiCompressor.ashx?js=main,other,etc"></script>

Il est conçu pour fonctionner avec plusieurs fichiers tous dans le même chemin, mais pourrait facilement être mis à niveau pour prendre en charge différents chemins.

2
répondu travis 2009-06-16 21:58:25

J'utilise une solution personnalisée basée sur MSBuild et le Minifier Microsoft Ajax. La plupart des articles de blog existants ne gèrent pas correctement certains cas tels que l'intégration avec TFS build.

Pour chaque projet web, nous créons un "wpp.cibles " fichier pour étendre le Pipeline de publication Web. Par exemple, si le projet est "site Web.csproj" créer un fichier nommé "Site web.wpp.cibles" dans le projet.

Placez le code suivant dans le fichier targets:

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath32)\PATH TO YOUR MSBUILD MINIFY TARGETS" />

  <!-- Hook up minification task to WPP build process -->
  <PropertyGroup>
    <OnAfterPipelineTransformPhase>
      $(OnAfterPipelineTransformPhase);
      MinifyResourceFiles;
    </OnAfterPipelineTransformPhase>
  </PropertyGroup>

  <!-- Define temporary location to store minified resources -->
  <PropertyGroup>
    <MinifyResourceIntermediateOutput Condition="'$(MinifyResourceIntermediateOutput)'==''">MinifyResourceFiles</MinifyResourceIntermediateOutput>
    <MinifyResourceIntermediateLocation Condition="'$(MinifyResourceIntermediateLocation)'==''">$(_WPPDefaultIntermediateOutputPath)$(MinifyResourceIntermediateOutput)</MinifyResourceIntermediateLocation>
  </PropertyGroup>

  <Target Name="MinifyResourceFiles" DependsOnTargets="PipelineCollectFilesPhase" Condition="'$(Configuration)' == 'Release'">
    <!-- Create lists of the resources to minify -->
    <!-- These extract all Javascript and CSS files from the publishing pipeline "FilesForPackagingFromProject" and create two new lists.
     The "MinifiedFile" metadata on each item contains the temporary location where the minified file will be stored -->
    <ItemGroup>
      <JavaScriptToMinify Include="@(FilesForPackagingFromProject)" 
                          Condition="'%(FilesForPackagingFromProject.Extension)' == '.js'">
        <MinifiedFile>$(MinifyResourceIntermediateLocation)\minified\%(DestinationRelativePath)</MinifiedFile>
      </JavaScriptToMinify>
      <StylesheetToMinify Include="@(FilesForPackagingFromProject)"
                          Condition="'%(FilesForPackagingFromProject.Extension)' == '.css'">
        <MinifiedFile>$(MinifyResourceIntermediateLocation)\minified\%(DestinationRelativePath)</MinifiedFile>
      </StylesheetToMinify>    
    </ItemGroup>

    <!-- Minify resources -->
    <!-- These commands should be replaced with the MSBuild Tasks used to perform your minification
         I use my own custom tasks based on the Microsoft Ajax Minifier DLL 
         The input of the minifier takes a source file directly from the project and outputs to a temporary location -->
    <MinifyJavaScript SourceFiles="@(JavaScriptToMinify)" DestinationFiles="@(JavaScriptToMinify->'%(MinifiedFile)')"
                      Comments="None" />
    <MinifyStylesheet SourceFiles="@(StylesheetToMinify)" DestinationFiles="@(StylesheetToMinify->'%(MinifiedFile)')"
                      Comments="None" />

    <!-- Remove the original source files from the packaging system and include the new minfied resources from the temporary location -->
    <ItemGroup>
      <!--Remove unminified resources from the pipeline -->
      <FilesForPackagingFromProject Remove="@(JavaScriptToMinify)" Condition="'@(JavaScriptToMinify)' != ''" />
      <FilesForPackagingFromProject Remove="@(StylesheetToMinify)" Condition="'@(StylesheetToMinify)' != ''" />
      <!--Add the minified resources at the new loction to the pipeline -->
      <FilesForPackagingFromProject Include="@(JavaScriptToMinify->'%(MinifiedFile)')" Condition="'@(JavaScriptToMinify)' != ''"/>
      <FilesForPackagingFromProject Include="@(StylesheetToMinify->'%(MinifiedFile)')" Condition="'@(StylesheetToMinify)' != ''"/>
    </ItemGroup>
  </Target>
</Project>

Le La condition "'$(Configuration') = = 'Release' " sur la cible de minification peut être modifiée en fonction de vos besoins. Il réduira automatiquement (et validera) tous les fichiers CSS et JS du projet lors de la publication, de l'empaquetage et de la construction sur le serveur.

Vous devrez peut-être activer la cible WPP "CopyWebApplication" pour les versions du serveur. Pour ce faire, définissez la propriété MSBuild UseWP_CopyWebApplication sur True et PipelineDependsOnBuild sur False. Nous les définissons dans le fichier de projet, avant le web fichier de cibles d'application est inclus.

2
répondu ShadowChaser 2011-07-22 17:36:20

Je recommande http://www.RequestReduce.com {[2] } qui minimise et combine css et javascript ainsi que des images d'arrière-plan CSS sprites et optimise leur compression PNG. Il fait tout cela au moment de l'exécution et met en cache la sortie. Il ne nécessite aucun code ou configuration au-delà de l'ajout du HttpModule. Il sert tout le contenu mis en cache avec des en-têtes et des ETags optimisés pour s'assurer que les navigateurs mettent en cache les CSS/javascript/sprites aussi longtemps que possible. Bien qu'il ne nécessite aucune configuration, il est hautement configurable et peut être configuré pour fonctionner avec un CDN et synchroniser les fichiers mis en cache à travers une batterie de serveurs web.

Tous les javascript, images et css sont récupérés via HTTP afin qu'il puisse inclure css et js de tiers et c'est aussi un excellent moyen de réduire/combiner .ressources axd comme WebResource.axd et ScriptResource.axd. Il détermine la presense de js et css via content-type afin que la ressource cible puisse avoir n'importe quelle extension (ou pas). Il fonctionne sur n'importe quelle technologie basée sur IIS y compris toutes les versions et vue moteurs de MVC, formulaires web et "pages web".

Vous pouvez télécharger à partir de http://www.RequestReduce.com, Nuget ou la fourche à partir de https://github.com/mwrock/RequestReduce.

2
répondu Matt Wrock 2011-11-10 17:35:26