Creazione di bundle e minimizzazione

di Rick Anderson

Il raggruppamento 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 attuali 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 that shows the A S P dot NET developer tools network tab with asset request URLs on the left column and their timings on the right column.

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 that shows an asset's timing details tab on the I E F 12 developer tools. The Start event is highlighted.

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 Rinominato
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 that shows the I E F 12 developer tool's Script tab. The Search Script input box, a bundle, and a Java Script function are highlighted.
  3. Formattare JavaScript minified selezionando il pulsanteImage that shows the Configuration button icon. Configurazione e quindi selezionando Format 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 that shows the I E F 12 developer tool's Script tab. The Search Script input box with Add Alt To lmg entered in it is highlighted.

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 MVC ASP.NET per esaminare l'aggregazione e la minificazione. Prima di tutto, creare un nuovo progetto Internet ASP.NET MVC 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 un rete CDN

Il codice seguente sostituisce il bundle jQuery locale con un bundle jQuery 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 dal rete CDN mentre in modalità di rilascio e la versione di debug di jQuery verrà recuperata localmente in modalità di debug. Quando si usa un rete CDN, è necessario disporre di un meccanismo di fallback nel caso in cui la richiesta di rete CDN abbia esito negativo. Il frammento di markup seguente dalla fine del file di layout mostra lo script aggiunto alla richiesta jQuery deve avere esito negativo rete CDN.

</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 illustra come il valore predefinito ASP.NET viste del progetto Internet fa 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.

Bundle Caching

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 Caching del riquadro di risposta fiddler:

fiddler caching image

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 senza punti NuGet con estensione meno NuGet al progetto.
    NuGet dotless install

  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 usando un rete CDN. Poiché la rete CDN avrà un nome host diverso rispetto al sito di hosting, le richieste di asset del rete CDN non verranno conteggiati rispetto ai sei limiti di connessioni simultanee all'ambiente di hosting. Un 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