Creazione di bundle e minimizzazione

di Rick Anderson

L'aggregazione e la minificazione sono due tecniche che è possibile usare in ASP.NET 4,5 per migliorare il tempo di caricamento delle richieste. Il raggruppamento e la minificazione migliorano il tempo di caricamento riducendo il numero di richieste al server e riducendo le dimensioni degli asset richiesti , ad esempio CSS e JavaScript.

La maggior parte dei principali browser correnti limita il numero di connessioni simultanee per ogni nome host a sei. Ciò significa che durante l'elaborazione di sei richieste, le richieste aggiuntive per gli asset in un host verranno accodate dal browser. Nell'immagine seguente, le schede di rete degli strumenti di sviluppo IE F12 mostrano la tempistica per gli asset richiesti dalla visualizzazione Informazioni di un'applicazione di esempio.

B/M

Le barre grigie mostrano l'ora in cui la richiesta viene accodata dal browser in attesa del limite di connessione di sei. La barra gialla è il tempo di richiesta per il primo byte, ovvero il tempo impiegato per inviare la richiesta e ricevere la prima risposta dal server. Le barre blu mostrano il tempo impiegato per ricevere i dati di risposta dal server. È possibile fare doppio clic su un asset per ottenere informazioni dettagliate sulla tempistica. Ad esempio, l'immagine seguente mostra i dettagli relativi al tempo per il caricamento del file /Script/MyScripts/JavaScript6.js .

Screenshot che mostra la scheda A S P dot NET developer tools network with asset request URL (URL richiesta asset) nella colonna sinistra e i relativi tempi nella colonna destra.

L'immagine precedente mostra l'evento Start , che fornisce l'ora in cui la richiesta è stata accodata a causa del limite del browser al numero di connessioni simultanee. In questo caso, la richiesta è stata accodata per 46 millisecondi in attesa del completamento di un'altra richiesta.

Impacchettare

Il raggruppamento è una nuova funzionalità in ASP.NET 4.5 che semplifica la combinazione o l'aggregazione di più file in un singolo file. È possibile creare bundle CSS, JavaScript e altri bundle. Meno file significano meno richieste HTTP e che possono migliorare le prestazioni del carico di prima pagina.

L'immagine seguente mostra la stessa visualizzazione temporale della visualizzazione Informazioni mostrata in precedenza, ma questa volta con raggruppamento e minificazione abilitata.

Screenshot che mostra la scheda dettagli della tempistica di un asset negli strumenti di sviluppo I E F 12. L'evento Start è evidenziato.

Minimizzazione

La minificazione esegue diverse ottimizzazioni di codice per script o css, ad esempio la rimozione di spazi vuoti e commenti non necessari e l'abbreviazione dei nomi delle variabili a un carattere. Prendere in considerazione la funzione JavaScript seguente.

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

Dopo la minificazione, la funzione viene ridotta al seguente:

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

Oltre a rimuovere i commenti e gli spazi vuoti non necessari, i parametri e i nomi delle variabili seguenti sono stati rinominati (abbreviati) come segue:

Originale Renamed
imageTagAndImageID n
imageContext t
imageElement i

Impatto del raggruppamento e della minificazione

La tabella seguente mostra diverse differenze importanti tra l'elenco di tutti gli asset singolarmente e l'uso del raggruppamento e della minificazione (B/M) nel programma di esempio.

Uso di B/M Senza B/M Modifica
Richieste di file 9 34 256%
KB Inviato 3.26 11.92 266%
KB ricevuta 388.51 530 36%
Tempo di caricamento 510 MS 780 MS 53%

I byte inviati hanno avuto una riduzione significativa con raggruppamento come browser sono abbastanza dettagliati con le intestazioni HTTP applicate alle richieste. La riduzione dei byte ricevuti non è così grande perché i file più grandi (Scripts\jquery-ui-1.8.11.min.js e Scripts\jquery-1.7.1.min.js) sono già minificati. Nota: i tempi nel programma di esempio hanno usato lo strumento Fiddler per simulare una rete lenta. Dal menu Regole di Fiddler selezionare Prestazioni e quindi Simulare velocità del modem.

