在ASP.NET MVC中使用ScriptBundle打包十來個Script,發現壓縮後的JavaScript無法正常執行,費了好大功夫,抓到問題出在某個變數名稱重複。再費了好一番力氣,抽絲剝繭整理出能重現問題的最小JavaScript樣本如下:

$.fn.setMod = function(id, cb, vm) {
    var $elem = this.first();
    $.post("mod/" + id, function (r) {
        $("<a />").appendTo($elem);
        if (r.js) eval(r.js);
        if (cb) cb(r.arg, vm);
    }, "json");
};

在BundleConfig.cs中定義打包組合,只放入這個JS檔:

        public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new ScriptBundle("~/bundles/test").Include(
                        "~/Scripts/mod.js"));
        }

寫一個簡單的View進行驗證:

<!DOCTYPE html>
 
<html>
    <head>
        <title>Minify Issue Test</title>
        <script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.0.min.js"></script>
        @Scripts.Render("~/bundles/test")
    </head>
    <body>
        Test
        <div id="display">
        </div>
    <script>
        $("#display").html($.fn.setMod.toString());
    </script>
    </body>
</html>

修改web.config停用Debug模式,蟲蟲現身! 壓縮後的JavaScript如下:

function (n,t,i){var r=this.first();$.post("mod/"+n,function(r){$("<a />").appendTo(r),r.js&&eval(r.js),t&&t(r.arg,i)},"json")}

由以上程式可以看到一處不合理,變數 r 既是jQuery first()傳回結果,在$.post() callback又是傳入參數,當執行到callback的appendTo(r)就爆炸了。測試過程中發現調整程式內容會影響n, t, i, r 等變數命名,研判其中有某種演算法隨機為內部變數更名,該演算法在特定組合下會產重複變數名稱,因只在特殊情境下才發生,成為久埋未解的Bug。在stackoverflow上找到一則變數名稱重複導致錯誤的類似案例,與我的狀況有些共同點: 使用jQuery、問題變數與this有關、重複變數名也出現在Callback參數... 等,猜想Bug與這些因素可能有相關性。

原本推測可能是ASP.NET MVC所用的Microsoft.Ajax.Utilities.Minifier有Bug,但經過自行用Minifier.MinifyJavaScript()壓縮同一JavaScript卻不會出現變數名稱重複,只有結合ScriptBundle才能重現問題。試著用NuGet更新到WebGrease 1.5.2、Microsoft.AspNet.Web.Optimization 1.1.0,問題依舊,只能推斷是特定情境下才會發生的Bug,但來源未定。

至於解決之道,我在這個案例中,將原程式$.post()的function(r)改為function(result)就解決了,但無法確定是避免用單一字母變數名稱有效,還是純粹因為更名影響隨機命名使然。總之,未來在遇ScriptBundle壓縮後JavaScript有錯的情境,可以先查查是否有變數名稱重複的方向偵辦。這點經驗提供遇到類似問題的朋友參考。


Comments

# by 大金

請教黑大,由於現在很多 JS Framework (jQuery、DataTables...)都有支援 CDN,所以我大部分也都使用 CDN 來降低網路頻寬;若 asp.net MVC 已經有支援 ScriptBundle 壓縮打包,那是否還需要使用 CDN? 還是有評估的建議?

# by Jeffrey

to 大金,ScriptBundle有支援CDN(請參見 http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification Using CDN一節),可以在Release環境用CDN、Debug模式用本機版。 依我個人看法,ScriptBundle雖然可以壓縮JS節省頻寬,但仍會累積頻寬費用,且若網站使用者遍及全球,CDN能避免越洋下載的傳輸延遲。如果網站對外且流量偏大,使用CDN會有明顯效能提升及節費效果;反之,則使用CDN與否差異不大。

# by 大金

謝謝黑大

Post a comment