ASP.NET MVC - 即時整合與壓縮網站輸出的CSS及JavaScript檔案

讓網頁有高效率

不知道你有沒有為了
  • SEO
  • 安全性
  • 加速網頁載入
種種的理由,去進行過任何的CSS與JavaScript檔案壓縮的行為。在 YSlow 這套測試網頁執行率效軟體的首頁中,提供一篇「Web Performance Best Practices and Rules」文章,此篇文章針對網頁效率提供了 23 個非常棒建議(註1)
  1. 減少發出HTTP請求 (Minimize HTTP Requests)
  2. 使用內容分散式網路 (Use a Content Delivery Network (CDN))
  3. 避免空白的 src 或 href (Avoid empty src or href)
  4. 增加過期或Cache-Control標頭(add an Expires or a Cache-Control Header)
  5. 使用Gzip 組件(Gzip Components)
  6. 樣式表(CSS檔案)放在最頂端(Put StyleSheets at the Top)
  7. 腳本(JavaScript檔案)放在最底端(Put Scripts at the Bottom)
  8. 避免 CSS 表達式(Avoid CSS Expressions)
  9. 將 JavaScript 與 CSS 放在外部檔案(Make JavaScript and CSS External)
  10. 減少 DNS 查詢(Reduce DNS Lookups)
  11. 壓縮 JavaScript 與 CSS(Minify JavaScript and CSS)
  12. 避免重導向(Avoid Redirects)
  13. 刪除重複的腳本(JavaScript檔案)(Remove Duplicate Scripts)
  14. 設置 ETags(Configure ETags)
  15. 讓AJAX能快取(Make AJAX Cacheable)
  16. 在AJAX請求中使用 GET 方法(Use GET for AJAX Requests)
  17. 減少DOM元素數量(Reduce the Number of DOM Elements)
  18. 不要有HTTP 404錯誤(No 404s)
  19. 減少 Cookie 大小(Reduce Cookie Size)
  20. 為組件使用 Cookie-free 網域(Use Cookie-free Domains for Components)
  21. 避免過濾器(Avoid Filters)
  22. 不要在HTML裡使用縮放圖片(Do Not Scale Images in HTML)
  23. 讓favicon.ico圖小且能快取(Make favicon.ico Small and Cacheable)

註1:在查詢相關說明資料時發現,Web Performance Best Practices and Rules 文章內不只有 23 個建議,列在 YSlow 上的 23 個建議,應該重點中的重點了。有些看標題就能了解內容,有些則相反,必須去讀內容能才了解,例如,第 20 點的Cookie-free?非常建議花一些時間全部完整看完。如果你對網頁執行效率或是想改善你的網站/網頁,由此為出發點,絕對是個非常好的開始。另外,這篇 Web Performance Best Practices and Rules 文章和另一本 2007 出版的 High Performance Web Sites: Essential Knowledge for Front-End Engineers (中文:網頁加速的14條黃金法則)重複性很高,內容差不多,原因也很簡單,此書作者是曾任雅虎CPO(Chief Performance Officer),所以 Web Performance Best Practices and Rules 應該是此書的重點整理版本,作者還提供一個測試那 14 條規則的網站http://stevesouders.com/hpws/rules.php,也非常值的一玩。還有,Will保哥也針對 High Performance Web Sites 這本書提供了非常棒的經驗分享「加速前端網頁效能的14條規則」推薦非讀不可。

第2點 CDN 部分,一般網站如果不是成長到一定規模,是不太可能去使用,不過就 Scripts 部分,到是可以使用免費的 CDN 服務。可以參考我的「Microsoft Ajax content delivery network (CDN)」、「Microsoft ajax CDN Domain改名為aspnetcdn」、「jquery-1.4.4-vsdoc.js會讓jQuery無法運作」這幾篇。

借上面的項目,我想要討論的就是第11點「壓縮 JavaScript 與 CSS(Minify JavaScript and CSS)」。其實第9點「將 JavaScript 與 CSS 放在外部檔案(Make JavaScript and CSS External)」可以和第11點一起討論。

當網站開發到一定層度,我們會開始切割CSS檔案及JavaScript檔案以方便管理。

CSS檔案分割範例
例如,原本單一的CSS檔案,我們分割為 reset.css、layout.css、header.css、main.css、footer.css、ie6.css,依每個網站需求規劃而定。


JavaScript檔案分割範例
例如,原本單一的JavaScript檔案,我們分割為ajax.js、cookie.js、dom.js、forms.js、tables.js、validate.js、windows.js、ie6hack.js、ie7.js、ieall.js,依每個網站需求規劃而定。