Debug di JavaScript in bundle e minified

È facile eseguire il debug di JavaScript in un ambiente di sviluppo (dove l'elemento di compilazione nel file diWeb.config è impostato su debug="true" ) perché i file JavaScript non sono raggruppati o minificati. È anche possibile eseguire il debug di una build di versione in cui i file JavaScript vengono raggruppati e minificati. Usando gli strumenti di sviluppo IE F12, eseguire il debug di una funzione JavaScript inclusa in un bundle minified usando l'approccio seguente:

  1. Selezionare la scheda Script e quindi selezionare il pulsante Avvia debug .
  2. Selezionare il bundle contenente la funzione JavaScript che si vuole eseguire il debug usando il pulsante asset.
    Screenshot che mostra la scheda Script dello strumento di sviluppo I E F 12. La casella di input dello script di ricerca, un bundle e una funzione Script Java sono evidenziate.
  3. Formattare javaScript minified selezionando il pulsante Configurazione immagine che mostra l'icona del pulsante Configurazione. Selezionare Formato JavaScript.
  4. Nella casella di input dello script di ricerca selezionare il nome della funzione da eseguire il debug. Nell'immagine seguente , AddAltToImg è stato immesso nella casella di input dello script di ricerca .
    Screenshot che mostra la scheda Script dello strumento di sviluppo I E F 12. La casella di input Script di ricerca con Aggiungi alt a lmg immessa è evidenziata.

Per altre informazioni sul debug con gli strumenti di sviluppo F12, vedere l'articolo MSDN Usando gli strumenti per sviluppatori F12 per eseguire il debug di errori JavaScript.

Controllo del raggruppamento e della minificazione

Il raggruppamento e la minificazione sono abilitati o disabilitati impostando il valore dell'attributo di debug nell'elemento di compilazione nel file Web.config . Nel codice XML seguente, è impostato su true in debug modo che la creazione di aggregazioni e la minificazione sia disabilitata.

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

Per abilitare il raggruppamento e la minificazione, impostare il debug valore su "false". È possibile eseguire l'override dell'impostazione Web.config con la EnableOptimizations proprietà nella BundleTable classe. Il codice seguente consente di raggruppare e minificare e eseguire l'override di qualsiasi impostazione nel file diWeb.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;
}

Nota

A meno che EnableOptimizations non sia true o l'attributo di debug nell'elemento di compilazione nel file Web.config sia impostato su false, i file non verranno raggruppati o minificati. Inoltre, la versione min dei file non verrà usata, verranno selezionate le versioni di debug complete. EnableOptimizationsesegue l'override dell'attributo debug nell'elemento di compilazione nel file diWeb.config

Uso del raggruppamento e della minificazione con Web Forms ASP.NET e pagine Web

Uso del raggruppamento e della minificazione con ASP.NET MVC

In questa sezione verrà creato un progetto ASP.NET MVC per esaminare l'aggregazione e la minificazione. Creare prima di tutto un nuovo progetto Internet MVC ASP.NET denominato MvcBM senza modificare alcuna delle impostazioni predefinite.

Aprire il file App\_Start\BundleConfig.cs ed esaminare il RegisterBundles metodo usato per creare, registrare e configurare i bundle. Il codice seguente mostra una parte del RegisterBundles metodo.

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

