Using Script and Style Bundles【翻譯】

Halower發表於2013-07-03

 

遇到個MVC4中的bundles的問題,問了別人,由於不熟悉MVC4,始終問不到點子上,所以自己就翻譯了下資料,搞明白了這個VS顯示正常IIS顯示異常的小問題,申明我翻譯的很爛,不過共享出來或許會幫到人。

Preparing the Example Application

For thischapter, we have created a new MVC project called ClientFeatures using theBasic template option. We are going to create a variation on the applicationthat we used in the previous chapter, so we started by creating a new classfile called Appointment.cs in the Models folder.  You can see the contents of this file inListing 24-1.

在這一章中,我們先建立一個新的名為ClientFeatures並使用Basic template 模板的MVC應用程式,我們將建立一個在前一章中已經使用過的變數,因此我們首先要在Models資料夾中建立一個名為Appointment.cs的檔案。清單24-1中可以看到此檔案的內容。

Listing 24-1. The Appointment Model Class 
 
We created a Home controller that operates on the Appointment model class, as shown in Listing 24-2. Listing 24-2. The Home Controller in the ClientFeatures Application 
我們建立一個HomeController操作這個Appointment 模型類,如清單24-2所示。
複製程式碼
using System; 
using System.Web.Mvc; 
using ClientFeatures.Models; 
  
namespace ClientFeatures.Controllers { 
  public class HomeController : Controller { 
  
        public ViewResult MakeBooking() { 
            return View(new Appointment {  
                ClientName = "Adam", 
                Date = DateTime.Now.AddDays(2), 
                TermsAccepted = true 
            }); 
        } 
  
        [HttpPost] 
        public JsonResult MakeBooking(Appointment appt) { 
            // statements to store new Appointment in a 
            // repository would go here in a real project 
            return Json(appt, JsonRequestBehavior.AllowGet); 
        } 
    } 
} 
複製程式碼
There are two version of the MakeBooking method in this controller. The version with no parameters creates an Appointment object and passes it to the View method to render the default view. The HttpPost version of the MakeBooking method relies on the model binder to create an Appointment object and uses the Json method to encode the Appointment and send it back to the client in the JSON format. We are focused on the MVC Framework features that support client-side development in this chapter, so we have taken some shortcuts in the controller that wouldn’t be sensible or useful in a real project. Most importantly, we do not perform any kind of validation when we receive a HTTP POST request and just sent the details of the object created by the model binder back to the browser as JSON (with no support for HTML responses to POST requests). We want to make it as easy as possible to use the Ajax-enabled form element that we have defined in the /Views/Home/MakeBooking.cshtml file, which you can see in Listing 24-3. Our interest is in the script and link elements in the view and the interaction with the application is far less important. 
在這個Controller中有兩個過載版本的的MakeBooking方法, 不帶引數的版本建立一個Appointment物件並通過View()傳遞並渲染預設的檢視。HttpPost版本的MakeBooking方法依賴於模型繫結器的建立AppoimtMent物件,並使用Json方法編碼Appoientment並以JSON格式傳送回客戶端。本章我們的重點是支援客戶端開發功能MVC框架特性,所以我們在Controller中的功能做了簡化,當然這不是一個切實有用專案。最重要的是,我們並沒有進行任何形式的驗證,當我們收到一個HTTP POST請求,僅傳送由模型繫結器建立的物件以JSON的形式返回給瀏覽器(不支援HTML響應的POST請求)我們希望儘可能容易地使用支援Ajax的表單元素,我們在/Views/Home/MakeBooking.cshtml檔案,如清單24-3所示。 我們感興趣的是檢視中的指令碼和link元素和應用程式互動的重要性。
Listing 24-3. The MakeBooking View 
複製程式碼
@model ClientFeatures.Models.Appointment 
  
@{ 
    ViewBag.Title = "Make A Booking"; 
    AjaxOptions ajaxOpts = new AjaxOptions { 
        OnSuccess = "processResponse" 
    }; 
} 
<h4>Book an Appointment</h4> 
  
<link rel="stylesheet" href="~/Content/CustomStyles.css" /> 
<script src="~/Scripts/jquery-1.7.1.min.js"></script> 
<script src="~/Scripts/jquery.validate.js"></script> 
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script> 
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script> 
  
<script type="text/javascript"> 
    function processResponse(appt) { 
        $('#successClientName').text(appt.ClientName); 
        $('#successDate').text(processDate(appt.Date)); 
        switchViews(); 
    } 
  
    function processDate(dateString) { 
        return new Date(parseInt(dateString.substr(6, 
            dateString.length - 8))).toDateString(); 
    } 
  
    function switchViews() { 
        var hidden = $('.hidden'); 
        var visible = $('.visible'); 
        hidden.removeClass("hidden").addClass("visible"); 
        visible.removeClass("visible").addClass("hidden"); 
    } 
    $(document).ready(function () { 
        $('#backButton').click(function (e) { 
            switchViews(); 
        }); 
    }); 
</script> 
<div id="formDiv" class="visible"> 
    @using (Ajax.BeginForm(ajaxOpts)) { 
        @Html.ValidationSummary(true)     
        <p>@Html.ValidationMessageFor(m => m.ClientName)</p> 
        <p>Your name: @Html.EditorFor(m => m.ClientName)</p> 
        <p>@Html.ValidationMessageFor(m => m.Date)</p> 
        <p>Appointment Date: @Html.EditorFor(m => m.Date)</p> 
        <p>@Html.ValidationMessageFor(m => m.TermsAccepted)</p> 
        <p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p>     
        <input type="submit" value="Make Booking" /> 
    } 
