Bundle小鎮(zhèn)中由EasyUI引發(fā)的“血案”

由于默認(rèn)的 ASP.NET MVC 模板使用了 Bundle 技術(shù),大家開始接受并喜歡上這種技術(shù)。Bundle 技術(shù)通過 Micorosoft.AspNet.Web.Optimization 包實現(xiàn),如果在 ASP.NET WebForm 項目中引入這個包及其依賴包,在 ASP.NET WebForm 項目中使用 Bundle 技術(shù)也非常容易。

創(chuàng)新互聯(lián)專注于鎮(zhèn)安企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站開發(fā),購物商城網(wǎng)站建設(shè)。鎮(zhèn)安網(wǎng)站建設(shè)公司,為鎮(zhèn)安等地區(qū)提供建站服務(wù)。全流程按需求定制網(wǎng)站,專業(yè)設(shè)計,全程項目跟蹤,創(chuàng)新互聯(lián)專業(yè)和態(tài)度為您提供的服務(wù)


關(guān)于在 WebForm 中使用 Bundle 技術(shù)的簡短說明

通過 NuGet 很容易在 WebForm 項目中引入Microsoft.AspNet.Web.Optimization 包及其依賴包。不過在 MVC 項目的 Razor 頁面中可以使用類似下面的語句引入資源

@Scripts.Render("...")

而在 *.aspx 頁面中則需要通過 <%= %> 來引入了:

<%@ Import Namespace="System.Web.Optimization" %>
// ...
<%= Scripts.Render("...") %>

備注 有些資料中是使用的 <%: %>,我實在沒有發(fā)現(xiàn)它和 <%= %> 有啥區(qū)別,但至少我在《ASP.NET Reference》的《Code Render Blocks》一節(jié)找到了 <%= %>,卻暫時沒在官方文檔里找到 <%: %>


然后,我在一個使用了 EasyUI 的項目中使用了 Bundle 技術(shù)。才開始一切正常,至到第一個 Release 版本測試的那一天,“血案”發(fā)生了——

由于一個腳本錯誤,EasyUI 沒有生效。最終原因是 Bunlde 在 Release 版中將 EasyUI 的腳本壓縮了——當(dāng)然,定位到這個原因還是經(jīng)歷了一翻周折,這就不細(xì)說了。

[方案一] 禁用代碼壓縮

這個解決方案理論上只需要在配置里加一句話就行:

BundleTable.EnableOptimizations = false;

但問題在于,這樣一來,為了一個 EasyUI,就放棄了所有腳本的壓縮,而僅僅只是合并,效果折半,只能當(dāng)作萬不得已的備選。

[方案二] 分段引入并阻止壓縮 EasyUI 的 Bundle

先看看原本的 Bundle 配置(已簡化)

public static void Register(BundleCollection bundles)
{
    bundles.Add(new ScriptBundle("~/libs")
        .Include("~/scripts/jquery-{version}.js")
        .Include("~/scripts/jquery.eaysui-{versoin}.js")
        .Include("~/scripts/locale/easyui-lang-zh_CN.js")
        .IncludeDirectory("~/scripts/app", "*.js", true)
    );
}

這段配置先引入了 jquery,再引入了 easyui,最后引入了一些為當(dāng)前項目寫的公共腳本。為了實現(xiàn)解決方案二,必須要改成分三個 Bundle 引入,同時還得想辦法阻止壓縮其中一個 Bundle。

要分段,簡單

public static void Register(BundleCollection bundles)
{
    bundles.Add(new ScriptBundle("~/jquery")
        .Include("~/scripts/jquery-{version}.js")
    );
    bundles.Add(new ScriptBundle("~/easyui")
        .Include("~/scripts/jquery.eaysui-{versoin}.js")
        .Include("~/scripts/locale/easyui-lang-zh_CN.js")
    );
    bundles.Add(new ScriptBundle("~/libs")
        .IncludeDirectory("~/scripts/app", "*.js", true)
    );
}

但為了阻止壓縮,查了文檔,也搜索了不少資料都沒找到解決辦法,所以只好看源碼分析了,請出 JetBrains dotPeek。分析代碼之后得出結(jié)論,只需要去掉默認(rèn)的 Transform 就行

// bundles.Add(new ScriptBundle("~/easyui")
//     .Include("~/scripts/jquery.eaysui-{versoin}.js")
//     .Include("~/scripts/locale/easyui-lang-zh_CN.js")
// );
Bundle easyuiBundle = new ScriptBundle("~/easyui")
    .Include("~/scripts/jquery.eaysui-{versoin}.js")
    .Include("~/scripts/locale/easyui-lang-zh_CN.js")
);
easyuiBundle.Transforms.Clear();
bundles.Add(easyuiBundle);


關(guān)鍵代碼的分析說明

首先從 ScriptBunlde 入手

public class ScriptBundle: Bundle {
    public ScriptBundle(string virtualPath)
        : this(virtualPath, (string) null) {}

    public ScriptBundle(string virtualPath, string cdnPath)
        : base(virtualPath, cdnPath,
            (IBundleTransform) new JsMinify()
        ) {
        this.ConcatenationToken = ";" + Environment.NewLine;
    }
}

可以看出,ScriptBunlde 的構(gòu)建最終是通過其基類 Bunlde 中帶 IBunldeTransform 參數(shù)的那一個來構(gòu)造的。再看 Bunlde 的關(guān)鍵代碼