也就是第9點所討論的內容。我認為,切割最重要的是「縮小問題點。例如,validate.js,是我常新增、修改、使用的一支 JavaScript,當使用者反應說"驗證"方便有問題時,我馬上知道要去找那一支程式查詢及修改,例如有一次使用者反應,為什麼程式在 Firefox 之下都不會有任何提示,而只有 IE 有?查詢之下,我使用到 IE 專用語法,結果所有 Firefox 都可以在不驗證情況下送出表單,嚇死我了。

網站上線之後

好了,通常那是上線開發之時,那上線之後呢?先看一個討論串「抄很大、抄不用錢」,線上之前。建議進行第11點「壓縮 JavaScript 與 CSS(Minify JavaScript and CSS)」,這方面的線上工具很多,然後進行上線動作。

回到討論串,因為那些 JavaScript 沒有經過壓縮,都是明碼,要了解及看懂內容對有功力的人,不是太難。以 JavaScript 而言,壓縮過程會順便重新編碼過,讓人看懂不容易。如果你辛苦寫了 JavaScript 或 jQuery 的程式碼,不想很容易讓人家複製使用,那請執行壓縮這一步驟。

你可以試著去看 jquery-version.js 與 jquery-version.min.js,兩個 JavaScript 檔案的內容。就會了解我說的意思。

但切割及壓縮CSS與JavaScript檔案容易造成一個問題:「管理不易。」

你可以很方便針對「專案」進行版本控制,但針對大量切割的CSS檔案與JavaScript檔案進行版本控制不太容易。另一重點是,我每次只對針對CSS或JavaScript檔案進行了修改,就必須「重新製作壓縮後的CSS與JavaScript檔案」,然後上傳、覆寫原有的CSS與JavaScript檔案。

或許你能學 jQuery,分為未壓縮 jquery-version.js 與壓縮 jquery-version.min.js,也是不錯,不過那一個 min 很好猜,有心人大概只要把 min 去除,就可以得到未壓縮的原始碼。

這方面我有二個想法:
  1. 讓未壓縮與壓縮檔案命名之間無關聯性,但這又造成「管理更加不易」。
  2. 讓未壓縮與壓縮檔案放在不同目錄之中,且那著目錄命名差異大或無關聯性。但還是一樣,易造成「管理更加不易」。

第二點,例如,jsUnZip與jsZip兩個目錄,關聯性及命名差異不大,也是很好猜。但如果改為kkbruceUUUjs(UUU代表unzip)與kkbruceZjs(Z代表zip)目錄,你只要不要在上線網頁引用kkbruceUUUjs目錄裡的 JavaScript 檔案,我想就很難猜得到。

還是麻煩呀,有沒有更好的辦法?

有,不然我前面廢話連篇做什麼?

即時壓縮CSS及JavaScript檔案

如果可以在網站上線之後,有個程式可以幫我們整合分割的CSS與JavaScript檔案,而且順便壓縮CSS與JavaScript檔案,不知道有多好?

整合的意思是,將多個分割的檔案整理、整合為單一個CSS與JavaScript檔案來輸出,那樣的話,我又可以得到第1點「減少發出HTTP請求 (Minimize HTTP Requests)」的好處,以我上述分割的CSS與JavaScript例子為例,CSS與JavaScript檔案如果要全部載入,需使用16次HTTP請求,但整合後只需要2次(1個*.css,一個*.js)HTTP請求,差異馬上出現。

你的需求,Hajan Selmani 聽見了。現在救星出現了,它的名子叫「SquishIt」。

以SquishIt Framework得到即時壓縮CSS與JavaScript檔案功能


讓我們開一個全新的ASP.NET MVC 3 或 ASP.NET MVC 4專案來實作。既然前些日子我們已經把 Visual Studio 11 與 ASP.NET MVC 4 準備好了,我當然是選擇使用 Visual Studio 11 + ASP.NET MVC 4 來實作。(步驟上與 Visual Studio 2010 + ASP.NET MVC 3 是沒有差異,只是用 ASP.NET MVC 4 抓出來的圖會好看些,^_^。)

  1. File → New Project → Visual Basic → Name: Mvc4SquishIt → OK → Internet Application : ASPX → OK
  2. Tools → Library Package Manager → Package Manager Console | Manage NuGet Packages...

Package Manager Console 是使用指令方式;Manage NuGet Packages... 是 GUI 方式。Package Manager Console 有個好用的功能,我不知道各位知不知道,就是好用的「Tab 指令選擇」。

