[翻譯]ASP.NET MVC4新特性之腳本壓縮和合並


目前主流瀏覽器限制客戶端對同一域名只能同時發起6PS:原文如此)個HTTP連接。 這意味着,打開一個網頁只能同時加載6HTTP請求,在同一個域名下其他的請求將被瀏覽器加入到請求隊列中。 在IE瀏覽器中按F12調出開發人員工具,切換到網絡標簽,如下圖所示,顯示的是一個實例網站關於HTTP資源請求的情況。 

 灰色的進度條顯示的是當前請求等待的時間,瀏覽器通過隊列來實現其他資源的依次加載。 黃色的進度條顯示的是客戶端與服務器建立請求所花費的時間。 藍色條顯示的是當前資源從服務器下載完畢所花費的時間。 你可以雙擊當前請求查詢詳細情況。 例如,下圖顯示的是加載/ Scripts/MyScripts/JavaScript6.js文件整個請求過程的詳細情況。 

 

 上面的圖片中顯示了當前資源在開始事件中被瀏覽器加入了請求隊列。因為瀏覽器同時請求限制的影響,當前資源必須等待46毫秒完成上一個HTTP請求才能執行當前的請求

合並

合並是ASP.NET 4.5中的新功能,使開發者很容易實現把多個文件合並成一個文件。 你可以實現CSSjavascript腳本以及其他文件的合並功能。合並多個文件意味着減少了HTTP請求的個數,同時提高了頁面的加載速度

下圖顯示了開啟了腳本合並功能后打開該網站的HTTP請求情況

 

壓縮

壓縮功能實現了對javascript腳本和CSS進行壓縮的功能,它能夠去除腳本或樣式中不必要的空白和注釋,同時能夠優化腳本變量名的長度。 我們來看下面這段JavaScript函數

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

壓縮后,該函數被合並到了一列: 

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

除了刪除注釋和多余的空格外,其變量名也被縮短為如下這樣: 

原變量

縮短后

imageTagAndImageID

n

imageContext

t

imageElement

i

 

合並和壓縮腳本對網站性能的影響

下面表格顯示了腳本被合並壓縮后對網站性能提升的影響。

 

合並和壓縮后

未壓縮和合並

性能提升

HTTP請求數

9

34

256%

發送字節(KB)

3.26

11.92

266%

響應字節(KB)

388.51

530

36%

耗時(ms)

510 MS

780 MS

53%

 

與未優化的網站相比,優化后不但減少了HTTP請求頭的大小,同時請求文件的大小也有着明顯的減少。 不同文件的壓縮大小是不一樣的,該網站的最大的腳本文件已經是壓縮過的(Scripts\jquery-ui-1.8.11.min.js and Scripts\jquery-1.7.1.min.js) 。 

注:網站的耗時實例是通過Fiddler工具來實現的 。 (從FiddlerRules 菜單中選擇Performance 然后選擇 Simulate Modem Speeds )。

調試以及壓縮Javascript

在開發環境下因為JavaScript腳本不會被壓縮和合並,所以調試JavaScript是件很容易的事情( Web.config文件中compilation節點設置debug="true" )。你可以調試一個發布版本的JavaScript用於生產環境。 使用IE F12開發人員工具,調試JavaScript腳本的方法如下

1 選擇“腳本 ”選項卡,然后選擇“開始調試”按鈕。

2 選擇你要調試的腳本文件。 

 

3. 選擇“配置” 按鈕 ,格式化壓縮后的JavaScript,然后選擇“格式后的JavaScript”按鈕

4. 你還可以通過搜索方法來檢索你需要調試的函數。 在下面的圖片,在搜索輸入框中輸入AddAltToImg,搜索結果會以高亮方式顯示。 

 

關於開發人員工具調試的更多信息,請參閱MSDN文章使用開發人員工具調試JavaScript錯誤 。

配置腳本壓縮和合並功能

在 Web.config文件中compilation節點設置debug 的值可以開啟或關閉壓縮和合並功能。 在下面的XML中, debug設置值為true可以禁用腳本壓縮和合並功能。

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

如果要啟用腳本壓縮和合並,則設置debug 為false 。你可以通過BundleTable 類的EnableOptimizations 屬性來覆蓋Web.config中的設置。 下面的代碼演示了如果通過BundleTable 來覆蓋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;
}

注意 :除非設置EnableOptimizations為true或設置Web.config文件中compilption 節點的debug屬性為false,否則程序是不會合並和壓縮文件的。 此外,系統也不會選擇壓縮過的腳本,而是選擇調試版本。 EnableOptimizations屬性的設置將會覆蓋Web.config中的設置。

在ASP.NET Web窗體和Web Pages中使用腳本合並和壓縮功能

在ASP.NET MVC中使用壓縮和合並功能

在本節中,我們將創建一個ASP.NET MVC項目,來體驗壓縮和合並功能。 首先,創建一個新的ASP.NET MVC Internet項目名為MvcBM ,其他設置默認。