public class Bunlde 

    public IList<IBundleTransform> Transforms {
        get { return this._transforms; }
    }

    public Bundle(
        string virtualPath,
        string cdnPath,
        params IBundleTransform[] transforms
    ) {

        // ...

        foreach(IBundleTransform bundleTransform in transforms) {
            this._transforms.Add(bundleTransform);
        }
    }
}

容易理解,ScriptBunlde 構(gòu)建的時候往 Transforms 中添加了一默認(rèn)的 Transform——JsMinify,從名字就可以看出來,這是用來壓縮腳本的。而 IBundleTransform 只有一個接口方法

public interface IBundleTransform {
    void Process(BundleContext context, BundleResponse response);
}

看樣子它是在處理 BundleResponse。而 BundleResponse 中定義有文本類型的 Content 和 ContentType 屬性,以及一個 IEnumerable<BundleFile> Files。

為什么是 Files 而不是 File 呢,我猜 Content 中包含的是一個 Bundle 中所有文件的內(nèi)容,而不是某一個文件的內(nèi)容。要驗證也很容易,自己實現(xiàn)個 IBundleTransform 試下就行了

Bundle b = new ScriptBundle("~/test")
    .Include(...)
    .Include(...);
b.Transforms.Clear();b.Transforms.Add(new MyTransform())

// MyTransform 可以自由發(fā)揮,我其實啥都沒寫,只是在 Process 里打了個斷點,檢查了 response 的屬性值而已

實驗證明在 BundleResponse 傳入 Transforms 之前,其 Content 就已經(jīng)有所有引入文件的內(nèi)容了。


方案二解決了方案一不能解決的問題,但同時也帶來了新問題。原來只需要一句話就能引入所有腳本

@Scripts.Render("~/libs")

而現(xiàn)在需要 3 句話

@Scripts.Render("~/jquery")
@Scripts.Render("~/easyui")
@Scripts.Render("~/libs")

[方案三] Bundle 的 Bundle

鑒于方案二帶來的新問題,試想,如果有一個東西,能把 3 個 Bundle 對象組合起來,變成一個 Bundle 對象,豈不是就解決了?

于是,我發(fā)明了 Bundle 的 Bundle,不妨就叫 BundleBundle 吧。

public class BundleBundle : Bundle{
    readonly List<Bundle> bundles = new List<Bundle>();
 
    public BundleBundle(string virtualPath)
        : base(virtualPath)
    {
    }
 
    public BundleBundle Include(Bundle bundle)
    {
        bundles.Add(bundle);
        return this;
    }
 
    // 在引入 Bundle 對象時申明清空 Transforms,這幾乎就是為 EasyUI 準(zhǔn)備的
    public BundleBundle Include(Bundle bundle, bool isClearTransform)
    {
        if (isClearTransform)
        {
            bundle.Transforms.Clear();
        }
        bundles.Add(bundle);
        return this;
    }
 
    public override BundleResponse GenerateBundleResponse(BundleContext context)
    {
        List<BundleFile> allFiles = new List<BundleFile>();
        StringBuilder content = new StringBuilder();
        string contentType = null;
 
        foreach (Bundle b in bundles)
        {
            var r = b.GenerateBundleResponse(context);
            content.Append(r.Content);

            // 考慮到 BundleBundle 可能用于 CSS,所以這里進(jìn)行一次判斷,
            // 只在 ScriptBundle 后面加分號(兼容 ASI 風(fēng)格腳本)
            // 這里可能會出現(xiàn)在已有分號的代碼后面加分號的情況,
            // 考慮到只會浪費 1 個字節(jié),忍了
            if (b is ScriptBundle)
            {
                content.Append(';');
            }
            content.AppendLine();
 
            allFiles.AddRange(r.Files);
            if (contentType == null)
            {
                contentType = r.ContentType;
            }
        }
 
        var response = new BundleResponse(content.ToString(), allFiles);
        response.ContentType = contentType;
        return response;
    }
}

使用 BundleBundle 也簡單,就像這樣

bundles.Add(new BundleBundle("~/libs")
    .Include(new ScriptBundle("~/bundle/jquery")
        .Include("~/scripts/jquery-{version}.js")
    )
    .Include(
        new ScriptBundle("~/bundle/easyui")
            .Include("~/scripts/jquery.easyui-{version}.js")
            .Include("~/scripts/locale/easyui-lang-zh_CN.js")
    )
    .Include(new ScriptBundle("~/bundle/app")
        .IncludeDirectory("~/scripts/app", "*.js", true)
    )
);

然后

@Scripts.Render("~/libs")

注意,每個子 Bundle 都有名字,但這些名字不能直接給 @Scripts.Render() 使用,因為它們并沒有直接加入 BundleTable.Bundles 中。但名字是必須的,而且不能是 null,不信就試試。

當(dāng)前名稱:Bundle小鎮(zhèn)中由EasyUI引發(fā)的“血案”
網(wǎng)址分享:http://bm7419.com/article46/jjsjhg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航企業(yè)網(wǎng)站制作、自適應(yīng)網(wǎng)站、小程序開發(fā)、域名注冊、App設(shè)計

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

手機(jī)網(wǎng)站建設(shè)