Experts are quick to come around and help solve the puzzlement ~About the choice of ASP.NET MVC Bundling and RequireJS, I am confused recently. I hope there is a way to combine the advantages of both. As a. NET programmer, don't you have any confusion about this?
Because I feel that each has its own advantages and disadvantages, the disadvantage of RequireJS is that when developing, you can not introduce compressed js or css, otherwise you can not debug and modify, and Bundling's debug mode is not compressed by default, as soon as you release into production, it will automatically compress, debugging is very convenient. The advantage of RequireJS is that it can load asynchronously on demand, and modular js code. Bundling is a simple and rude combination of all into one file to load. You can't see that modular references can't load on demand. So how do you choose to be a. NET programmer in the development process? Can we combine the advantages of the two?
If you tell me you don't know that RequireJS is an amazing winter? Please move to: http://requirejs.org/docs/api.html
Mode 1 Bunding+RequireJS Mixed Use
Let's first look at a foreigner's approach, which he basically did:
Bundling section
App_Start/BundleConfig.cs:
bundles.Add(new ScriptBundle("~/bundles/test").Include( "~/Scripts/jquery-{version}.js", "~/Scripts/q.js", "~/Scripts/globalize.js"));
RequireJS configuration section
In ASP.NET MVC projects, we usually add js references to the master page of _Layout
<script src="~/Scripts/require.js"></script> @if (!HttpContext.Current.IsDebuggingEnabled) { <script> requirejs.config({ bundles: { '@Scripts.Url("~/bundles/test").ToString()': [ 'jquery', 'globalize', 'q'] } }); </script> }
Comments: Very elegant way to achieve, say good modularity? And it does not provide a complete application solution.
The original address of the foreigner: ASP.NET MVC Bundling and Minification with RequireJS
Mode 2 RequireJS.NET
But then I found a plug-in. RequireJS.NET
What is RequireJS.NET?
RequireJS.NET allows every C# programmer to build JavaScript code without having advanced js programming skills to understand and use.
Advantages of using RequireJS in ASP.NET MVC:
- Make JavaScript code more reusable
- Reliable object and dependency management
- Suitable for large and complex applications
- Asynchronous loading of JavaScript files
Comments: Installing this installation and that, and more rigid, I can write code to achieve its functions, and more flexible, how to change it.
For the use of RequireJS.NET, please refer to: Getting started with RequireJS for ASP.NET MVC
My Way to Realize
Next, I will launch my implementation. My approach is to abandon the Bundling feature of ASP.NET MVC, because it is too foolish and rude, but you can integrate RequireJS and R.js in ASP.NET MVC project very friendly. Although RequireJS seems very convenient to use in single-page application scenarios, it is also applicable in application scenarios as long as you are willing to accept it in this way.
Use technology: using RequireJS and R.js
The catalogue structure is as follows:
Since there is template page _Layout.cshtml in ASP.NET MVC project, I can put some common calls directly into the template page. Here I encapsulate them by Html extension method.
Call of css:
<link rel="stylesheet" href="@Html.StylesPath("main.css")" />
Call of 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); } } }
Let's look at our js main file 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"] } });
In the development environment, our css files can't be compressed and merged, or can't be debugged, and the production environment must be compressed and merged, so I want to develop when not merged, as soon as released to production will automatically merge.
So there are two ways, one is to write a batch script separately, run it every time it is released to production, the other is to configure it directly in the project generation event, if debug mode, it will not be compressed and merged, if release mode, it will be compressed and merged.
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"
Automatic construction
Batch automatic merge compression script 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
Because my JS file corresponds to the view view view interface in the controller one by one, then I need a dynamic JS build script. Here I use a powerful T4 template to implement, and create a new text template build-js.tt. If your VS does not have the intelligent prompt of T4, you need to install a VS plug-in, open VS-Tool-Extension and Update:
The T4 template code is as follows:
<#@ 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("\\", "/"); } #>
The build scripts produced through T4 templates are as follows:
({ 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; }, })
Comments: Flexibility is good, how to organize it, and it can support daily build and continuous integration.
Sometimes, I always feel like a genius in a moment, O() Oha ~, please let me narcissism first, because programmers always have some ideas of their own, if you think my article is helpful or inspiring to you, please recommend it! If you have your own ideas, please come up with them. Let's discuss them together.
Postscript: I would like to add version number to the suffix of url path of each js and css to achieve caching, such as? v=1.0, but then I think that the browser itself has client cache, so I did not add it first, if necessary, you can fill it up at any time.