</div> 
<div id="successDiv" class="hidden"> 
    <h4>Your appointment is confirmed</h4> 
    <p>Your name is: <b id="successClientName"></b></p> 
    <p>The date of your appointment is: <b id="successDate"></b></p> 
    <button id="backButton">Back</button> 
</div> 
 
複製程式碼
In this view, there are two div elements. The first is shown to the user when the view is first rendered and contains an Ajax-enabled form. When the form is submitted, we respond to the server’s response to the Ajax request by hiding the form and revealing the other div element, which we use to display details of the appointment confirmation.  
We included a number of the standard JavaScript libraries from the Scripts folder and defined a local script element that contains some simple jQuery code that is specific to this view. We also added a link element that loads a CSS file from the /Content folder called CustomStyles.css, which you can see in Listing 24-4.  
在此檢視中,有兩個div元素。 第一個是在檢視首次渲染時向使用者呈現一個支援Ajax的表單。提交表單時,我們對Ajax請求的響應的伺服器的響應通過隱藏表單元素顯示其他的div元素,我們用於顯示預約確認的詳細資訊。我們使用了Script資料夾一些標準的JavaScript庫並定義了一些本地的指令碼元素其中包含一些簡單的jQuery的程式碼在特定View中。我們還新增了link元素從 /Content載入CustomStyles.css,如清單24-4:
Listing 24-4. The Contents of the CustomStyles.css File 
div.hidden { display: none;} 
div.visible { display: block;}
We want to create a typical scenario for a complex view file without needing to create a complex application, which is why we have added a CSS file that only contains two styles and why we are using a bunch of jQuery libraries for a very simple view. The key idea is that there are lots of files to be managed. When you are writing real applications, you will be struck by just how many script and style files you have to deal with in your views. You can see how our example application works by starting the application and navigating to the /Home/MakeBooking URL. The form is pre-populated with data so that you can just click the Make Booking button to submit the form data to the server using Ajax. When the response is received, you will see a summary of the Appointment object that was created by the model binder from the form data, along with a button element that will return you to the form, as illustrated in Figure 24-1.
我們要建立一個複雜的檢視檔案的典型方案,而無需建立複雜的應用程式,所以我們新增了一個只包含兩種風格的CSS檔案和一個非常簡單的使用了一堆jQuery庫的。關鍵是在於有大量的檔案需要管理。 當你寫真實專案時,會遇到更多少的本和樣式檔案需要你的處理。你可以可以啟動程式並導航到/Home/MakeBooking。預先填充表單資料,以便您可以點選預訂按鈕使用Ajax伺服器提交表單資料。收到響應時,您將從表單資料的模型看到所建立的Apointment物件的以input元素返回表單摘要,如圖24-1所示。

圖片
Managing Scripts and Style Sheets 
The view that we created in Listing 24-3 is typical of the MVC projects that we see in the wild. There is a mix of libraries from the Scripts folder, CSS style sheets from the Content folder, local script elements, and of course, HTML and Razor markup.  Developers tend to write view files just as they would write HTML pages, which is fine but isn’t the most effective approach. As we will show you in the sections that follow, there are some hidden problems in our MakeBooking.cshtml view file. We can make a number of improvements in the way that we manage our scripts and style sheets. 
清單24-3中建立的是典型的MVC專案。它由來自Script資料夾的指令碼庫,來自Content資料夾的CSS樣式表,本地js元素,當然,還有HTML元素和Razor標籤的組合。
開發人員傾向於像寫HTML頁面一樣寫檢視檔案,這是很好的,但不是最有效的方法。正如我們將告訴你在後面的章節中,也有一些隱藏的問題我們MakeBooking.cshtml檢視檔案。我們可以做一些改進的方式,我們管理我們的指令碼和樣式表。
Profiling Script and Style Sheet Loading 
When considering any kind of optimization in any kind of project, you need to start by taking some measurements. We are all for efficient and optimized applications, but our experience is that people rush to optimize problems that don’t have much impact, and in doing so, make design decisions that cause problems later.  
剖析指令碼和樣式表的載入
在考慮任何型別的專案優化時,你首先需要採取了一些措施以實現高效、優化的應用程式,但我們的經驗是,人們對於優化的問題並不是很重視,也正因此,作出的設計決策,導致以後出現問題。
For the problems that we are going to look at in this chapter, we are going to perform our measurements using the Internet Explorer 10 F12 tools (so called because you access them by pressing the F12 key). Load the application, navigate to the /Home/MakeBooking URL, and then press the F12 key. When the tools window opens, navigate to the Network tab and click the Start Capturing button. Reload the contents of the browser tab (right-click in the browser window and select Refresh), and you will see the results shown in Figure 24-2.  

 

針對這些問題,我們在本章中進行探討,我們使用Internet Explorer10的F12工具(你可以通過按下F12鍵訪問他們)。執行我們的測試,載入應用程式,導航到/Home/MakeBooking的網址,然後按F12鍵。當工具視窗開啟時,導航到“網路”選項卡,並單擊“開始捕獲”按鈕。重新整理瀏覽器“選項卡的內容​​(右擊在瀏覽器視窗,並選擇重新整理),你會看到如圖24-2所示的結果。

 

圖片

 

 

 