例如,我輸入 in → 然後按 Tab 鍵:

Package Manager Console 的Tab鍵找指令
圖一:Package Manager Console 的Tab鍵找指令
不只這樣,它也能幫我們找 NuGet 上的套件,例如,我譬一樣輸入 squ → 然後按 Tab 鍵 → 因為它要去 NuGet 比對,需要時間等一下:

Package Manager Console 的Tab鍵找套件
圖二:Package Manager Console 的Tab鍵找套件
對於那種又臭又長命名的套件名稱,這可是非常好用。Package Manager Console 小技巧教完了,把 SquishIt 安裝進我們 ASP.NET MVC 4 專案裡把。

PM> Install-Package SquishIt
Attempting to resolve dependency 'YUICompressor.NET (≥ 1.5.0.0)'.
Attempting to resolve dependency 'dotless (≥ 1.1.0)'.
Attempting to resolve dependency 'AjaxMin (≥ 4.13.4076.28499)'.
Successfully installed 'YUICompressor.NET 1.5.0.0'.
Successfully installed 'dotless 1.1.0'.
Successfully installed 'AjaxMin 4.13.4076.28499'.
Successfully installed 'SquishIt 0.7.1'.
Successfully added 'YUICompressor.NET 1.5.0.0' to Mvc4SquishIt.
Successfully added 'dotless 1.1.0' to Mvc4SquishIt.
Successfully added 'AjaxMin 4.13.4076.28499' to Mvc4SquishIt.
Successfully added 'SquishIt 0.7.1' to Mvc4SquishIt.


SquishIt 安裝了不少東西,除了自己 SquishIt 還安裝了YUICompressor.NET 1.5.0.0 (這我知道是壓縮CSS)、dotless (好像也是壓縮CSS)、AjaxMin (壓縮CSS與JavaScript),你可以在「References(參考)」之下所有安裝的組件檔。

安裝完,先建置專案(Alt + B, Alt + U,這太常用了,背起來)。

在需要使用的地方,先引用 NameSpace ,然後撰寫很簡單的程式碼。我們以 Site.Master 為例,

Site.Master 加入 SquishIt

<%@ Master Language="VB" Inherits="System.Web.Mvc.ViewMasterPage" %>
<%@ Import Namespace="Squishit.Framework" %>
<!DOCTYPE html>
<html lang="en">
    <head runat="server">
        <meta charset="utf-8" />
        <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
        <!-- 把原始Site.css段落註解 -->
        <!--
        <link href="<%: Url.Content("~/Content/Site.css") %>" rel="stylesheet" type="text/css" />
        -->
        <!-- 使用Squishit.Framework提供Bundle功能來產出CSS檔案 -->
        <%= Bundle.Css().
                   Add("~/Content/Site.css").
                   Add("~/Content/themes/base/jquery.ui.all.css").
                   Render("~/Content/Site_Squishit_#.css") %>
        <script src="<%: Url.Content("~/Scripts/jquery-1.6.2.min.js") %>" type="text/javascript"></script>
        <script src="<%: Url.Content("~/Scripts/modernizr-2.0.6-development-only.js") %>" type="text/javascript"></script>
        <meta name="viewport" content="width=device-width">
    </head>
    <body>
        <header>
   <!-- 略 -->
        </header>
        <div id="body">
   <!-- 略 -->
        </div>
        <footer>
   <!-- 略 -->
        </footer>
        <!-- 依文件第7點建議,將JavaScript檔放在最底端 -->
        <!-- 使用Squishit.Framework提供Bundle功能來產出JavaScript檔案 -->
        <%= Bundle.JavaScript().
                   Add("~/Scripts/jquery-ui-1.8.11.js").
                   Add("~/Scripts/jquery.unobtrusive-ajax.js").
                   Render("~/Scripts/jquery.squishit.#.js") %>
    </body>
</html>


  1. 引用Namespace="Squishit.Framework"。
  2. SquishIt 的使用很簡單,都是使用 Bundle 開頭。
  3. Bundle.Css() 代表要處理CSS檔案,Add("Path")加入要處理CSS檔案路徑,可以加入多個Add("Path"),最後的Render("Path_#.css")是要產生出來的CSS壓縮整合檔案,重點在那個"#","#"會被替代為一組隨機數字
  4. Bundle.JavaScript() 代表要處理JavaScript檔案,後面的Add("Path")與Render("Path_#.js") 意義同上。

建置,Ctrl + F5,把專案執行起來。

使用 SquishIt 輸出CSS與JavaScript檔案
圖三:使用 SquishIt 輸出CSS與JavaScript檔案
畫面看起來一切正常,

