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 ...
随机推荐
- java环境配置笔记
1.使用Eclipse,要安装jdk,jdk现在可用1.7版本 2.打开Eclipse,配置maven,打开window-preferencess,在maven-user settings处,设置ma ...
- 个性二维码开源专题<前背景>
//设置图片资源 private Image imgAgo; public override void SetParam() { base.SetParam(); // 读取前背景 string _i ...
- sql bcp 笔记
介绍: http://blog.csdn.net/soudog/article/details/4343415 导出格式 BCP NTS.dbo.T_User format nul -f c:/Use ...
- Spec模板
Spec模板 一.概述 1.项目背景 图书馆在正常运营中面对大量书籍.读者信息以及两者间相互联系产生的借书信息.还书信息.现有的人工记录方法既效率低又错误过多,大大影响了图书馆 ...
- 作业六—图书管理系统(SPEC)系统性能评估测试
一.图书管理系统的典型用户和场景: 该系统是为各类学校图书馆和社会各大图书馆和书店管理者使用的图书管理系统.但是我们还是已北京工业大学耿丹学院图书馆为典型用户进行主要设计的! 二.SPEC测试的目标: ...
- Unity3D——物体鼠标跟随转向
int floorMask; float camRayLenth = 100f;//摄像机射线距离 void Truning() { Ray ray = Camera.main.ScreenPoint ...
- Server Develop (九) Simple Web Server
Simple Web Server web服务器hello world!-----简单的socket通信实现. HTTP HTTP是Web浏览器与Web服务器之间通信的标准协议,HTTP指明了客户端如 ...
- [MFC] CList
1.CList说明 类CList支持可按顺序或按值访问的非唯一对象的有序列表.CList 列表与双链接列表行为相似. template < class TYPE, class ARG_TYPE ...
- 专题:点滴Javascript
JS#38: Javascript中递归造成的堆栈溢出及解决方案 JS#37: 使用console.time测试Javascript性能 JS#36: Javascript中判断两个日期相等 JS#3 ...
- [Python爬虫] 在Windows下安装PhantomJS和CasperJS及入门介绍(上)
最近在使用Python爬取网页内容时,总是遇到JS临时加载.动态获取网页信息的困难.例如爬取CSDN下载资源评论.搜狐图片中的“原图”等,此时尝试学习Phantomjs和CasperJS来解决这个问题 ...