Figure 24-2. Profiling script and style sheet loading for the example application The F12 tools in IE10 allow you to profile the network requests that your application makes. (There are other tools available if you are not using Internet Explorer. Our favorite is Fiddler, which you can get for free from www.fiddler2.com). So that we can compare the optimizations that we make in this chapter, we will use the data shown in Figure 24-2 as our baseline. Here are the key figures: 
• The browser made nine requests for the /Home/MakeBooking URL. 
• There were two requests for CSS files. 
• There were six requests for JavaScript files. 
• A total of 2,978 bytes were sent from the browser to the server. 
• A total of 470,417 bytes were sent from the server to the browser 

 

在IE10的F12工具可讓您分析應用程式的網路請求狀況。 (當然有其他可用的工具,如果你不使用Internet Explorer。我們推薦的Fiddler,你可以去www.fiddler2.com下載)。這樣我們我們在本章中就可以比較得出最優化的方案,我們使用圖24-2中顯示的資料作為的基準。
這裡是關鍵的資料:
•瀏覽器作出請求的 /Home/ MakeBooking的網址。
•有兩個CSS檔案的請求。
•有6個JavaScript檔案的請求。
•共2,978個位元組從瀏覽器傳送到伺服器。
•共470,417位元組從伺服器傳送到瀏覽器。

 

This is the worst-case profile for our application because we cleared the browser’s cache before we reloaded the view. We have done this because it allows us to easily create a measurable starting point, even though we know that real-world use would be improved by the browser caching files from previous requests. 
If we reload the /Home/MakeBooking URL without clearing the cache, then we get the following results: 
• The browser made nine requests for the /Home/MakeBooking URL. 
• There were two requests for CSS files. 
• There were six requests for JavaScript files. 
• A total of 2,722 bytes were sent from the browser to the server. 
• A total of 6,302 bytes were sent from the server to the browser. 
This is our best-case scenario, where all the requests for CSS and JavaScript files were able to be 
serviced using previously cached files. 

 

因為在我們載入檢視之前,我們清除瀏覽器的快取,對於應用程式這是最低效的配置方式。 儘管我們知道從以前的瀏覽器快取檔案的實際使用會改善請求,但我們還是這樣做了,因為它使我們可以方便地建立一個可衡量的基點。 如果我們重新載入/Home/MakeBooking URL而不清除快取,然後我們得到以下結果:

 

•瀏覽器做出請求的/Home/MakeBooking 的網址。
• 有兩個CSS檔案的請求。
•有6個JavaScript檔案的請求。
•共2,722個位元組從瀏覽器傳送到伺服器。
•共6,302個位元組是從伺服器傳送到瀏覽器。
 這是最理想的情況,CSS和JavaScript檔案的所有請求都能使用以前快取的檔案。

 

 Note In a real project, we would stop at this point and try to understand if we have a problem that needs to be solved. It may seem that 470K is a lot of bandwidth for a simple Web page, but context is everything. We might be developing an application for Intranet use where bandwidth is cheap and plentiful, and optimizations of any sort are outweighed by the cost of the developer, who could be working on more-important projects. Equally, we could be writing an application that operates over the Internet with high-value customers in countries with low-speed connections—in which case it is worth spending the time to optimize every aspect of the application. Our point is that you shouldn’t automatically assume that you have to squeeze every optimization into every application— there will often be better things you could be doing. (This is always the case if you are sneakily optimizing your application without telling anyone. Stealth optimization is a bad idea and will catch up with you eventually). 
   注意  在實際專案中,如果我們有問題需要解決,此時我們就會停止並嘗試瞭解這裡。這個看似簡單的470K大小的Web頁對於寬頻來說微不足道。我們或許開發一個應用程式內部使用的廉價和充足的頻寬任何一種優化的成本或許超過了開發人員的成本,他可以去做更重要的工作專案。同樣,我們如果在低網速的國家情況下開發在網際網路上執行的應用程式和高價值客戶這種,我們就值得花時間去優化應用的各個方面。我們的觀點是,你不應該認為你每天有更重要的的事情去做而不去擠些時間優化應用程式的各個方面。(如果你始終在不告訴任何人就優化應用,隱身優化是個壞主意,最終會自討苦吃)。

 

If you look at the list of JavaScript files that are downloaded for the view, you will notice that we havere-created two very common problems. The first is that we have a mix of minified and regular JavaScript files. This isn’t a huge issue, but it does mean that we won’t have the ability to easily debug all the library code during development.  The second problem is that we have downloaded both the minified and regular versions of the jQuery library. This has happened because the layout is loading some JavaScript and CSS files as well, and our lack of coordination means that we are forcing the browser to download code that it already has. We see both of these problems often. We will show you the MVC Framework features that help you get script and CSS files under control. More broadly, we will also show you how to reduce the number of requests that the browser has to make to the server and the amount of data that has to be downloaded. 

 

如果你看看為檢視下載的JavaScript檔案的列表,你會發現我們已重現了兩個非常常見的問題。
   首先是,我們有一個壓縮的和常規的JavaScript檔案組合。這不是一個大問題,但它確實意味著,我們將不會有能力在開發過程中輕鬆地除錯所有的庫程式碼。
   第二個問題是,我們已經下載了壓縮版或常規版的jQuery庫。由於佈局將載入一些JavaScript和CSS檔案,這種缺乏協調的發生協調意味著我們將強制瀏覽器下載已有的程式碼。
   我們經遇到這兩個問題。 我們將向您展示MVC框架功能以幫助你如何控制指令碼和CSS檔案的獲取。從更廣泛的意義上說,我們也將向您展示如何減少瀏覽器的請求,控制伺服器和下載的資料量。

 

