最近作項目用到 ASP.NET Web Optimizatoin Framework,發現 Sea.js 的依賴加載在 Release 版本下不能很好的工做了——由於 Web.Optimizatoin 合併了全部腳本。同時因爲寫慣了 Java
程序和 C# 程序,對於沒有命名空間概念的 Sea.js
和 RequireJS
也感受不爽。考慮了下,以爲模塊管理其實並不複雜,因此將以前在《ASP.NET MVC4 捆綁(Bundle)技術下的 JavaScript》 中提到的 js-modular
的基礎上進行了改進,最終產生了 jNs。jquery
jNs 是一個具備命名空間概念的 JavaScript 模塊管理工具。與 Sea.js 和 ReqireJS 等模塊管理工具不一樣,jNs 只管理模塊的定義和使用,而不負責加載,很是適合發佈合併 JavaScript 代碼的 Web 項目,好比使用了 ASP.NET Web Optimization Framework 提供的 Script Bundle 功能的 ASP.NET 項目,以及使用 UglifyJS 壓縮合並腳本的項目等。git
jNs 託管在 git.oschina.net 上,其 README 中已經有詳情的示例,因此這裏就不廢話了。這裏主要說說在 ASP.NET MVC 項目裏怎麼使用。ajax
下面展現了一個來自某個實際項目的腳本目錄(有所精簡),也許不是最好的結構,但我我的以爲很清晰(用方括號 []
括起來的是目錄)。c#
[Scripts] │ - jNs-1.0.0.js │ - jNs-1.0.0.min.js │ - jquery-2.1.3.js │ - jquery-2.1.3.min.js ├─[core] │ └─[co] │ │ - app.js │ │ - compatible.js │ │ - util.js │ ├─[app] │ │ │ - ajax.js │ │ │ - dialog.js │ │ │ - page.js │ │ └ - ui.js │ └─[util] │ │ - case.js │ │ - debug.js │ └ - format.js └─[page] ├─[home] │ └ - index.js └─[user] └ - index.js
其中有兩個比較關鍵的主目錄,一個是 core
,一個是 page
。bash
core
是整個項目中須要使用到的模塊,在 bunles
中配置成一個名爲 ~/bundle/core
的 ScriptBundle
。Release 版本下會被打包成一個合併的腳本文件。大部分的頁面只須要引用 ~/bundles/core
就能夠了,由於大部分的公用邏輯都在這裏了。app
可是仍然會有一部分頁面比較特殊,須要有本身的腳本。那麼這些腳本就按必定的路徑保存在 page
目錄下。ide
core
中的目錄結構與模塊的結構對應,這樣查找腳本文件的時候就比較方便,好比示例中的結構對應的模塊全稱(含命名空間)就是:工具
co.app co.app.ajax co.app.dialog co.app.page co.app.ui co.compatible co.util co.util.case co.util.debug co.util.format
這個結構看起來就像 Java 同樣。不過與 Java 不一樣,目錄沒必要與命名空間(或包)對應,文件名也沒必要與模塊名相同——這彷佛更像 C#。requirejs
還有一點須要注意的是,在同一個命名空間下,子級命名空間和模塊名是能夠相同的,這也算是 jNs 的特色之一吧。ui
有了合理的結構,還須要解決下面這個問題。
雖然通常認爲在 ScriptBunlde
中 Include
的腳本會順序加載,或者說在 Release 版本下按順序合併,因此認爲能夠這樣寫配置:
// BundleConfig.cs // 注意:這是錯誤的示例 public static void RegisterBundles(BundleCollection bundles) { bunles.Add(new ScriptBundle("~/bundles/core") .Include("~/scripts/jNs-{version}.js"). .IncludeDirectory("~/scripts/core/", "*.js", true) ); }
惋惜事實不是。我閱讀了它的源碼,發現它使用了一個 Dictionary
來進行中間過程的處理,因此最終輸出的順序徹底是不肯定的。因此 jNs 有可能在某個模塊文件以後加載,那麼模塊文件中的 jNs(...)
就會出錯——由於還沒定義。
不過很幸運,Web.Optimization 提供了 IBundleOrderer
接口來處理順序的問題。我比較懶,因此不想直接去實現IBundleOrderer
接口,而是從 DefaultBundleOrderer
繼承了個子類來處理順序——無論 DefaultBundleOrderer
是怎麼處理的,我只須要在它處理以後按輸入的順序從新排列一下就行了
public class AsIsBundleOrderer : DefaultBundleOrderer { public override IEnumerable<BundleFile> OrderFiles( BundleContext context, IEnumerable<BundleFile> files ) { var originalList = files.ToList(); IEnumerable<BundleFile> orderFiles = base.OrderFiles(context, originalList); return orderFiles.OrderBy(f => originalList.IndexOf(f)); } }
在有 AsIsBundleOrderer
以後,再來看看正確的 Bundle 配置
public static void RegisterBundles(BundleCollection bundles) { var bundle = new ScriptBundle("~/bundles/core") .Include("~/Scripts/jNs-{version}.js") .IncludeDirectory("~/Scripts/core/", "*.js", true); bundle.Orderer = new AsIsBundleOrder(); bundles.Add(bundle); }