Il codice precedente crea un nuovo bundle JavaScript denominato ~/bundles/jquery che include tutti gli elementi appropriati ,ovvero debug o minified ma non .file vsdoc) nella cartella Script che corrispondono alla stringa jolly "~/Scripts/jquery-{version}.js". Per ASP.NET MVC 4, questo significa che con una configurazione di debug, il file jquery-1.7.1.js verrà aggiunto al bundle. In una configurazione di versione, jquery-1.7.1.min.js verrà aggiunto. Il framework di raggruppamento segue diverse convenzioni comuni, ad esempio:

  • Selezionare il file ".min" per la versione quando FileX.min.js e FileX.js esiste.
  • Selezione della versione non ".min" per il debug.
  • Ignorando i file "-vsdoc" (ad esempio jquery-1.7.1-vsdoc.js), usati solo da IntelliSense.

La {version} corrispondenza con caratteri jolly mostrati sopra viene usata per creare automaticamente un bundle jQuery con la versione appropriata di jQuery nella cartella Script . In questo esempio, l'uso di una carta jolly offre i vantaggi seguenti:

  • Consente di usare NuGet per aggiornare a una versione jQuery più recente senza modificare il codice di raggruppamento precedente o i riferimenti jQuery nelle pagine di visualizzazione.
  • Seleziona automaticamente la versione completa per le configurazioni di debug e la versione "min" per le build di versione.

Uso di una rete CDN

Il codice seguente sostituisce il bundle jQuery locale con un bundle jQuery della rete 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.
}

Nel codice precedente, jQuery verrà richiesto dalla rete CDN durante la modalità di rilascio e la versione di debug di jQuery verrà recuperata localmente in modalità di debug. Quando si usa una rete CDN, è necessario disporre di un meccanismo di fallback nel caso in cui la richiesta della rete CDN abbia esito negativo. Il frammento di markup seguente dalla fine del file di layout mostra lo script aggiunto alla richiesta jQuery, se la rete CDN avrà esito negativo.

</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>

Creazione di un bundle

Il metodo della classe IncludeBundle accetta una matrice di stringhe, in cui ogni stringa è un percorso virtuale per la risorsa. Il codice seguente dal RegisterBundles metodo nel file App\_Start\BundleConfig.cs mostra come vengono aggiunti più file a 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"));

Il metodo della classe IncludeDirectoryBundle viene fornito per aggiungere tutti i file in una directory (e facoltativamente tutte le sottodirectory) che corrispondono a un modello di ricerca. L'API della classe IncludeDirectoryBundle è illustrata di seguito:

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.

I bundle vengono a cui si fa riferimento nelle visualizzazioni usando il metodo Render, (Styles.Render per CSS e Scripts.Render per JavaScript). Il markup seguente del file Views\Shared\_Layout.cshtml mostra come le visualizzazioni predefinite ASP.NET progetti Internet fanno riferimento ai bundle CSS e JavaScript.

<!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>

Si noti che i metodi di rendering accettano una matrice di stringhe, quindi è possibile aggiungere più bundle in una riga di codice. In genere si vuole usare i metodi di rendering che creano il codice HTML necessario per fare riferimento all'asset. È possibile usare il Url metodo per generare l'URL nell'asset senza il markup necessario per fare riferimento all'asset. Si supponga di voler usare il nuovo attributo asincrono HTML5. Il codice seguente illustra come fare riferimento alla modernizzazione usando il Url metodo .

<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>

Uso del carattere jolly "*" per selezionare i file

Il percorso virtuale specificato nel Include metodo e il modello di ricerca nel IncludeDirectory metodo può accettare un carattere jolly "*" come prefisso o suffisso nell'ultimo segmento di percorso. La stringa di ricerca è senza distinzione tra maiuscole e minuscole. Il IncludeDirectory metodo ha la possibilità di cercare sottodirectory.

Prendere in considerazione un progetto con i file JavaScript seguenti:

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

dir imag

La tabella seguente mostra i file aggiunti a un bundle usando il carattere jolly, come illustrato di seguito:

Chiamare File aggiunti o eccezioni generati
Include("~/Script/Common/*.js") AddAltToImg.js,ToggleDiv.js, ToggleImg.js
Include("~/Script/Common/T*.js") Eccezione di modello non valida. Il carattere jolly è consentito solo sul prefisso o sul suffisso.
Include("~/Scripts/Common/*og.*") Eccezione di modello non valida. È consentito un solo carattere jolly.
Include("~/Script/Common/T*") ToggleDiv.js, ToggleImg.js
Include("~/Script/Common/*") Eccezione di modello non valida. Un segmento jolly puro non è valido.
IncludeDirectory("~/Script/Common", "T*") ToggleDiv.js, ToggleImg.js
IncludeDirectory("~/Scripts/Common", "T*", true) ToggleDiv.js,ToggleImg.js, ToggleLinks.js

L'aggiunta esplicita di ogni file a un bundle è in genere la scelta preferita rispetto al caricamento con caratteri jolly dei file per i motivi seguenti:

  • L'aggiunta di script per impostazione predefinita con caratteri jolly per caricarli in ordine alfabetico, che in genere non è quello desiderato. I file CSS e JavaScript devono essere aggiunti spesso in un ordine specifico (non alfabetico). È possibile attenuare questo rischio aggiungendo un'implementazione IBundleOrderer personalizzata, ma aggiungendo in modo esplicito ogni file è meno soggetto a errori. Ad esempio, è possibile aggiungere nuovi asset a una cartella in futuro che potrebbe richiedere di modificare l'implementazione di IBundleOrderer .

  • Visualizzare file specifici aggiunti a una directory usando il caricamento con caratteri jolly possono essere inclusi in tutte le visualizzazioni che fanno riferimento a tale bundle. Se lo script specifico della visualizzazione viene aggiunto a un bundle, è possibile che venga visualizzato un errore JavaScript in altre visualizzazioni che fanno riferimento al bundle.

  • I file CSS che importano altri file generano due volte i file importati. Ad esempio, il codice seguente crea un bundle con la maggior parte dei file CSS del tema dell'interfaccia utente jQuery caricati due volte.

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

    Il selettore di caratteri jolly "*.css" porta in ogni file CSS nella cartella, incluso il file Content\themes\base\jquery.ui.all.css . Il file jquery.ui.all.css importa altri file CSS.

Memorizzazione nella cache dei bundle

I bundle impostano l'intestazione HTTP Scade un anno da quando viene creato il bundle. Se si passa a una pagina visualizzata in precedenza, Fiddler mostra che IE non effettua una richiesta condizionale per il bundle, ovvero non sono presenti richieste HTTP GET da Internet Explorer per i bundle e nessuna risposta HTTP 304 dal server. È possibile forzare L'internet Explorer a effettuare una richiesta condizionale per ogni bundle con la chiave F5 (con conseguente risposta HTTP 304 per ogni bundle). È possibile forzare un aggiornamento completo usando ^F5 (risultante una risposta HTTP 200 per ogni bundle).

L'immagine seguente mostra la scheda Memorizzazione nella cache del riquadro di risposta fiddler:

immagine di memorizzazione nella cache di fiddler

Richiesta.
http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81
è per il bundle AllMyScripts e contiene una coppia di stringhe di query v=r0sLDicvP58AIXN\_mc3QdyVvVj5euZNzdsa2N1PKvb81. La stringa di query v ha un token di valore che è un identificatore univoco usato per la memorizzazione nella cache. Purché il bundle non cambi, l'applicazione ASP.NET richiederà il bundle AllMyScripts usando questo token. Se qualsiasi file nel bundle cambia, il framework di ottimizzazione ASP.NET genererà un nuovo token, garantendo che le richieste del browser per il bundle otterranno il bundle più recente.

Se si eseguono gli strumenti di sviluppo IE9 F12 e si passa a una pagina caricata in precedenza, IE visualizza in modo errato le richieste GET condizionali effettuate a ogni bundle e il server che restituisce HTTP 304. È possibile leggere il motivo per cui IE9 presenta problemi che determinano se una richiesta condizionale è stata effettuata nella voce di blog Usando le reti CDN e scade per migliorare le prestazioni del sito Web.

