jNs 在 ASP.NET MVC 项目中的应用

最近作项目用到 ASP.NET Web Optimizatoin Framework,发现 Sea.js 的依赖加载在 Release 版本下不能很好的工做了——由于 Web.Optimizatoin 合并了全部脚本。同时因为写惯了 Java 
程序和 C# 程序,对于没有命名空间概念的 Sea.js 和 RequireJS 也感受不爽。考虑了下,以为模块管理其实并不复杂,因此将以前在《ASP.NET MVC4 捆绑(Bundle)技术下的 JavaScript》 中提到的 js-modular 的基础上进行了改进,最终产生了 jNsjquery

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,一个是 pagebash

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

有了合理的结构,还须要解决下面这个问题。

第二,保证 jNs.js 在模块定义以前加载

虽然通常认为在 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);
}
相关文章
相关标签/搜索