ASP.NET MVC Bundling and RequireJS

高手速来围观帮忙解惑~关于ASP.NET MVC Bundling and RequireJS的取舍问题,最近比较困惑,我希望有一种方式可以结合两者的优点。作为.NET程序员,难道你没有过这方面的困惑吗?

因为我感觉各自都有优缺点,RequireJS的缺点在于,在开发的时候,你不能引入压缩后的js或者css,否则无法调试和修改,而Bundling的话debug模式默认情况下是不压缩,你一发布到生产成release就自动压缩,调试起来非常方便。RequireJS的优点在于可以异步按需加载,还有就是模块化js代码,而Bundling 则是简单粗暴的全部合并成一个文件进行加载,你看不出模块化引用也实现不了按需加载, 那么在开发过程中作为.NET程序员是如何取舍的呢?能不能结合二者的优点来使用呢?

如果你跟我说你还不知道RequireJS是个神马冬冬?请移步至:http://requirejs.org/docs/api.html

方式一 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引用

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

点评:很不优雅的实现方式,说好的模块化呢?而且并没有提供完整的应用程序解决方案。

老外原文地址: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\release-js\build-js.js"
if $(ConfigurationName) == Release node "$(ProjectDir)themes\default\content\build\r.js" -o "$(ProjectDir)themes\default\content\release-css\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;
    },
})

点评:灵活性很好,想怎么整怎么整,而且可以很好的支持每日构建和持续集成。

有时候,总是会在某一刹那间感觉自己就像天才一般,O(∩_∩)O哈哈~,请让我先自恋一下,因为程序员总是有许多自己的一些想法,如果你觉得我的文章对你有帮助或者启发,请推荐一下吧!如果有自己的想法也请提出来,大家一起探讨!

后记:我本来还想给每js和css的url路径后缀添加版本号来实现缓存,如?v=1.0,但是后面想了下浏览器本身就自带客户端缓存,所以就先没有添加,如果真有需要,可以随时补上。

时间: 2024-10-10 15:44:15

ASP.NET MVC Bundling and RequireJS的相关文章

如何在 ASP.NET MVC 中集成 AngularJS(2)

在如何在 ASP.NET MVC 中集成 AngularJS(1)中,我们介绍了 ASP.NET MVC 捆绑和压缩.应用程序版本自动刷新和工程构建等内容. 下面介绍如何在 ASP.NET MVC 中集成 AngularJS 的第二部分. ASP.NET 捆绑和压缩 CSS 和 JavaScript 的捆绑与压缩功能是 ASP.NET MVC 最流行和有效的特性之一.捆绑和压缩降低了 HTTP 请求和有效载荷的大小,结果是可以更快和更好的执行 ASP.NET MVC 的网站.有许多可以减少 CS

ASP.NET MVC 4 (十一) Bundles和显示模式

Bundles用于打包CSS和javascript脚本文件,优化对它们的组织管理.显示模式则允许我们为不同的设备显示不同的视图. 默认脚本库 在VS创建一个MVC工程,VS会为我们在scripts目录下添加很多脚本库,下面来简单了解下这些脚本库的作用: 脚本文件 说明 jquery-1.7.1.js jquery的基本类库 jquery-ui-1.8.20.js jquery的UI类库,方便我们创建丰富的用户控件,基于jquery基本类库 jquery.mobile-1.1.0.js 用于移动设

ASP.NET MVC 5 - 验证编辑方法(Edit method)和编辑视图(Edit view)

原文:ASP.NET MVC 5 - 验证编辑方法(Edit method)和编辑视图(Edit view) 在本节中,您将验证电影控制器生成的编辑方法(Edit action methods)和视图.但是首先将修改点代码,使得发布日期属性(ReleaseDate)看上去更好.打开Models \ Movie.cs文件,并添加高亮行如下所示: using System; using System.ComponentModel.DataAnnotations; using System.Data.

ASP.NET MVC 4使用Bundle的打包压缩JS/CSS

打包(Bundling)及压缩(Minification)指的是将多个js文件或css文件打包成单一文件并压缩的做法,如此可减少浏览器需下载多个文件案才能完成网页显示的延迟感,同时通过移除JS/CSS文件案中空白.批注及修改JavaScript内部函数.变量名称的压缩手法,能有效缩小文件案体积,提高传输效率,提供使用者更流畅的浏览体验. 在ASP.NET MVC 4中可以使用BundleTable捆绑多个css文件和js文件,以提高网络加载速度和页面解析速度.更为重要的是通过捆绑可以解决IE浏览

ASP.NET MVC使用Bootstrap系列(1)——开始使用Bootstrap

阅读目录 Bootstrap结构介绍 在ASP.NET MVC 项目中添加Bootstrap文件 为网站创建Layout布局页 使用捆绑打包和压缩来提升网站性能 在Bootstrap项目中使用捆绑打包 测试打包和压缩 小结 作为一名Web开发者而言,如果不借助任何前端框架,从零开始使用HTML和CSS来构建友好的页面是非常困难的.特别是对于Windows Form的开发者而言,更是难上加难. 正是由于这样的原因,Bootstrap诞生了.Twitter Bootstrap为开发者提供了丰富的CS

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

Asp.net MVC Bundle 的使用与扩展

一.Asp.net 自带Bundle的使用: 1. 在Globale中注册与配置 BundleConfig.RegisterBundles(BundleTable.Bundles); public class BundleConfig { // For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725 public static void RegisterBundles(Bundle

[转]ASP.NET MVC 5 - 验证编辑方法(Edit method)和编辑视图(Edit view)

在本节中,您将验证电影控制器生成的编辑方法(Edit action methods)和视图.但是首先将修改点代码,使得发布日期属性(ReleaseDate)看上去更好.打开Models \ Movie.cs文件,并添加高亮行如下所示: using System; using System.ComponentModel.DataAnnotations; using System.Data.Entity; namespace MvcMovie.Models { public class Movie

Asp.net MVC 版本简史

http://www.dotnet-tricks.com/Tutorial/mvc/XWX7210713-A-brief-history-of-Asp.Net-MVC-framework.html Asp.net MVC is a new Framework built on the top of Microsoft .Net Framework to develop web application. This framework implements the MVC pattern which