LESS, CoffeeScript, SCSS, Sass Bundling.

Il framework di aggregazione e minificazione fornisce un meccanismo per elaborare linguaggi intermedi, ad esempio SCSS, Sass, LESS o Coffeescript, e applicare trasformazioni come minification al bundle risultante. Ad esempio, per aggiungere file con estensione meno al progetto MVC 4:

  1. Creare una cartella per il contenuto LESS. Nell'esempio seguente viene usata la cartella Content\MyLess .

  2. Aggiungere il pacchetto NuGet senza punti al progetto.
    Installazione senza punti nuGet

  3. Aggiungere una classe che implementa l'interfaccia IBundleTransform . Per la trasformazione .less, aggiungere il codice seguente al progetto.

    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. Creare un bundle di file LESS con LessTransform e la trasformazione CssMinify . Aggiungere il codice seguente al RegisterBundles metodo nel file 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. Aggiungere il codice seguente a tutte le visualizzazioni che fanno riferimento al bundle LESS.

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

Considerazioni sul bundle

Una buona convenzione da seguire quando si creano bundle consiste nell'includere "bundle" come prefisso nel nome del bundle. Ciò impedirà un possibile conflitto di routing.

Dopo aver aggiornato un file in un bundle, viene generato un nuovo token per il parametro stringa di query bundle e il bundle completo deve essere scaricato alla successiva richiesta di una pagina contenente il bundle. Nel markup tradizionale in cui ogni asset è elencato singolarmente, verrà scaricato solo il file modificato. Gli asset che cambiano spesso potrebbero non essere buoni candidati per il raggruppamento.

L'aggregazione e la minificazione migliorano principalmente il tempo di caricamento della prima richiesta di pagina. Dopo aver richiesto una pagina Web, il browser memorizza nella cache gli asset (JavaScript, CSS e immagini) in modo da non offrire un aumento delle prestazioni quando si richiede la stessa pagina o pagine nello stesso sito che richiede gli stessi asset. Se non si imposta correttamente l'intestazione di scadenza sugli asset e non si usa l'aggregazione e la minificazione, gli heuristici di aggiornamento dei browser contrassegneranno gli asset non aggiornati dopo alcuni giorni e il browser richiederà una richiesta di convalida per ogni asset. In questo caso, il raggruppamento e la minificazione forniscono un aumento delle prestazioni dopo la prima richiesta di pagina. Per informazioni dettagliate, vedere il blog Uso di reti CDN e scadenza per migliorare le prestazioni del sito Web.

La limitazione del browser di sei connessioni simultanee per ogni nome host può essere mitigata tramite una rete CDN. Poiché la rete CDN avrà un nome host diverso rispetto al sito di hosting, le richieste di asset dalla rete CDN non verranno conteggiati rispetto ai sei limiti di connessioni simultanee all'ambiente di hosting. Una rete CDN può anche offrire vantaggi comuni per la memorizzazione nella cache dei pacchetti e la memorizzazione nella cache perimetrale.

I bundle devono essere partizionati da pagine necessarie. Ad esempio, il modello di ASP.NET MVC predefinito per un'applicazione Internet crea un bundle jQuery Validation separato da jQuery. Poiché le visualizzazioni predefinite create non hanno alcun input e non pubblicano i valori, non includono il bundle di convalida.

Lo System.Web.Optimization spazio dei nomi viene implementato in System.Web.Optimization.dll. Sfrutta la libreria WebGrease (WebGrease.dll) per le funzionalità di minification, che a sua volta usa Antlr3.Runtime.dll.

Uso Twitter per creare post rapidi e condividere collegamenti. Il mio handle Twitter è: @RickAndMSFT

Risorse aggiuntive

Autori di contributi