讓我們看一下 Home/Index 的原始碼:

<!DOCTYPE html>
<html lang="en">
    <head><meta charset="utf-8" /><title>
    Welcome - My ASP.NET MVC Application
</title>
  <!-- 略 -->
        <!-- 使用Squishit.Framework提供Bundle功能來產出CSS檔案 -->
        <link rel="stylesheet" type="text/css" href="/Content/Site.css" />
 <link rel="stylesheet" type="text/css" href="/Content/themes/base/jquery.ui.all.css" />
        <!-- 略 -->
    <body>
  <!-- 略 -->
        <!-- 依文件第7點建議,將JavaScript檔放在最底端 -->
        <!-- 使用Squishit.Framework提供Bundle功能來產出JavaScript檔案 -->
        <script type="text/javascript" src="/Scripts/jquery-ui-1.8.11.js"></script>
 <script type="text/javascript" src="/Scripts/jquery.unobtrusive-ajax.js"></script>
    </body>
</html>

怎麼好像和我要介紹的不一樣?開 Firebug 看一下:

Firebug - SquishIt 網路連線圖
圖四:Firebug - SquishIt 網路連線圖
載入是正確啦,但沒有整合也沒有壓縮,這是「騙瘋仔嗎?(台)」

No,No,No,代誌不是憨人所想得那麼簡單。

SquishIt 設想的很周詳,把開發環境和正式上線環境分的很清楚。我們的專案預設為開發環境,在開發環境我們需要 Debug,如果一開始就把 CSS 與 JavaScript 整合及壓縮,那是很沒效率也很呆。所以,在開發環境中,SquishIt 會先輸出你在 add("Path") 設定的每一個檔案鏈結,即這一步驟 Render("Path_#") 是不會執行,等到了正式上線環境,才會執行。

SquishIt 是以根目錄下的 web.config 裡的system.web → complilation → debug 屬性來判斷:

<system.web>
  <compilation debug="true" targetFramework="4.5">
  <-- 略 -->

把 debug="true" 修改為 debug="false",儲存 → 建置 → 重新執行網頁。這一次,第一次載入會久一些些,讓我們看 Home/Index 的原始碼:

 <!-- 略 -->
    <!-- 使用Squishit.Framework提供Bundle功能來產出CSS檔案 -->
    <link rel="stylesheet" type="text/css" href="/Content/Site_Squishit_A534E5B4509C8BD890D2EA92C11A9241.css" />
 <!-- 略 -->
<body>
 <!-- 略 -->
 <!-- 使用Squishit.Framework提供Bundle功能來產出JavaScript檔案 -->
 <script type="text/javascript" src="/Scripts/jquery.squishit.BED6C4861B1628CA0A0675F077483465.js"></script>
</body>

看起來是成功了,再看一下 Firebug 的網路圖:

Firebug - SquishIt(Enable) 產生連線圖
圖五:Firebug - SquishIt(Enable) 產生連線圖
SquishIt 會產生一個「整合及壓縮的暫存檔」,在你指定的 Render("Path_#") 的 Path 底下,你必須使用「顯示所有檔案 | Show All Files」才能看到。

載入未壓縮與壓縮JavaScript檔案大小比較表


讓我們來比較一下JavaScript檔案整合及壓縮前後大小。

載入未壓縮JavaScript檔案與SquishIt整合壓縮後JavaScript檔案大小比較表
JavaScript File Name Size SquishIt File Name Size
jquery-ui-1.8.11.js 368 KB jquery.squishit.亂數.js 200 KB
jquery.unobtrusive-ajax.js 6 KB
Total 374 KB Total 200 KB

在 JavaScript 我故意載入未壓縮版本,就算我載入的是壓縮過的 *.min.js 版本,光 jquery-ui-1.8.11.min.js 就已經 214 KB,還是比使用 SquishIt 壓縮過的還大。

載入未壓縮與壓縮CSS檔案大小比較表


讓我們來比較一下CSS檔案整合及壓縮前後大小。

載入未壓縮CSS檔案與SquishIt整合壓縮後CSS檔案大小比較表
CSS File Name Size SquishIt File Name Size
Site.css 12 KB Site_squishit_亂數.css 7 KB
jquery.ui.all.css 1 KB
jquery.ui.core.css 2 KB
jquery.ui.resizable.css 2 KB
jquery.ui.selectable.css 1 KB
jquery.ui.accordion.css 2 KB
jquery.ui.autocomplete.css 2 KB
jquery.ui.button.css 3 KB
jquery.ui.dialog.css 2 KB
jquery.ui.slider.css 2 KB
jquery.ui.tabs.css 2 KB
jquery.ui.datepicker.css 5 KB
jquery.ui.progressbar.css 1 KB
jquery.ui.theme.css 20 KB
Total 57 KB Total 7 KB