打開App_Start \BundleConfig.cs文件並查找RegisterBundles方法,該方法是用來創建、注冊和配置需要壓縮和優化的腳本文件的。下面的代碼顯示了部分RegisterBundles方法。

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

上面的代碼我們創建了一個新的命名為~/bundles/jquery 的JavaScript bundle並包含了合適的 腳本文件(用於壓縮和合並,這里不包含.vsdoc),還可以通過通配符或關鍵字來匹配腳本文件夾下不同版本的腳本文件。 ASP.NET MVC 4 中,系統默認集成了jquery-1.7.1.js 庫。在release版本中,系統將選擇jquery-1.7.1.min.js 包含在項目中。 腳本合並框架遵循以下幾個原則:

  •  當“FileX.min.js” 和“FileX.js 同時存在時,在release版本中,系統將選擇文件名包含“.min”的腳本
  • 請選擇文件名中不包含“.min”的文件進行調試。
  • 請過濾文件名中包含“.vsdoc”的文件(如jQuery-1.7.1-vsdoc.js),該文件僅僅用於VS的智能感知提示。

{version}關鍵字是用來包含不同版本的jQuery庫的。 在這個例子中,使用通配符存在以下幾個好處:

  • 允許你在不改變原有配置的情況下通過NuGet 更新你的jQuery版本。
  • 自動選擇調試版本和文件名中帶有“.min”的發行版本。

使用CDN

 CDN jQuery 庫將取代本地jQuery庫。 

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 = "http://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.
}

在上面的代碼中,在發行版中將使用CDN 上的jQuery庫,而本地調試的時候使用本地版本jQuery庫。 當使用CDN 版本時,你應該建立一個容錯機制,預防CDN加載失敗的情況。 下面的代碼演示了如果在CDN 中的jQuery庫加載失敗的情況下如何加載本地版本。

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

創建一個Bundle

Bundle類Include方法接受一個字符串或數組類型,其中每個字符串是一個虛擬的文件路徑。其中RegisterBundles方法在App_Start /BundleConfig.cs 文件中演示了如何將多個文件添加到一個文件中:

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"));

Bundle類的IncludeDirectory方法可以添加一個目錄中的所有的(和所有子目錄)相匹配文件。 Bundle類IncludeDirectory API是如下所示:

 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.

在頁面中可以使用Render方法來引用創建好的包(JavaScript對應了Scripts.Render  而CSS對應的是Styles.Render )。下面的代碼演示了在Views\Shared\_Layout.cshtml  文件中如果引用CSSJavaScript的:

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

請注意Render方法的參數是一個字符串或數組類型,它允許你在一個方法中添加多個引用。 一般情況下,你可以使用Render方法來自動引用文件而省略了其他HTML標簽。你可以使用Url的方法來生成一個文件URL絕對路徑。 假設你想使用的HTML5 異步屬性。 下面的代碼演示了如何使用Url方法引用Modernizr 包。

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

使用通配符*匹配文件

在Include 和 IncludeDirectory方法中可以通過通配符“*”來匹配路徑中某一些相同元素的文件。匹配時不區分路徑中的大小寫。 IncludeDirectory方法具有搜索子目錄的功能。

假如一個項目下有如下JavaScript文件:

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

下面的表演示了使用通配符來添加如圖所示文件到項目中:

使用通配符

匹配到的文件或異常提示

Include("~/Scripts/Common/*.js")

AddAltToImg.js, ToggleDiv.js, ToggleImg.js

Include("~/Scripts/Common/T*.js")

無效的匹配模式,通配符只能在文件的前綴或后綴中使用。

Include("~/Scripts/Common/*og.*")

無效的匹配模式,一個路徑中只能使用一個通配符。

"Include("~/Scripts/Common/T*")

ToggleDiv.js, ToggleImg.js

