Condividi tramite


Creazione di bundle e minimizzazione

di Rick Anderson

La creazione di bundle e la minificazione sono due tecniche che è possibile usare in ASP.NET 4.5 per migliorare il tempo di caricamento delle richieste. La creazione di bundle 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 F12 di Internet Explorer mostrano la tempistica per gli asset richiesti dalla visualizzazione Informazioni su 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 sei connessioni. La barra gialla è il tempo di richiesta al 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 di intervallo per il caricamento del file /Scripts/MyScripts/JavaScript6.js .

Screenshot che mostra la scheda A S P dot NET Developer Tools network con GLI URL delle richieste di asset nella colonna sinistra e i relativi intervalli nella colonna a destra.

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

Impacchettare

La creazione di bundle è una nuova funzionalità di ASP.NET 4.5 che semplifica la combinazione o l'aggregazione di più file in un singolo file. È possibile creare pacchetti CSS, JavaScript e altri bundle. Un minor numero di file significa un minor numero di richieste HTTP e che può migliorare le prestazioni di caricamento della prima pagina.

L'immagine seguente mostra la stessa visualizzazione temporale della visualizzazione Informazioni mostrata in precedenza, ma questa volta con bundle e minification abilitati.

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

Minimizzazione

La minificazione esegue un'ampia gamma di ottimizzazioni del codice diverse per gli script o css, ad esempio la rimozione di spazi vuoti e commenti non necessari e la riduzione dei nomi delle variabili in un unico carattere. Si consideri 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 indicato di seguito:

Originale Ridenominato
imageTagAndImageID n
imageContext t
imageElement i

Impatto di Bundling e Minification

La tabella seguente illustra diverse differenze importanti tra l'elenco di tutti gli asset singolarmente e l'uso di bundle e minification (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 ricevuto 388.51 530 36%
Tempo di caricamento 510 MS 780 MS 53%

I byte inviati hanno avuto una riduzione significativa con la creazione di bundle perché i browser sono piuttosto verbosi 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à minimizzati. Nota: i tempi nel programma di esempio usavano lo strumento Fiddler per simulare una rete lenta. (Da Fiddler Menu Regole , selezionare Prestazioni e simulare velocità modem.

Debug di JavaScript in bundle e minimizzati

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

  1. Selezionare la scheda Script e quindi selezionare il pulsante Avvia debug .
  2. Selezionare il bundle contenente la funzione JavaScript di cui si vuole eseguire il debug usando il pulsante assets.
    Screenshot che mostra la scheda Script dello strumento di sviluppo I E F 12. La casella di input Search Script, un bundle e una funzione Java Script sono evidenziate.
  3. Formattare il codice JavaScript minimizzato selezionando il pulsanteImmagine che mostra l'icona del pulsante Configurazione. Configurazione e quindi selezionando Formatta JavaScript.
  4. Nella casella di input Search Script (Input script di ricerca) selezionare il nome della funzione di cui si vuole eseguire il debug. Nell'immagine seguente, AddAltToImg è stato immesso nella casella di input Search Script .In the following image, AddAltToImg was entered in the Search Script input box.
    Screenshot che mostra la scheda Script dello strumento di sviluppo I E F 12. La casella di input Search Script con Aggiungi alt a lmg immessa è evidenziata.

Per altre informazioni sul debug con gli strumenti di sviluppo F12, vedere l'articolo MSDN Using the F12 Developer Tools to Debug JavaScript Errors .For more information on debugging with the F12 developer tools, see the MSDN article Using the F12 Developer Tools to Debug JavaScript Errors.

Controllo della creazione di bundle e della minificazione

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

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

Per abilitare la creazione di bundle 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 abilita la creazione di bundle e la minimizzazione ed esegue l'override di qualsiasi impostazione nel file 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;
}

Nota

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

Uso di bundle e minification con Web Forms ASP.NET e pagine Web

Uso di Bundling e Minification con ASP.NET MVC

In questa sezione verrà creato un progetto MVC ASP.NET per esaminare la creazione di bundle e la minimizzazione. Creare prima di tutto un nuovo progetto Internet MVC ASP.NET denominato MvcBM senza modificare le 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 illustra 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 minimizzati ma non .file vsdoc) nella cartella Scripts che corrispondono alla stringa con caratteri 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 creazione di bundle segue diverse convenzioni comuni, ad esempio:

  • Selezionare il file ".min" per il rilascio quando FileX.min.js e FileX.js esistente.
  • 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 mostrata in precedenza viene usata per creare automaticamente un bundle jQuery con la versione appropriata di jQuery nella cartella Scripts . In questo esempio, l'uso di un carattere jolly offre i vantaggi seguenti:

  • Consente di usare NuGet per eseguire l'aggiornamento a una versione jQuery più recente senza modificare il codice di creazione di bundle o i riferimenti jQuery precedenti 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 mentre è in 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 non riesca. Il frammento di markup seguente dalla fine del file di layout mostra lo script aggiunto alla richiesta jQuery in caso di esito negativo della 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 Include Bundle accetta una matrice di stringhe, in cui ogni stringa è un percorso virtuale della risorsa. Il codice seguente del 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 IncludeDirectory Bundle viene fornito per aggiungere tutti i file in una directory (e facoltativamente tutte le sottodirectory) che corrispondono a un criterio di ricerca. L'API della classe IncludeDirectory Bundle è 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 progetto 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 Render accettano una matrice di stringhe, in modo da poter aggiungere più bundle in una riga di codice. In genere si vogliono usare i metodi Render che creano il codice HTML necessario per fare riferimento all'asset. È possibile usare il Url metodo per generare l'URL dell'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 a modernizr 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>

Utilizzo del carattere jolly "*" per selezionare i file

Il percorso virtuale specificato nel Include metodo e il criterio di ricerca nel IncludeDirectory metodo possono accettare un carattere jolly "*" come prefisso o suffisso nell'ultimo segmento di percorso. La stringa di ricerca non fa distinzione tra maiuscole e minuscole. Il IncludeDirectory metodo ha la possibilità di eseguire ricerche nelle sottodirectory.

Si consideri 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:

Call File aggiunti o eccezioni generati
Include("~/Scripts/Common/*.js") AddAltToImg.js, ToggleDiv.js, ToggleImg.js
Include("~/Scripts/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("~/Scripts/Common/T*") ToggleDiv.js, ToggleImg.js
Include("~/Scripts/Common/*") Eccezione di modello non valida. Un segmento con caratteri jolly puro non è valido.
IncludeDirectory("~/Scripts/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 spesso aggiunti in un ordine specifico (non alfabetico). È possibile ridurre questo rischio aggiungendo un'implementazione IBundleOrderer personalizzata, ma l'aggiunta esplicita di ogni file è meno soggetta a errori. Ad esempio, è possibile aggiungere nuovi asset a una cartella in futuro che potrebbe richiedere di modificare l'implementazione di IBundleOrderer .

  • È possibile includere file specifici aggiunti a una directory usando il caricamento con caratteri jolly 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 comportano il caricamento dei file importati due volte. 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 Expires un anno da quando viene creato il bundle. Se si passa a una pagina visualizzata in precedenza, Fiddler mostra che Internet Explorer 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 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 (con conseguente risposta HTTP 200 per ogni bundle).

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

immagine di memorizzazione nella cache 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. Finché il bundle non cambia, l'applicazione ASP.NET richiederà il bundle AllMyScripts usando questo token. Se un file nel bundle cambia, il framework di ottimizzazione ASP.NET genererà un nuovo token, garantendo che le richieste del browser per il bundle ottengano l'aggregazione più recente.

Se si eseguono gli strumenti di sviluppo F12 di Internet Explorer 9 e si passa a una pagina caricata in precedenza, Internet Explorer visualizza erroneamente le richieste GET condizionali effettuate a ogni bundle e il server che restituisce HTTP 304. È possibile leggere perché Internet Explorer 9 presenta problemi per determinare se è stata effettuata una richiesta condizionale nel post di blog Uso di reti CDN e scadenza per migliorare le prestazioni del sito Web.

LESS, CoffeeScript, SCSS, Sass Bundling.

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

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

  2. Aggiungere il pacchetto NuGet con estensione less 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. In questo modo si evita un possibile conflitto di routing.

Dopo aver aggiornato un file in un bundle, viene generato un nuovo token per il parametro della stringa di query bundle e il bundle completo deve essere scaricato alla successiva richiesta da parte di un client 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 la creazione di bundle.

La creazione di bundle e la minimizzazione 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 che la creazione di bundle e la minificazione non forniscano alcun miglioramento 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 per gli asset e non si usa la creazione di bundle e la minificazione, l'euristica di aggiornamento dei browser contrassegnerà gli asset non aggiornati dopo alcuni giorni e il browser richiederà una richiesta di convalida per ogni asset. In questo caso, la creazione di bundle e la minimizzazione 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 al limite di sei 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 in base alle pagine che ne hanno bisogno. Ad esempio, il modello MVC ASP.NET predefinito per un'applicazione Internet crea un bundle di convalida jQuery separato da jQuery. Poiché le visualizzazioni predefinite create non hanno input e non inseriscono 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 minificazione, che a sua volta usa Antlr3.Runtime.dll.

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

Risorse aggiuntive

Collaboratori