Our first step will be to organize our JavaScript and CSS files into bundles, which allows us to treat them as a single unit.Bundles are defined in the /App_Start/BundleConfig.cs file. We have listed the default contents of this file, as created by Visual Studio, in Listing 24-5.

我們第一步組織JavaScriptCSS檔案放入bundles中,可以將它們作為獨立的單元。bundles的定義在/App_Start/BundleConfig.cs檔案中。我們列出了通過Visual Studio建立的此檔案的預設內容,如清單24-5

 

using System.Web; 
using System.Web.Optimization; 
 
namespace ClientFeatures { 
    public class BundleConfig { 
        public static void RegisterBundles(BundleCollection bundles) { 
 
            bundles.Add(new ScriptBundle("~/bundles/jquery").Include( 
                        "~/Scripts/jquery-{version}.js")); 
 
            bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include( 
                        "~/Scripts/jquery-ui-{version}.js")); 
 
            bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( 
                        "~/Scripts/jquery.unobtrusive*", 
                        "~/Scripts/jquery.validate*")); 
 
            bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( 
                        "~/Scripts/modernizr-*")); 
             bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css")); 
 
            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")); 
        } 
    } 
} 

 

The static RegisterBundles method is called from the Application_Start method in Global.asax when the MVC Framework application first starts. The RegisterBundles method takes a BundleCollection object, which we use to register new bundles of files through the Add method. 

 Tip The classes that are used for creating bundles are contained in the System.Web.Optimization namespace and, as we write this, the MSDN API documentation for this namespace isn’t easy to find. You can navigate directly to http://msdn.microsoft.com/en-us/library/system.web.optimization.aspx if you want to learn more about the classes in this namespace.

在MVC框架應用程式第一次啟動時將從 Global.asax 的Application_Start 方法中呼叫靜態的RegisterBundles方法。 RegisterBundles的方法將BundleCollection的物件作為引數,我們可以用通過Add方法來註冊新的bundles。

提示 這些類都包含在用於建立bundles的System.Web.Optimization名稱空間中,之所以關注它是因為這個名稱空間的MSDN API文件是不容易找到。您可以直接導航到http://msdn.microsoft.com/en-us/library/system.web.optimization.aspx如果您想了解更多關於這個名稱空間中的類。

We can create bundles for script files and for style sheets and it is important that we keep these types of file separate because the MVC Framework optimizes the files differently. Styles are represented by the StyleBundle class and scripts are represented by the ScriptBundle class. When you create a new bundle, you create an instance of either StyleBundle or ScriptBundle, both of which take a single constructor argument that is the path that the bundle will be referenced by. The path is used as a URL for the browser to request the contents of the bundle, so it is important to use a scheme for your paths that won’t conflict with the routes your application supports. The safest way to do this is to start your paths with ~/bundles or ~/Content. (The importance of this will become apparent as we explain 

how bundles work).
Once you have created the StyleBundle or ScriptBundle objects, you use the Include method to add details of the style sheets or script files that the bundle will contain. There are some nice features available for making your bundles flexible. To help us demonstrate this, we have edited and simplified the bundles our example application supports in the BundleConfig.cs file, as shown in Listing 24-6. We added a new bundle, edited one of the existing ones, and removed all of the ones that we do not need.
我們可以為指令碼檔案和樣式表的建立bundles,而且重要的是,我們要保持這些型別的檔案獨立,因為MVC框架按照不同的檔案進行優化。樣式的由StyleBundle類,指令碼由ScriptBundle類來控制。當你建立一個新的bundle的一個例項,這兩者採取單一的建構函式的引數即bundle被引用的路徑。這個路徑被用於瀏覽器請求bundle的內容的Url,所以使用該方案時使您的應用程式請求路徑與路由不會衝突非常重要。要做到這一點,最安全的方式是路徑以〜/bundles或〜/Content開始。 (之後為大家講解bundles是如何工作時,這一點的重要性將變得很明顯)。
一旦你已經建立了StyleBundle或ScriptBundle的物件,您可以使用Include方法為這個bundle新增樣式表或指令碼檔案的具體內容。有一些不錯的功能,可靈活的用於bundles。為了我們演示這一點,我們對現有的BundleConfig.cs檔案中的bundles進行了編輯及簡化,如清單24-6所示。我們新增了一個新的bundle,編輯一個現有的,並刪除了所有那些我們不需要的。

Listing 24-6. Customizing the Bundle Configuration 
using System.Web; 
using System.Web.Optimization; 
 