"Include("~/Scripts/Common/*")

無效的匹配模式,僅使用一個通配符是沒有任何意義的。

IncludeDirectory("~/Scripts/Common", "T*")

ToggleDiv.js, ToggleImg.js

IncludeDirectory("~/Scripts/Common", "T*",true)

ToggleDiv.js, ToggleImg.js, ToggleLinks.js

 

通常情況下,我們應該首選通過完整路徑來添加文件,其原因如下:

  • 使用通配符添加腳本其添加順序是根據文件名的字母排序來依次添加的,有時候因為腳本相互依賴的情況,這種添加方式可能會出錯。 CSSJavaScript文件經常因為相互依賴而不能按照文件名排序來添加。為了減少潛在的風險,你可以通過添加一個自定義IBundleOrderer來添加文件,但是通過完整文件名來添加文件是最保險的。 例如,將來需要添加新文件時,你可能要通過修改 IBundleOrderer類才能實現。
  • 在JavaScript中使用通配符來添加一個目錄的所有腳本到項目中可能會引起JavaScript的錯誤提示。
  • 在加載CSS文件時,可能會出現加載重復的情況,如下所示的例子:
bundles.Add(new StyleBundle("~/jQueryUI/themes/baseAll")
    .IncludeDirectory("~/Content/themes/base", "*.css"));

通配符“*.CSS可以匹配文件夾下所有的CSS文件,包括Content\themes\base\jquery.ui.all.css 。Jquery.ui.all.css可能在其他文件中已經存在。

包緩存

合並后的系統設置了HTTP過期時間為一年。如果你重復打開了一個也沒,在服務器的版本為未改變的情況下,服務器將會返回一個HTTP 304狀態碼,這樣瀏覽器會加載緩存中的文件。 你也可以在IE瀏覽器下通過按Ctrl + F5來強制刷新頁面,這時瀏覽器會重新請求文件,而不是從緩存中讀取(此時服務器響應的HTTP狀態碼為200)

下面的圖像顯示了在Fiddler“Caching”選項卡緩存文件的情況 :

 

請求的路徑為

http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81 
其中AllMyScripts是優化后的包名,V = r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81是當前包的一個特定字符,用於緩存當前的文件。 只要服務器端包不改變,ASP.NET應用程序將一直使用AllMyScripts包中的該標記。 如果包中的文件存在變化,ASP.NET程序將會生成一個新的字符串,以便於刷新緩存,使客戶端得到新的文件。

If you run the IE9 F12 developer tools and navigate to a previously loaded page,
IE incorrectly shows conditional GET requests made to each bundle and the server returning HTTP 304.
You can read why IE9 has problems determining if a conditional request was made
in the blog entry Using CDNs and Expires to Improve Web Site Performance. (該段不會翻譯)

在LESS, CoffeeScript, SCSS, Sass中使用腳本優化功能

合並和壓縮框架提供了一個機制來處理SCSS 、Sass 、LESSCoffeeScript並轉換並合並到優化包中。 例如,添加 LESS 文件到你的MVC4項目:

  1. 創建一個文件夾用於添加你的LESS文件。 下面的示例演示了如何使用 Content\MyLess folder。 
  2. 通過NuGet來添加合適的包到你的項目中:
  3. 添加一個類,並實現 IBundleTransform 接口。然后可以通過下面的代碼把LESS添加到你的項目中:
    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. 創建一個Bundle對象,然后添加 LessTransform 和CssMinify ,接下來在 App_Start\BundleConfig.cs 中使用 RegisterBundles  方法來注冊你的方法。
    var lessBundle = new Bundle("~/My/Less").IncludeDirectory("~/My", "*.less");
    lessBundle.Transforms.Add(new LessTransform());
    lessBundle.Transforms.Add(new CssMinify());
    bundles.Add(lessBundle);
  5. 調用Bundle包如下所示:
    @Styles.Render("~/My/Less");

合並的注意事項

當你使用Bundle對象來創建一個包時應該用"bundle" 來做前綴,這樣可以防止路由沖突 。

一旦你的包中存在一個文件更新,程序會生成一個新的查詢參數來使客戶端在下一次訪問的時候獲取到最新的版本。在傳統的單獨列出文件引用程序中,客戶端僅會重新請求修改過的文件,但是在這種新的方式下,會重新下載整個包中的文件,因此,如果你的包經常變動,使用這種合並方式也許並不是一個好的選擇。

文件的合並和壓縮主要是為了改善頁面在第一次加載的時候文件下載所消耗的時間。當該頁面加載完畢,當再一次打開該頁面時,瀏覽器會從緩存中讀取這些緩存資源(JavaScriptCSS和圖像),所以,在打開同一站點的其他頁面,這種方式並不能提高網站的性能(PS:因為不合並和壓縮的文件也可以被緩存)。如果文件的過期設置不正確,瀏覽器會重復請求該文件,這種情況下合並和壓縮文件會提高非第一次打開網頁的性能。 有關詳細信息,請參閱Using CDNs and Expires to Improve Web Site Performance 。

通過CDN可以改善瀏覽器同站點同時請求限制的問題。在CDN環境下,客戶端將從不同的域名來請求暑假,這個時候同一網站的資源會被緩存在不同的主機商,從而加快網站打開速度,同時CDN還提供了數據緩存的功能。

System.Web.Optimization命名空間包含在System.Web.Optimization.DLL文件中。 它壓縮功能利用的是WebGrease庫(WebGrease.dll),然后使用Antlr3.Runtime.dll

Additional Resources

· Video: Bundling and Optimizing by Howard Dierking

· Adding Web Optimization to a Web Pages Site.

· Adding Bundling and Minification to Web Forms.

· Performance Implications of Bundling and Minification on Web Browsing by Henrik F Nielsen   ‏@frystyk

· Using CDNs and Expires to Improve Web Site Performance by Rick Anderson   @RickAndMSFT

· Minimize RTT (round-trip times)

Contributors

· Hao Kung

· Howard Dierking

· Diana LaRose

 原文地址:http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification

轉載請注明原文地址:http://www.cnblogs.com/lifeil/archive/2013/03/11/2954071.html 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM