ASP.NET MVC应用require.js实践
这里有更好的阅读体验和及时的更新:http://pchou.info/javascript/asp.net/2013/11/10/527f6ec41d6ad.html
Require.js是一个支持javascript模块化编程的类库,不了解的读者请移步至:Javascript模块化编程(三):require.js的用法。
require在单页面应用中能够如鱼得水,然而对于传统的多页面应用,使用require多少会有些困惑和不方便。
多页面应用的一个典型的例子是https://github.com/requirejs/example-multipage,读者可以clone下来参考。本文参考这个例子在ASP.NET MVC的结构中应用require,并且给出了压缩脚本,实现半自动化压缩。
将js代码分离
一般而言ASP.NET MVC的一个路由对应一个视图,视图的文件结构可能如下:
Views
|--Shared
|--_layout.cshtml
|--Home
|--Index.cshtml
|--Blog
|--Create.cshtml
|--Edit.cshtml
|--Detail.cshtml
|--Index.cshtml
这里假设_layout.cshtml是所有页面共享的。一般情况下,我们会在_layout中引用公共的js类库,比如jQuery,bootstrap等,这样的话其他的页面就不需要对这些类库再引用一遍,提高了编码的效率。然而,不同的页面终究会依赖不同的js,尤其是实现页面本身功能的自定义的js,这样我们不得不在其他页面中再引用特殊的js,甚至将js直接写在页面中,例如下面的代码经常会出现在View中:
<script type="text/javascript">
$(function(){...});
</script>
这样会导致页面比较混乱,而且页面<script>标签中代码不能被浏览器缓存,增加了页面代码的长度。更为重要的缺陷是,诸如jQuery之类的类库会在加载到页面后执行匿名函数,这需要一些时间,而如果有些页面根本不需要jQuery的话,只要页面把_layout作为布局页面,那么jQuery的初始化代码将不可避免的执行,这是一种浪费。事实上,javascript的模块化加载的思想就是为了解决这些问题的。
接下来我们来用require规划我们的js,构建诸如下面结构的js目录
js
|--app
|--home.index.js
|--blog.create.js
|--blog.edit.js
|--blog.detail.js
|--blog.index.js
|--jquery.js
|--bootstrap.js
|--underscore.js
|--jquery.ui.js
|--jquery.customplugin.js
|--config.js
|--require.js
把公共的类库级别的js模块直接放在js目录下,而把页面级别的js放在一个app的子目录下。注意,在app中,每个页面一个js文件,这意味着我们需要把页面各自的js提取出来,虽然这样增加了结构复杂度,但是避免了在页面中随手写<script>标签的陋习。另外,在js目录下的公共库,除了第三方的库,还包括自己开发的库,还有一个叫config.js的文件,这个文件很关键,稍后会说到。
然后,我们可以删除_layout中所有的js引用,并使用@RenderSection的命令要求子页面提供js引用:
_layout.cshtml
<head>
...
@RenderSection("require_js_module", false)
...
</head>
这样对js的需求就下放到每个view页面中了,根据require的用法,我们需要在各个子View中引用require.js,并指定主模块,而这些主模块就是上面app目录下的一个个js
@section require_js_module{
<script src="@Url.Content("~/js/require.js")" data-main="@Url.Content("~/js/app/home.index.js")" ></script>
}
所有的js代码都将写到app下的js中,这样规范了js,使得页面更干净,更为重要的是这些js还可以经过压缩,以及被浏览器缓存等,进一步提高执行效率
公共的config
我们知道主模块除了使用require方法外,经常需要通过require.config来配置其他模块的路径,甚至需要shim,例如下面的代码经常会出现在主模块的开头:
require.config({
paths: {
"jquery": "lib/jquery.min",
"underscore": "lib/underscore.min",
"backbone": "lib/backbone.min"
},
shim: {
'underscore':{
exports: '_'
},
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
}
});
对于单页面应用来说,主模块往往只有一个,所以上面的代码写一遍也就OK了。但是,在多页面的情况下,主模块有多个,每个主模块都要包含这样的代码,岂不是很不科学?于是,希望有一个统一配置的地方,但是应该如何来写呢?我们想到,将这些配置作为一个模块config.js,让其他的主模块对这个模块产生依赖就可以了,例如下面的config.js:
config.js
requirejs.config({
paths: {
"jquery": "/js/jquery.min",
"bootstrap": "/js/bootstrap"
},
shim: {
'bootstrap': {
deps: ['jquery'],
exports: "jQuery.fn.popover"
}
}
});
config.js的写法没有什么特别的,接下来只要在home.index.js中引用
home.index.js
require(['../config','jquery', 'bootstrap'], function () {
//main module code here
});
不过这样写还是不对的,因为,被主模块依赖的模块(这里的config,jquery,bootstrap),在加载的时候,加载顺序是不确定的,但是又需要config模块在其他模块之前加载,怎么办呢?一个折衷的方案是修改home.index.js,成为如下代码:
home.index.js
require(['../config'], function () {
require(['home.index2']);
})
, define("home.index2", ['jquery', 'bootstrap'], function () {
//main module code here
})
使用一个命名的模块home.index2作为过渡,在主模块中手动require,这样可以保证config在主模块执行之前加载,也就使得home.index2在加载的时候已经加载了config了。
压缩
require提供一个压缩工具,用于压缩和合并js,详情请移步至http://requirejs.org/docs/optimization.html。简单的说,require提供一个叫r.js的文件,通过本地的node程序(Node.js),执行这个r.js并传入一些参数,即可自动分析模块互相之间的依赖,以达到合并和压缩的目的。同样的,这对于单页面应用来说是容易的,因为主模块只有一个,但是对于多页面又如何做呢?好在这个压缩工具支持用一个配置文件来指导压缩,这样的话,我们可以编写下面的配置脚本:
build.js
var build = {
appDir: '../js',
baseUrl: '.',
dir: '../js-built',
modules: [
//First set up the common build layer.
{
//module names are relative to baseUrl
name: 'config',
//List common dependencies here. Only need to list
//top level dependencies, "include" will find
//nested dependencies.
include: ["bootstrap", "config","jquery"]
},
//Now set up a build layer for each page, but exclude
//the common one. "exclude" will exclude nested
//the nested, built dependencies from "common". Any
//"exclude" that includes built modules should be
//listed before the build layer that wants to exclude it.
//"include" the appropriate "app/main*" module since by default
//it will not get added to the build since it is loaded by a nested
//require in the page*.js files.
{
name:"app/home.index",
exclude:["config"]
},
{
name:"app/blog.create",
exclude:["config"]
},
...
]
}
通过这个命令来执行压缩,压缩的结果将被保存到js-build目录:
node.exe r.js -o build.js
build.js脚本实际上是一个js对象,我们将config加入公共模块,而在各个主模块中将其排除。这样,所有的公共库包括config将压缩成一个js,而主模块又不会包含多余的config。这样可想而知,每个页面在加载时最多只会下载两个js,而且公共模块的代码会“按需执行”。
执行上面的脚本压缩,需要安装有node。可以在从这里下载。
自动脚本
但是,随着主模块的增加,需要随时跟踪和修改这个build文件,这也是很麻烦的。于是,笔者基于node.js开发了一个叫build-build.js的脚本,用来根据目录结构自动生成build.js:
build-build.js
fs = require('fs');
var target_build = process.argv[2];
//console.log(__filename);
var pwd = __dirname;
var js_path = pwd.substring(0,pwd.lastIndexOf('\\')) + '\\js';
console.log('js path : ' + js_path);
var app_path = js_path + '\\app';
console.log('js app path : ' +app_path);
var app_modules = [];
var global_modules = [];
//build json object
var build = {
appDir: '../js',
baseUrl: '.',
dir: '../js-built',
modules: [
//First set up the common build layer.
{
//module names are relative to baseUrl
name: 'config',
//List common dependencies here. Only need to list
//top level dependencies, "include" will find
//nested dependencies.
include: []
}
]
}
fs.readdir(app_path,function (err,files) {
// body...
if (err) throw err;
for(var i in files){
//put module in app_modules
var dotindex = files[i].lastIndexOf('.');
if(dotindex >= 0){
var extension = files[i].substring(dotindex+1,files[i].length);
if(extension == 'js'){
app_modules.push({
name: 'app/' + files[i].substring(0,dotindex),
exclude: ['config']
});
}
}
}
for(var j in app_modules){
build.modules.push(app_modules[j]);
}
fs.readdir(js_path,function (err,files){
if (err) throw err;
for(var i in files){
//put module in app_modules
var dotindex = files[i].lastIndexOf('.');
if(dotindex >= 0){
var extension = files[i].substring(dotindex+1,files[i].length);
if(extension == 'js'){
global_modules.push(files[i].substring(0,dotindex));
}
}
}
build.modules[0].include = global_modules;
//console.log(build);
var t = pwd + '\\' + target_build;
console.log(t);
var fd = fs.openSync(t, 'w');
fs.closeSync(fd);
var json = JSON.stringify(build);
fs.writeFileSync(t, json);
});
});
这里的代码并不复杂,主要是遍历目录,生成对象,最后将对象序列化为build.js。读者可以自行阅读并修改。最后,编写一个bat,完成一键压缩功能:
build.bat
@echo off
set PWD=%~p0
set PWD=%PWD:\=/%
cd "D:\node"
node.exe %PWD%build-build.js build.js
node.exe %PWD%r.js -o %PWD%build.js
cd %~dp0
这样,我们就简单实现了一个方便的多页面require方案,最后项目目录可能是这样的:
Views
|--Shared
|--_layout.cshtml
|--Home
|--Index.cshtml
|--Blog
|--Create.cshtml
|--Edit.cshtml
|--Detail.cshtml
|--Index.cshtml build
|--build.js
|--r.js
|--build-build.js
|--build.bat js
|--app
|--home.index.js
|--blog.create.js
|--blog.edit.js
|--blog.detail.js
|--blog.index.js
|--jquery.js
|--bootstrap.js
|--underscore.js
|--jquery.ui.js
|--jquery.customplugin.js
|--config.js
|--require.js
可以从这里fork示例程序
ASP.NET MVC应用require.js实践的更多相关文章
- require.js实践
ASP.NET MVC应用require.js实践 这里有更好的阅读体验和及时的更新:http://pchou.info/javascript/asp.net/2013/11/10/527f6ec41 ...
- ASP.NET MVC防范CSRF最佳实践
XSS与CSRF 哈哈,有点标题党,但我保证这篇文章跟别的不太一样. 我认为,网站安全的基础有三块: 防范中间人攻击 防范XSS 防范CSRF 注意,我讲的是基础,如果更高级点的话可以考虑防范机器人刷 ...
- 使用ASP.NET MVC局部视图避免JS拼接HTML,编写易于维护的HTML页面
以前使用ASP.NET WebForm开发时,喜欢使用Repeater控件嵌套的方式开发前台页面,这样就不用JS拼接HTML或者后台拼接HTML了,写出的HTML页面美观.简捷.易于维护,由于不用JS ...
- ASP.NET MVC 中CSS JS压缩合并 功能的使用方法
通过压缩合并js文件和css文件,可以减少 服务器的响应 次数和 流量,可以大大减小服务器的压力,对网站优化有比较明显的帮助!压缩合并 css 文件和js文件是网站优化的一个 比较常用的方法. ASP ...
- 在ASP.NET MVC中使用Knockout实践01,绑定Json对象
本篇体验在ASP.NET MVC下使用Knockout,将使用EF Code First创建数据库.最后让Knockout绑定一个Json对象. 创建一个领域模型. namespace MvcAppl ...
- ASP.NET MVC API与JS进行POST请求时传递参数 -CHPowerljp原创
在API前添加 [HttpPost] 表示只允许POST方式请求 [HttpPost] public IHttpActionResult Get_BIGDATA([FromBody]Datas ...
- 【转】asp.net mvc webapi+angular.js案例
参考地址:http://www.mamicode.com/info-detail-892383.html 大家好,本文用一个简单的demo演示AngularJS在MVC中的使用,在学习这个demo之前 ...
- ASP.NET MVC 4 的JS/CSS打包压缩功能-------过滤文件
今天在使用MVC4打包压缩功能@Scripts.Render("~/bundles/jquery") 的时候产生了一些疑惑,问什么在App_Start文件夹下BundleConfi ...
- ASP.Net MVC(4) 之js css引用与压缩
资源引用 可以用即可以直接使用“~”来表示根目录. 引入js <script src="~/Areas/OrderManage/JS/Form.js"></scr ...
随机推荐
- jsonp与ajax
jsonp思维导图:1.定义2.为什么用3.json原理4.优缺点何为跨域?何为JSONP?JSONP技术能实现什么?是否有必要使用JSONP技术? JSON和JSONP??虽然只有一个字母的差别,但 ...
- Vmware Vsphere WebService SDK开发(第一讲)-基本知识学习
刚开始这方面开发的时候,不知道如何下手,能够查到的资料特别少,而且看到很多网友和我一样也在找这方面的资料.接下来的一段时间我就结合自己所参与的项目,完成关于Vmware Vsphere WebServ ...
- AutoMapper在ABP框架中的使用说明
为了说明AutoMapper如何使用,我专门开设了一个专题来讲,如果您还没有查看该专题,请点击这里.既然系统地学习了AutoMapper,那么接下来就是该用它实战的时候了.今天,我们就来揭开AutoM ...
- [翻译]Bob大叔:反思极限编程
译者注: Bob大叔14年后再次谈论极限编程.极限编程经历了14年的风风雨雨后,Bob大叔将会给它怎么样的定义那? 在我手中拿着的一本白皮薄书,在14年前彻底的改变了软件世界.这本书的标题是解析极限编 ...
- git log控制输出宽度
%<(N, trunc) 下一个单元的输出宽度限制为N列, 左对齐 %<|(N, trunc) 下一个单元输出至全局第N列, 左对齐 %>, %>|, %>>, % ...
- Maven学习总结(一副本)——Maven配置和搭建
环境准备: JDK 1.6 Maven 3.0.4 myeclipse 8.6.1 安装 Maven 之前要求先确定你的 JDK 已经安装配置完成.Maven是 Apache 下的一个项目,目前最新版 ...
- bzoj 1191: [HNOI2006]超级英雄Hero
1191: [HNOI2006]超级英雄Hero Time Limit: 10 Sec Memory Limit: 162 MB 二分图匹配... Description 现在电视台有一种节目叫做超 ...
- java集合类总结二
上篇已经总结了常用集合类的一些基本特征以及他们之间的区别,下面,再对集合类部分进行总结 一.集合类的常用方法 1.remove方法:移除元素操作,下面以ArrayList为例. import java ...
- docker使用GPU
1.物理机安装显卡驱动 2.安装nvidia-docker wget -P /tmp https://github.com/NVIDIA/nvidia-docker/releases/download ...
- Beego源码分析(转)
摘要 beego 是 @astaxie 开发的重量级Go语言Web框架.它有标准的MVC模式,完善的功能模块,和优异的调试和开发模式等特点.并且beego在国内企业用户较多,社区发达和Q群,文档齐全, ...