namespace ClientFeatures { 
    public class BundleConfig { 
        public static void RegisterBundles(BundleCollection bundles) {  
            bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/*.css")); 
 
            bundles.Add(new ScriptBundle("~/bundles/clientfeaturesscripts") 
                .Include("~/Scripts/jquery-{version}.js", 
                        "~/Scripts/jquery.validate.js", 
                        "~/Scripts/jquery.validate.unobtrusive.js",  
                        "~/Scripts/jquery.unobtrusive-ajax.js")); 
 
        } 
    } 
} 

We started by modifying the StyleBundle with the ~/Content/css path. We want this bundle to include all the CSS files in our application, so we changed the argument passed to the Include method from ~/Content/site.css (which refers to a single file) to ~/Content/*.css. The asterisk (*) character is a wild card, which means that our bundle now refers to all of the CSS files in the /Content folder of our project.
This is an excellent way of ensuring that files in a directory are automatically included in a bundle and where the order in which the files are loaded isn’t important. The order in which the browser loads our CSS files isn’t important, so using a wildcard is just fine; but if you are relying on the CSS style precedence rules, then you need to list the files individually to ensure a specific order.
The addition to the BundleConfig.cs file is a ScriptBundle whose path we set to ~/bundles/clientfeaturesscripts. You will see the paths for both of these bundles again when we apply them in our application shortly. We used the Include method for this bundle to specify individual JavaScript files, separated by commas, because we only require some of the files in the Scripts folder and we care about the order in which the browser loads and executes the code.
Notice how we specified the jQuery library file:
我們開始通過修改StyleBundle的~/Content/css 路徑。 我們希望這個bundle包含應用程式中的所有的CSS檔案,所以我們將傳遞給Include方法引數~/Content/site.css(指單個檔案)改成~/Content/*.css。 星號(*)字元是一個萬用字元,這意味著現在的Bundle引用我們的專案/Content資料夾中所有的CSS檔案。確保目錄中的檔案會自動包含在bundle中的檔案很好的方法,同時檔案載入的順序並不重要因為瀏覽器載入我們的CSS檔案的順序並不重要,所以使用萬用字元是非常好的方式;但如果程式依賴於CSS樣式規則的優先順序,則需要列出單獨的檔案,以確保特定的順序。
除此之外BundleConfig.cs檔案還有一組ScriptBundle,它的路徑被設定成~/bundles/clientfeaturesscripts。他們很快會在我們的應用程式中出現,你會再見到這些bundle的路徑。我們在bundle的Include方法指定由逗號分隔的單獨的JavaScript檔案,因為我們只需要Script資料夾中的一些檔案,此時我們關心瀏覽器的載入並執行程式碼順序。注意我們是如何指定jQuery庫檔案:

... 
~/Scripts/jquery-{version}.js 
... 

The {version} part of the file name is pretty handy because it matches any version of the file specified and it uses the configuration of the application to select either the regular or minified version of the file. MVC 4 comes with version 1.7.1 of the jQuery library, which means that our bundle will include /Scripts/jquery-1.7.1.js during development and /Scripts/jquery-1.7.1.min.js when deployment.
 Tip The decision between the regular and minified version is driven by the compilation element in the Web.config file. The regular version is used when the debug attribute is set to true and the minified version is used when debug is false. We will switch our application from debug to deployment mode later in the chapter so you can see how it is done.
{version} 部分檔名是非常方便的,因為它匹配指定的檔案的任何版本,這種的配置可以是應用程式選擇常規或壓縮的版本的檔案。 MVC 4自帶1.7.1版本的jQuery庫,這意味著在開發階段我們的Bundle將包括Scripts/jquery-1.7.1.js的部署時載入/ Scripts/jquery-1.7.1.min.js 。
提示 常規和壓縮版本之間的切換都基於Web.config檔案compilation元素。 當debug屬性設定為true時使用正式版本, 當debug屬性設定為false,使用壓縮版本。我們將我們的應用程式切換除錯到部署的模式,在本章的後面,你可以看到它是如何做的。

The benefit of using {version} is that you can update the libraries you use to new versions without having to redefine your bundles. The drawback is that the {version} token isn’t able to differentiate between two versions of the same library in the same directory. So, for example, if we were to add the jquery-1.7.2.js file in our Scripts folder, we would end up with both the 1.7.1 and 1.7.2 files being shipped to the client. Since this would undermine our optimization, we must ensure that only one version of the library is in the /Scripts folder.
使用{version}的好處是你可以更新你的的版本,而不必重新在bundle中定義。{version}標記的缺點是是無法區分在同一目錄中兩個版本的相同的庫。因此,舉例來說,如果我們要新增的Script資料夾中的query1.7.2.js檔案,而最終的1.7.1和1.7.2的檔案都被髮送到客戶端。因為這會破壞我們的優化,我們必須確保/ Scripts資料夾中只有一個版本庫。

Tip The MVC Framework is smart enough to ignore the IntelliSense files when processing {version} in a bundle, but we always check what is being requested by the browser because it is so easy to include unwanted files. You will see how we do this shortly.

提示 MVC框架是聰明到在bundle中處理{version}忽略智慧感知檔案,但我們總是檢查所請求的瀏覽器因為太容易了,包括不需要的檔案。很快你將看到我們如何做到這一點。

Applying Bundles 

The first thing we need to do when applying bundles is prepare the view. Our first step is optional, but it will allow the MVC Framework to perform maximum optimization for our application. We have created a new /Scripts/Home folder and added a new JavaScript file called MakeBooking.js within it. This is the convention that we follow to keep our per-page JavaScript files organized by controller. You can see the contents of the MakeBooking.js file in Listing 24-7.

應用bundles,我們需要做的第一件事情是準備檢視。我們的第一個步驟是可選的,但它可以讓MVC框架進行最大限度地優化我們的應用程式。我們已經建立了一個新的/Scripts/Home資料夾,並新增了一個名為MakeBooking.jsJavaScript新檔案。這是我們遵循的慣例,使我們的每個頁面的JavaScript檔案由控制器組織。清單24-7中,你可以看到MakeBooking.js檔案的內容。 

 Listing 24-7. The Contents of the MakeBooking.js File

Listing 24-7. The Contents of the MakeBooking.js File 
function processResponse(appt) { 
    $('#successClientName').text(appt.ClientName); 
    $('#successDate').text(processDate(appt.Date)); 
    switchViews(); 
} 
function processDate(dateString) { 
    return new Date(parseInt(dateString.substr(6, 
        dateString.length - 8))).toDateString(); 
} 
function switchViews() { 
    var hidden = $('.hidden'); 
    var visible = $('.visible'); 
    hidden.removeClass("hidden").addClass("visible"); 
    visible.removeClass("visible").addClass("hidden"); 
} 
 
$(document).ready(function () { 
    $('#backButton').click(function (e) { 
        switchViews(); 
    }); 
}); 

This is the same code that we used previously—we have just moved it into a separate file. The next step is to change the /Views/Home/MakeBooking.cshtml view file to remove the link and script elements for which we have created a bundle, as shown in Listing 24-8. We only want the browser to request the files it needs; leaving those elements in place will lead to duplicate requests. The only script element that remains is the one that refers to the view-specific MakeBooking.js file we created in Listing 24-7

這是我們以前使用的相同程式碼-我們剛把它移動到單獨的檔案。我們只想讓瀏覽器請求它需要的檔案,下一步是修改/Views/Home/MakeBooking.cshtml檢視檔案以刪除連結和指令碼元素,留下這些元素會導致重複請求。我們建立了一個bundle,如清單24-8所示。我們在清單24-7中建立MakeBooking.js檔案仍然是指特定檢視唯一的指令碼元素。

Listing 24-8. Removing the Script and Link Elements from the MakeBooking.cshtml View File 
@model ClientFeatures.Models.Appointment 
 
@{ 
    ViewBag.Title = "Make A Booking"; 
    AjaxOptions ajaxOpts = new AjaxOptions { 
        OnSuccess = "processResponse" 
    }; 
} 
<h4>Book an Appointment</h4> 
 
<script src="~/Scripts/Home/MakeBooking.js" type="text/javascript"></script> 
 
<div id="formDiv" class="visible"> 
    @using (Ajax.BeginForm(ajaxOpts)) { 
        @Html.ValidationSummary(true)     
        <p>@Html.ValidationMessageFor(m => m.ClientName)</p> 
        <p>Your name: @Html.EditorFor(m => m.ClientName)</p> 
        <p>@Html.ValidationMessageFor(m => m.Date)</p> 
        <p>Appointment Date: @Html.EditorFor(m => m.Date)</p> 
        <p>@Html.ValidationMessageFor(m => m.TermsAccepted)</p> 
        <p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p>     
        <input type="submit" value="Make Booking" /> 
    } 
</div> 
<div id="successDiv" class="hidden"> 
    <h4>Your appointment is confirmed</h4> 
    <p>Your name is: <b id="successClientName"></b></p> 
    <p>The date of your appointment is: <b id="successDate"></b></p> 
    <button id="backButton">Back</button> 
</div> 

You can refer to bundles in view files, but we tend to create bundles only for content that is shared
across multiple views, and that means we apply bundles in the layout files. In Listing 24-9, you can see the
/Views/Shared/_Layout.cshtml file that Visual Studio added to the project.

您可以在檢視檔案引用bundels,但我們傾向於在多個檢視共享的內容時才建立bundles,這意味著我們將bundles應用在佈局檔案。在清單24-9中,你可以看到有Visual Studio中新增到專案中中的/Views/Shared/_Layout.cshtml檔案。

Listing 24-9. The Layout Added to the Project by Visual Studio 
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="utf-8" /> 
    <meta name="viewport" content="width=device-width" /> 
    <title>@ViewBag.Title</title> 
 
    @Styles.Render("~/Content/css") 
    @Scripts.Render("~/bundles/modernizr") 
</head> 
<body> 
    @RenderBody() 
 
    @Scripts.Render("~/bundles/jquery") 
 
    @RenderSection("scripts", required: false) 
</body> 
</html> 

Bundles are added using the @Scripts.Render and @Styles.Render helper methods. You can see that the layout already contains three bundles, which we have marked in bold. The two calls to @Scripts.Render explain some of the initial profile data. The ~/bundles/jquery bundle is the reason that we ended up with two copies of the jQuery library being requested by the browser. The ~/bundles/modernizr bundle makes the browser request a library that we do not use in our simple application.
In fact, only the ~/Content/css bundle was any help to us because it loaded the /Content/Site.css file
in the original bundle definition and will now load all the CSS files in the /Content folder following the
change we made in Listing 24-9. To get the behavior we want, we have edited the _Layout.cshtml file to
use our newly defined bundles and remove the references that we do not want, as shown in Listing 24-10.

Bundles使用 @Scripts.Render和@Styles.Render輔助器方法進行新增。你可以看到的佈局頁已經包含三個bundle,我們以粗體標記。兩個的呼叫了@Scripts.Rende解析一些初始配置檔案資料。 使用~/bundles/jquery 的bundle的原因是我們最終使用jQuery庫的兩個副本請求瀏覽器。 ~/bundles/modernizr的bundle使瀏覽器請求一個庫,而在簡單的應用程式並不使用它。
事實上,只有~/Content/css 的bundle對我們有一些幫助,因為它裝載了/Content/Site.css的檔案在原生的bundle定義中,現在我們需要載入所有的CSS/ Content資料夾中的檔案因而需要一些修改如清單24-9所示。為了得到我們想要的結果,我們需要修改_Layout.cshtml檔案使用我們新定義的bundle,並刪除我們不想引用,如清單24-10所示。

Listing 24-10. Ensuring the Right Bundles Are Using the _Layout.cshtml File  
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="utf-8" /> 
    <meta name="viewport" content="width=device-width" /> 
    <title>@ViewBag.Title</title> 
    @Styles.Render("~/Content/css") 
</head> 
<body> 
    @RenderBody() 
    @Scripts.Render("~/bundles/clientfeaturesscripts") 
    @RenderSection("scripts", required: false) 
</body> 
</html> 

You can see the HTML that these helper methods generate by starting the application, navigating to the /Home/MakeBooking URL, and viewing the page source. Here is the output produced by the Styles.Render method for the ~/Content/css bundle:

你可以啟動該應用,導航到 /Home/MakeBooking URL,檢視通過這些幫助器方法生成的HTML,並檢視網頁原始碼。下面 @Styles.Render("~/Content/css") 方法產生的:

... 
<link href="/Content/CustomStyles.css" rel="stylesheet"/> 
<link href="/Content/Site.css" rel="stylesheet"/> 
... 
And here is the output produced by the Scripts.Render method: 
... 
<script src="/Scripts/jquery-1.7.1.js"></script> 
<script src="/Scripts/jquery.unobtrusive-ajax.js"></script> 
<script src="/Scripts/jquery.validate.js"></script> 
<script src="/Scripts/jquery.validate.unobtrusive.js"></script> 
... 

Using the Scripts Section
We have one more thing to do. Our view-specific JavaScript contained in the /Scripts/Home/MakeBooking.js file depends on jQuery to set up the event handler for the button. This means that we have to make sure that the jQuery file is loaded before the MakeBooking.js file. If you look at the layout in Listing 24-10, you will see that the call to the RenderBody method is before the call to Scripts.Render, which means that the script element in the view appears before the script elements in the layout and our button code won’t work. (It will either fail quietly or report a JavaScript error, depending on the browser being used.)

使用Script分段
我們還有一件事要做。我們的檢視--特定的指令碼包含在/Scripts/Home/MakeBooking.js檔案中並依賴jQuery來設定按鈕的事件處理程式 這就意味著我們必須確保在載入MakeBooking.js檔案之前載入jQuery檔案 如果你看看清單24-10中的內容,您將看到RenderBody呼叫方法在呼叫 Scripts.Render方法之前。這意味著在我們的按鈕程式碼出現在佈局檢視中的這些指令碼之前,那麼按鈕將無法正常工作。。

We could fix this by moving the Scripts.Render call to the head element in the view; in fact, this is
what we usually do. However, we can also take advantage of the optional scripts section that is defined in
the _Layout.cshtml file and which you can see in Listing 24-10. In Listing 24-11, you can see how we have
updated the MakeBooking.cshtml view to use this section.
Listing 24-11. Using the Optional Scripts Section in the View

我們可以通過呼叫Scripts.Render將指令碼在視方法圖head元素中呈現;事實上,我們通常就這麼做。不過,我們還可以利用_Layout.cshtml檔案定義的可選的指令碼在清單24-10中你可以看到。清單24-11中,可以看到使用了這個section更新了MakeBooking.cshtml檢視

Listing 24-11. Using the Optional Scripts Section in the View 
@model ClientFeatures.Models.Appointment 
 
@{ 
    ViewBag.Title = "Make A Booking"; 
    AjaxOptions ajaxOpts = new AjaxOptions { 
        OnSuccess = "processResponse" 
    }; 
} 
<h4>Book an Appointment</h4> 
@section scripts { 
    <script src="~/Scripts/Home/MakeBooking.js" type="text/javascript"></script> 
} 
<div id="formDiv" class="visible"> 
    @using (Ajax.BeginForm(ajaxOpts)) { 
        @Html.ValidationSummary(true)     
        <p>@Html.ValidationMessageFor(m => m.ClientName)</p> 
        <p>Your name: @Html.EditorFor(m => m.ClientName)</p> 
        <p>@Html.ValidationMessageFor(m => m.Date)</p> 
        <p>Appointment Date: @Html.EditorFor(m => m.Date)</p> 
        <p>@Html.ValidationMessageFor(m => m.TermsAccepted)</p> 
        <p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p>     
        <input type="submit" value="Make Booking" /> 
    } 
</div> 
<div id="successDiv" class="hidden"> 
    <h4>Your appointment is confirmed</h4> 
    <p>Your name is: <b id="successClientName"></b></p> 
    <p>The date of your appointment is: <b id="successDate"></b></p> 
    <button id="backButton">Back</button> 
</div> 

The scripts section appears after the call to Scripts.Render in the layout, which means that our view-
specific script won’t be loaded until after jQuery, and our button element will work the way we intended.
This is an incredibly common mistake to make when using bundles, which is why we have demonstrated
it explicitly.

scripts section 出現在佈局呼叫Scripts.Render方法之後,這意味著我們的檢視載入jQuery庫完成後才載入指定的指令碼,我們的按鈕元素將我們預期的方式工作。
是一種使在使用bundels時非常常見的錯誤,這就是為什麼我們明確說明它的原因。

Profiling the Changes
We have defined our own bundles, removed the unwanted JavaScript references, and generally tidied up our use of scripts and style sheets. It is now time to profile our changes and get a handle on the difference. To do this, we cleared the browser cache and navigated to the /Home/MakeBooking URL, and monitored the requests the browser makes using the F12 tools. You can see the results in Figure 24-3.

變動的效能分析
我們已經定義我們自己的bundles,去掉了不必要的JavaScript引用,並且整理了通常我們使用的指令碼和樣式表。現在是時候來分析我們的更改並瞭解這些區別。要做到這一點,我們清除瀏覽器快取導航到 /Home/MakeBooking的網址,使用F12工具並監測瀏覽器發出的請求。在圖24-3中,你可以看到的結果。


Here is the summary of the profile information:
• The browser made eight requests for the /Home/MakeBooking URL.
• There were two requests for CSS files.
• There were five requests for JavaScript files.
• A total of 2,638 bytes were sent from the browser to the server.
• A total of 326,549 bytes were sent from the server to the browser.
That’s not bad. We have shaved about 30 percent off the amount of data that is sent to the browser.
But we should get even greater benefits when we switch the application from its debugging to deployment
configuration, which we do by setting the debug attribute on the compilation element in the Web.config
file to false, as shown in Listing 24-12.
下面是優化的資訊摘要:
•瀏覽器向/Home/MakeBooking URL傳送了有8請求的。
•有2個CSS檔案的請求。
•有5個JavaScript檔案的請求。
•共2,638個位元組從瀏覽器傳送到伺服器。
•共326,549位元組,從伺服器傳送到瀏覽器。
這已經非常不錯了。我們已幹掉了被髮送到瀏覽器的大約30%的的資料量。但是,我們還可以得到更多的好處,當我們通過將Web.config檔案中compilation 元素的debug屬性設定為false將應用程式從除錯模式切換到部署模式。如清單24-12所示。

Listing 24-12. Disabling Debug Mode in the Web.config File  
... 
<system.web> 
    <httpRuntime targetFramework="4.5" /> 
    <compilation debug="true" targetFramework="4.5" /> 
... 

Once we have made this change, we restart the application, clear the browser cache, and profile the network requests again. You can see the results in Figure 24-4.

一旦我們做了這些改變,我們重新啟動應用程式,清除瀏覽器快取,並再次剖析網路請求。如圖24-4中,你可以看到如下結果.

 

Figure 24-4. Profiling with bundles in the deployment configuration Here is the summary of the profile information: 

• The browser made four requests for the /Home/MakeBooking URL. 
• There was one request for CSS. 
• There were two requests for JavaScript files. 
• A total of 1,372 bytes were sent from the browser to the server. 
• A total of 126,340 bytes were sent from the server to the browser.

 

圖24-4。剖析bundles 在部署配置優化的資訊摘要:

•瀏覽器向/Home/MakeBooking URL傳送了有4請求的。
•對CSS有1個請求。
•有2個JavaScript檔案的請求。
•共1,372個位元組從瀏覽器傳送到伺服器。
共126,340位元組,從伺服器傳送到瀏覽器。


You might be wondering why there are fewer requests for CSS and JavaScript files. The reason is that the MVC Framework concatenates and minifies the style sheets and JavaScript files in the deployment mode so that all the content in a bundle can be loaded in a single request. You can see how this works if you look at the HTML that the application renders. Here is the HTML that the Styles.Render method has produced:

你也許會奇怪,為什麼會有更少的對CSS和JavaScript檔案的請求。原因是MVC框架連線和minifies樣式表和部署模式中的JavaScript檔案,以便所有bundle中的內容可以載入在單個請求。如果你看一下應用程式呈現的HTML你可以看到這是如何工作的。這是Styles.Render方法產生的HTML樣式:

... 
<link href="/Content/css?v=6jdfBoUlZKSHjUZCe_rkkh4S8jotNCGFD09DYm7kBWE1"  
    rel="stylesheet"/> 
... 
And here is the HTML produced by the Scripts.Render method: 
... 
<script   src="/bundles/clientfeaturesscripts?v=SOAJUpvGwNr0XsILBsLrEwEpdyIziIN9frqxIjgTyWE1"> 
</script> 
... 

These long URLs are used to request the contents of a bundle in a single blob of data. The MVC Framework minifies CSS data differently from JavaScript files, which is why we have to keep style sheets and scripts in different bundles.
The impact of the optimizations we have applied is significant. We have far fewer requests from the browser, which reduces the amount of data sent to the client. And we have less data sent in return; in fact, we have sent about 27 percent of the volume of data that we recorded when we profiled the application at the start of the chapter.
This is the point where we stop optimizing the requests. We could add our MakeBooking.js file to a bundle to eliminate another request and have the MVC Framework minify the code, but we have reached the point where our returnsare diminished and we start mixing up the content that is view-specific with the code that is in the layout, which we prefer not to do. If we had a more complex application, we might well create a second script bundle that contains more custom code, but we have a simple application and our final rule of optimization is to know where to stop. For this application, we have reached that point.

 這些長url用於請求的內容包中的單個blob資料。 MVC框架不同的JavaScript檔案minifies CSS資料,這就是為什麼我們必須保持樣式表和指令碼在不同的bundle中。優化對應用的影響很大,來自瀏覽器的請求要少得多,減少了大量資料傳送到客戶端和回傳送較少的資料;事實上,我們已傳送的資料量只有我們在一章的開始分析的應用程式時的27%左右。這是優化的請求停止的時候。 我們可以加入我們的MakeBooking.js檔案到另一個bundle,消除另一個請求,並有縮小MVC框架程式碼,但我們已經達到了我們的預期的壓縮的效果,我們開始混合了具體的檢視的內容和佈局的程式碼,這是我們不喜歡做的內容。如果我們有一個更復雜的應用,我們很可能會建立第二個script bundle,包含更多的自定義程式碼,但有對於一個簡單的應用程式,最後的優化準則是知道在哪裡停止的。對於這種應用,我們已經達到了這一點。

 

 

 

相關文章