System.Web.Optimization的好大家都知道,但是缺點也是無敵多,與其說是缺點,應該是說創造者賦予它太多相關的規定制度,或者是對於其彈性使用零封裝參數來對應,這在大家實際的應用過程中,是一種非常痛苦的經驗。
在Javascript、Css的原始檔案裡面,註解通常被區分成兩種,一種是真正的註解,另外一種是版權(版本)宣告,並不是說真的要尊重版權到將其保留(jQuery團隊也不見得會去責難你這間小公司),而是有時候我們真的需要這個plug-in套件的相關資訊時,很不幸的這些資訊都被移除了。當然,你可以選擇關閉不要用壓縮它,或者直接跑回Server端的資訊查看即可,但是,這樣的需求在某些環境下,確實有其存在的必要。
一個典型的Javascript、Css重要註解(Important Comments)如下:
/*! * 這裡是重要註解,通常記錄著套件名稱、版權與版本資訊 */
但...是的,System.Web.Optimization預設就將註解全部移除掉,且沒有留下任何修改通道的參數。唯一的解決方式就是自己把System.Web.Optimization.IBundleBuilder介面全部實作一次,蠻無奈的。
這算是一種語法解析器未更新,導致於新的語法出現時,引發剖析失敗的問題。例如在Bootstrap ver.4以後,就開始使用新式的Css Variables寫法,例如下面的範例
:root { --global-color: #666; --pane-padding: 5px 42px; }
這樣的「--」寫法將會導致System.Web.Optimization裡面的CSS語法解析器崩潰,進而跳出Exception。同樣的,這樣的問題System.Web.Optimization並沒有留一條退路讓你注入一些設定文件,你必須自己實作System.Web.Optimization.IBundleBuilder介面,然後在Microsoft.Ajax.Utilities.CssSettings()中,將其IgnoreAllErrors。
Javascript這種東西本身就有次序性問題,例如最常見的jQuery Framework一定是擺放在第一位,接著才有可能是套件或其餘的框架之類的。System.Web.Optimization最大的問題就是他自己有實作自己的順序引入規則,但這些規則都是用名稱來決定的,所以往往你會不小心踩到雷,例如你裡面有一個套件名稱相近,會被它識別為JS核心框架,於是它就先將這個錯誤的套件先引入了。
我認為這個特性蠻好笑的,大概是「過度設計」的最佳典範。試想,會使用System.Web.Optimization來進行整體網站打包作業的,不知道前端的引入順序有幾人?(如果你真的不知道順序,那你還會用System.Web.Optimization嗎?)內建的優先框架排序作業,在根本上就是一個完全沒有必要的設計。
這個問題算是有解,可以使用注入的方式,把自己實作的介面掛上去即可。
namespace Slashview.Bundle { /* ----- * 本類別用來實作保留Javascript、Css之重要註解(Important Comments) * 如果沒有此需求,應該回歸使用System.Web.Optimization.XXXBundle。 * ----- */ /* ***** Javascript 保留重要註解 實作區 ***** */ /// <summary> /// (繼承)Javascript Bundle /// </summary> public class CommentScriptBundle : System.Web.Optimization.Bundle { public CommentScriptBundle(string virtualPath) : base(virtualPath) { this.Builder = new CommentScriptBundler(); } public CommentScriptBundle(string virtualPath, string cdnPath) : base(virtualPath, cdnPath) { this.Builder = new CommentScriptBundler(); } } /// <summary> /// (實作介面)Javascript Bundler /// </summary> public class CommentScriptBundler : System.Web.Optimization.IBundleBuilder { public virtual string BuildBundleContent(System.Web.Optimization.Bundle oBundle, System.Web.Optimization.BundleContext oContext, System.Collections.Generic.IEnumerable<System.Web.Optimization.BundleFile> oFiles) { var oContent = new System.Text.StringBuilder(); foreach (var oItem in oFiles) { var oFile = new System.IO.FileInfo(System.Web.HttpContext.Current.Server.MapPath(oItem.VirtualFile.VirtualPath)); var oMini = new Microsoft.Ajax.Utilities.Minifier(); string cTemp = ""; using (var oSR = oFile.OpenText()) { cTemp = oSR.ReadToEnd(); } string cSuccess = oMini.MinifyJavaScript( cTemp, new Microsoft.Ajax.Utilities.CodeSettings() { RemoveUnneededCode = true, StripDebugStatements = true, PreserveImportantComments = true, TermSemicolons = true } ); if (oMini.ErrorList.Count > 0) { oContent.Insert(0, PrependErrors(cTemp, oMini.ErrorList)); } else { oContent.Append(cSuccess); } } return oContent.ToString(); } //準備錯誤資訊 private string PrependErrors(string cFile, System.Collections.Generic.ICollection<Microsoft.Ajax.Utilities.ContextError> oErrors) { var oContent = new System.Text.StringBuilder(); oContent.Append("\r\n/* Minification Error \r\n"); oContent.Append(string.Join(" \r\n", oErrors)); oContent.Append("\r\n Minification Error */\r\n"); oContent.Append(cFile); return oContent.ToString(); } } /* ***** CSS 保留重要註解 實作區 ***** */ /// <summary> /// (繼承)Css Bundle /// </summary> public class CommentStyleBundle : System.Web.Optimization.Bundle { public CommentStyleBundle(string virtualPath) : base(virtualPath) { this.Builder = new CommentStyleBundler(); } public CommentStyleBundle(string virtualPath, string cdnPath) : base(virtualPath, cdnPath) { this.Builder = new CommentStyleBundler(); } } /// <summary> /// (實作介面)Css Bundler /// </summary> public class CommentStyleBundler : System.Web.Optimization.IBundleBuilder { public virtual string BuildBundleContent(System.Web.Optimization.Bundle oBundle, System.Web.Optimization.BundleContext oContext, System.Collections.Generic.IEnumerable<System.Web.Optimization.BundleFile> oFiles) { var oContent = new System.Text.StringBuilder(); foreach (var oItem in oFiles) { var oFile = new System.IO.FileInfo(System.Web.HttpContext.Current.Server.MapPath(oItem.VirtualFile.VirtualPath)); var oMini = new Microsoft.Ajax.Utilities.Minifier(); string cTemp = ""; using (var oSR = oFile.OpenText()) { cTemp = oSR.ReadToEnd(); } string cSuccess = oMini.MinifyStyleSheet( cTemp, new Microsoft.Ajax.Utilities.CssSettings() { CommentMode = Microsoft.Ajax.Utilities.CssComment.Important, IgnoreAllErrors = true } ); if (oMini.ErrorList.Count > 0) { oContent.Insert(0, PrependErrors(cTemp, oMini.ErrorList)); } else { oContent.Append(cSuccess); } } return oContent.ToString(); } //準備錯誤資訊 private string PrependErrors(string cFile, System.Collections.Generic.ICollection<Microsoft.Ajax.Utilities.ContextError> oErrors) { var oContent = new System.Text.StringBuilder(); oContent.Append("\r\n/* Minification Error \r\n"); oContent.Append(string.Join(" \r\n", oErrors)); oContent.Append("\r\n Minification Error */\r\n"); oContent.Append(cFile); return oContent.ToString(); } } /* ***** 其他 實作區 ***** */ /// <summary> /// 實作IBundleOrderer介面,藉以重新依照我們自己的意願進行檔案排序 /// </summary> public class CommentBundleOrder : System.Web.Optimization.IBundleOrderer { public virtual System.Collections.Generic.IEnumerable<System.Web.Optimization.BundleFile> OrderFiles(System.Web.Optimization.BundleContext oContext, System.Collections.Generic.IEnumerable<System.Web.Optimization.BundleFile> oFiles) { return oFiles; } } }
使用方式範例:
public static void Register(System.Web.Optimization.BundleCollection oBundles) { Slashview.Bundle.CommentScriptBundle oJsBundle = new Slashview.Bundle.CommentScriptBundle("cKey"); oJsBundle.Orderer = new Slashview.Bundle.CommentBundleOrder(); oJsBundle.Include( "~/include/jquery.js", "~/include/bootstrap.js" ); oBundles.Add(oJsBundle); }System.Web.Optimization ScriptBundle StyleBundle ImportantComments CssVariables IncludeSort