ASP.NET MVC Bundling and RequireJS

    高手速來圍觀幫忙解惑~關於ASP.NET MVC Bundling and RequireJS的取捨問題,最近比較困惑,我但願有一種方式能夠結合二者的優勢。做爲.NET程序員,難道你沒有過這方面的困惑嗎?css

    由於我感受各自都有優缺點,RequireJS的缺點在於,在開發的時候,你不能引入壓縮後的js或者css,不然沒法調試和修改,而Bundling的話debug模式默認狀況下是不壓縮,你一發布到生產成release就自動壓縮,調試起來很是方便。RequireJS的優勢在於能夠異步按需加載,還有就是模塊化js代碼,而Bundling 則是簡單粗暴的所有合併成一個文件進行加載,你看不出模塊化引用也實現不了按需加載, 那麼在開發過程當中做爲.NET程序員是如何取捨的呢?能不能結合兩者的優勢來使用呢?html

    目標:在ASP.NET MVC項目中實現js和css的模塊化,並支持壓縮合並。node

    若是你跟我說你還不知道RequireJS是個神馬鼕鼕?請移步至:http://requirejs.org/docs/api.htmljquery

    項目目錄結構沿用上一篇ASP.NET MVC 重寫RazorViewEngine實現多主題切換git

方式一 Bunding+RequireJS混用

先來看看一個老外的作法,他大致上是這樣作的:程序員

Bundling部分

App_Start/BundleConfig.cs:編程

bundles.Add(new ScriptBundle("~/bundles/test").Include(
                   "~/Scripts/jquery-{version}.js",
                   "~/Scripts/q.js",
                   "~/Scripts/globalize.js"));

RequireJS配置部分

在ASP.NET MVC項目中,咱們通常是在_Layout母版頁中添加js引用bootstrap

    <script src="~/Scripts/require.js"></script>
    @if (!HttpContext.Current.IsDebuggingEnabled)
    {
        <script>
            requirejs.config({
                bundles: {
                    '@Scripts.Url("~/bundles/test").ToString()': [
                        'jquery',
                        'globalize',
                        'q']
                }
            });
        </script>
    }

我的點評:很不優雅的實現方式,說好的模塊化呢?並且並無提供完整的應用程序解決方案。api

老外原文地址:ASP.NET MVC Bundling and Minification with RequireJS瀏覽器

方式二 RequireJS.NET

可是隨後我就發現了一個插件RequireJS.NET

什麼是RequireJS.NET?

RequireJS.NET讓每個C#程序員能夠來構建JavaScript代碼,不須要具有高級的js編程技能就能夠來理解和使用。

在ASP.NET MVC中使用RequireJS的優點:

  • 讓JavaScript代碼更加可複用
  • 可靠的對象和依賴關係管理
  • 適用於大型複雜的應用
  • 異步加載JavaScript文件

我的點評:安裝這個安裝那個,並且比較死板,我徹底能夠本身寫代碼實現它的功能,並且更加靈活,想怎麼改怎麼改。

RequireJS.NET的使用請參考:Getting started with RequireJS for ASP.NET MVC

個人實現方式

    接下來,我將隆重推出個人實現方式個人作法是:拋棄ASP.NET MVC自帶的Bundling功能,由於它太傻瓜、太粗暴了,可是能夠將RequireJS and R.js 很友好的集成在ASP.NET MVC項目中來。雖然RequireJS看上去在單頁應用的場景下用起來很是方便,可是在應用程序場景下也是一樣適用的,只要你願意接受它的這種方式。

使用技術: using RequireJS and R.js

目錄結構以下:

因爲在ASP.NET MVC項目中,有模板頁_Layout.cshtml,那麼我能夠把一些公用調用的東西直接放到模板頁中,這裏我經過Html的擴展方法進行了封裝

css的調用:

     <link rel="stylesheet" href="@Html.StylesPath("main.css")" />

js的調用:

    <script src="@Url.Content("~/themes/default/content/js/require.js")"></script>
    <script>   @Html.ViewSpecificRequireJS()</script>
        @RenderSection("scripts", required: false)

RequireJsHelpers:

using System.IO;
using System.Text;
using System.Web;
using System.Web.Mvc;

namespace Secom.Emx.WebApp
{
    public static class RequireJsHelpers
    {
        private static MvcHtmlString RequireJs(this HtmlHelper helper, string config, string module)
        {
            var require = new StringBuilder();
            string jsLocation = "/themes/default/content/release-js/";
#if DEBUG
            jsLocation = "/themes/default/content/js/";
#endif

            if (File.Exists(helper.ViewContext.HttpContext.Server.MapPath(Path.Combine(jsLocation, module + ".js"))))
            {
                require.AppendLine("require( [ \"" + jsLocation + config + "\" ], function() {");
                require.AppendLine("    require( [ \"" + module + "\",\"domReady!\"] ); ");
                require.AppendLine("});");
            }

            return new MvcHtmlString(require.ToString());
        }

        public static MvcHtmlString ViewSpecificRequireJS(this HtmlHelper helper)
        {
            var areas = helper.ViewContext.RouteData.DataTokens["area"];
            var action = helper.ViewContext.RouteData.Values["action"];
            var controller = helper.ViewContext.RouteData.Values["controller"];

            string url = areas == null? string.Format("views/{0}/{1}", controller, action): string.Format("views/areas/{2}/{0}/{1}", controller, action, areas);

            return helper.RequireJs("config.js", url);
        }
        public static string StylesPath(this HtmlHelper helper, string pathWithoutStyles)
        {
#if (DEBUG)
            var stylesPath = "~/themes/default/content/css/";
#else
            var stylesPath =  "~/themes/default/content/release-css/";
#endif
            return VirtualPathUtility.ToAbsolute(stylesPath + pathWithoutStyles);
        }
    }
}