SquishIt 使用心得

以上結果很明顯,SquishIt 的壓縮不只可以減少流量的產生,而且整合為單一個檔案的功能更是棒的不得了,可以大量減少HTTP請求。我相信,如果圖片檔案是HTTP請求第一名,那CSS和JavaScript檔案絕對是第二、三名。減少圖片的HTTP請求,那是另一門學問。但光使用一個 SquishIt 與簡單幾行程式碼,就能最佳化CSS與JavaScript檔案,這不管在檔案管理、SEO…都助益太大了。

參考資料


9 則留言:

  1. 這篇文章讚喔,解決了我每次都要自己壓縮js和css的困擾

    不過 JavaScript 放到最下面有個很大的缺點,尤其是使用大量 Jquery UI 的網站,使用者第一次登入到網站或網路比較慢時,使用者會看到未套用 Jquery UI 前的 tab,就整個版面亂掉的樣子

    這個問題也讓我決決一直下不了決定要不要把JavaScript 放到最下面

    回覆刪除
    回覆
    1. 未來的 .NET Framwork 4.5 是直接內建此功能。^_^

      JavaScript 放不放到下方,我是這樣想:
      1. 是針對內容進行的 JavaScript;
      2. 是針對畫面進行的 JavaScript;
      3. 是針對操作進行的 JavaScript;

      1,3 都必須在 HTML 內容完成後才有作用,所以可以放到下方,2 是針對 UI 進行操作,就不合適放到下方去載入,我們就可以定義它是和 CSS 同作用效果,所以應該和 CSS 一樣再一開始就載入。

      你可以試玩 Opera 瀏覽器,它就不管你前進載入,一定先載入主 HTML,再套用相關 CSS等 UI 內容,所以它號稱載入速度世界第一快。

      想清楚,就 OK 啦!

      刪除
  2. 聽到這個讓我開始期待 VS 2011 XD

    我剛剛有試著把 內容進行的 JavaScript 放到上方,其它的 JavaScript 放到下方
    然後使用 Firebug 觀察發現,只要有一個 JavaScript 先載入,其它不管放在那裡都會一樣在圖片載入前執行!!

    不知道你測試是不是也是這樣呢?

    以我以前的習慣來說,我是習慣性在 JavaScript 加上 defer="defer",這樣就算放到最上面,圖片也不會等待 JavaScript 載入完成才載入
    不過自從 Jquery 1.6 之後,使用 defer="defer",會在 IE 8 以下的版本出問題,就沒有在使用它了,不然它很好用的說, 用 YSlow 評分時也會偵測到。

    回覆刪除
    回覆
    1. 壓縮 JavaScript, CSS 是一定要做的。
      因為"手持設備"不像電腦,大量的 Request 會耗費大量電源,大量的封包更不用說,以我的 Blog 來說,瀏覽器是手持設備的已經量大到讓我"嚇到"。

      刪除
  3. 1. 如果你使用的地方圖片很多,有一套 http://www.appelsiini.net/projects/lazyload,
    我用的效果不錯,你試試。
    2. 有時 JavaScript 之間有相依性的話,可能就不會依你安排的順序來下載。
    3. 我們只能盡量安排好,最終的決定權是各家瀏覽器。例如,IE 的 Dev Tool,會自己把 head 的 title,meta chartset=utf-8 改位置。
    4. 你提出的 JavaScript 早於圖片載入的問題,我找個時間試試。

    回覆刪除
  4. lazyload 的確是一套好用的工具,謝謝囉^^

    大量減少 Http Request,我有試過一個方法還不錯
    把 css 裡的 background-image 編碼成 base 64
    如果能夠自動化執行的話,更能取代 css sprites

    回覆刪除
    回覆
    1. 說到 css 裡的 base 64,可參考 VS2010 的擴充組件:http://visualstudiogallery.msdn.microsoft.com/6ed4c78f-a23e-49ad-b5fd-369af0c2107f,可以直接在 VS2010 裡將 css 裡的 background-image 編碼成 base 64。

      VS 11 好像成為內建功能。^_^

      刪除
    2. 我這裡有篇介紹:http://blog.kkbruce.net/2011/11/visual-studio-webcss.html

      刪除
  5. 今天在這裡獲得好多 大感謝 XD

    回覆刪除

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。