再來看下咱們的js主文件config.js

requirejs.config({
    baseUrl: '/themes/default/content/js',
    paths: {
        "jquery": "jquery.min",
        "jqueryValidate": "lib/jquery.validate.min",
        "jqueryValidateUnobtrusive": "lib/jquery.validate.unobtrusive.min",
        "bootstrap": "lib/bootstrap.min",
        "moment": "lib/moment.min",
        "domReady": "lib/domReady",
    },
    shim: {
        'bootstrap': {
            deps: ['jquery'],
            exports: "jQuery.fn.popover"
        },
        "jqueryValidate": ["jquery"],
        "jqueryValidateUnobtrusive": ["jquery", "jqueryValidate"]
    }
});

 在開發環境,咱們的css文件確定不能壓縮合並,否則沒法調試了,而生產環境確定是須要壓縮和合並的,那麼我想要開發的時候不合並,一發布到生產就自動合併

那麼有兩種方式,一種呢是單獨寫一個批處理腳本,每次發佈到生產的時候就運行一下,一種呢是直接在項目的生成事件中進行配置,若是是debug模式就不壓縮合並,若是是release模式則壓縮合並

if $(ConfigurationName) == Release node "$(ProjectDir)themes\default\content\build\r.js" -o "$(ProjectDir)themes\default\content\build\build-js.js"
if $(ConfigurationName) == Release node "$(ProjectDir)themes\default\content\build\r.js" -o "$(ProjectDir)themes\default\content\build\build-css.js"

自動構建

批處理自動合併壓縮腳本build.bat:

@echo off
echo start build js
node.exe r.js -o build-js.js
echo end build js
echo start build css
node.exe r.js -o build-css.js
echo end build css
echo. & pause

由於個人js文件是和控制器中的view視圖界面一一對應的,那麼我須要一個動態的js構建腳本,這裏我使用強大的T4模板來實現,新建一個文本模板build-js.tt,若是你的VS沒有T4的智能提示,你須要安裝一個VS插件,打開VS——工具——擴展和更新:

T4模板代碼以下:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Configuration" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".js" #>
({
    appDir: '<#= relativeBaseUrl #>',
    baseUrl: './',
    mainConfigFile: '<#= relativeBaseUrl #>/config.js',
    dir: '../release-js',
    modules: [
    {
        name: "config",
        include: [
            // These JS files will be on EVERY page in the main.js file
            // So they should be the files we will almost always need everywhere
            "domReady",
            "jquery",
            "jqueryValidate",
            "jqueryValidateUnobtrusive",
            "bootstrap",
            "moment"
            ]
    },
    <# foreach(string path in System.IO.Directory.GetFiles(this.Host.ResolvePath(relativeBaseUrl+"/views"), "*.js", System.IO.SearchOption.AllDirectories)) { #>
{
       name: '<#= GetRequireJSName(path) #>'
    },
    <# } #>
],
    onBuildRead: function (moduleName, path, contents) {
        if (moduleName = "config") {
            return contents.replace("/themes/default/content/js","/themes/default/content/release-js")
        }
        return contents;
    },
})

<#+ 
    public const string relativeBaseUrl = "../js";

    public string GetRequireJSName(string path){
    var relativePath = Path.GetFullPath(path).Replace(Path.GetFullPath(this.Host.ResolvePath("..\\js\\")), "");
    return Path.Combine(Path.GetDirectoryName(relativePath), Path.GetFileNameWithoutExtension(relativePath)).Replace("\\", "/");
} #>

經過T4模板生產的構建腳本以下:

({
    appDir: '../js',
    baseUrl: './',
    mainConfigFile: '../js/config.js',
    dir: '../release-js',
    modules: [
    {
        name: "config",
        include: [
            // These JS files will be on EVERY page in the main.js file
            // So they should be the files we will almost always need everywhere
            "domReady",
            "jquery",
            "jqueryValidate",
            "jqueryValidateUnobtrusive",
            "bootstrap",
            "moment"
            ]
    },
    {
       name: 'views/areas/admin/default/index'
    },
    {
       name: 'views/home/index'
    },
    {
       name: 'views/home/login'
    },
    ],
    onBuildRead: function (moduleName, path, contents) {
        if (moduleName = "config") {
            return contents.replace("/themes/default/content/js","/themes/default/content/release-js")
        }
        return contents;
    },
})

 我的點評:靈活性很好,想怎麼整怎麼整,並且能夠很好的支持每日構建和持續集成。

 有時候,老是會在某一剎那間就有新奇的想法,這就是程序員,若是你以爲個人文章對你有幫助或者啓發,請推薦一下吧!若是有本身的想法也請提出來,你們一塊兒探討!

 後記:我原本還想給每js和css的url路徑後綴添加版本號來實現客戶端緩存的自動更新,如?v=1.0,可是後面想了下瀏覽器自己就自帶客戶端緩存,因此就先沒有添加,若是真有須要,能夠隨時補上。

 調整後的項目源碼:https://git.coding.net/zouyujie/Smp.git

相關文章
相關標籤/搜索