require.js实践
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示例程序
require.js实践的更多相关文章
- ASP.NET MVC应用require.js实践
这里有更好的阅读体验和及时的更新:http://pchou.info/javascript/asp.net/2013/11/10/527f6ec41d6ad.html Require.js是一个支持j ...
- require.js 最佳实践【转】
https://www.cnblogs.com/digdeep/p/4607131.html require.js是一个js库,相关的基础知识,前面转载了两篇博文:Javascript模块化编程(re ...
- require.js 最佳实践
require.js是一个js库,相关的基础知识,前面转载了两篇博文:Javascript模块化编程(require.js), Javascript模块化工具require.js教程,RequireJ ...
- Require.js中的路径在IDEA中的最佳实践
本文主要讲述require.js在IDEA中路径智能感知的办法和探索中遇到的问题. 测试使用的目录结构:一种典型的thinkphp 6的目录结构,如下图. 现在我通过在 vue-a.js 中运用不同的 ...
- r.js合并实践 --项目中用到require.js做生产时模块开发 r.js build.js配置详解
本文所用源代码已上传,需要的朋友自行下载:点我下载 第一步: 全局安装 npm install -g requirejs 第二步: 1.以下例子主要实现功能, 1)引用jq库获取dom中元素文本, ...
- 【实践】require.js + r.js 代码打包压缩初体验
第二个分享的是学校项目所接触到的新知识,代码压缩 + 代码打包 这次的项目用了require.js 这个插件做模块化管理的工具,所谓模块化就是在开发的过程中将功能划分成一个独立的模块,使代码可读性更强 ...
- Javascript模块化编程之路——(require.js)
转自:http://www.ruanyifeng.com/blog/2012/10/javascript_module.html Javascript模块化编程(一):模块的写法 随着网站逐渐变成&q ...
- r.js实践
r.js合并实践 项目中用到require.js做生产时模块开发,但上线要合并压缩,幸好它配套有r.js.下面就其用法说明一下. 首先建一个目录,里面的结构如下: require.js可以到r.js项 ...
- from Require.js to Webpack(why)
写在前面: 本文主要参考 From Require.js to Webpack - Part 1 (the reasons),原文作者将项目从 require.js 转移到了 webpack 并详细说 ...
随机推荐
- Instruments-Automation: 通过命令行执行测试用例
为了实现该脚本的自己主动定时执行.我们需要开始在命令行和脚本,详细代码如下所示的: instruments -t /Applications/Xcode.app/Contents/Applicatio ...
- javascript中event汇总
原文:javascript中event汇总 ie firefox chrome opera Safari浏览器中对 event的处理并不一致,在此我将各个浏览器中event的兼容处理做了一个汇总,此处 ...
- Java 实现装饰(Decorator)模式
在Java在.io反映非常多类包下是典型的装饰格局,例如: new BufferedOutputStream(OutputStream out) new BufferedInputStream(Inp ...
- 深度解析javascript中的浅复制和深复制
原文:深度解析javascript中的浅复制和深复制 在谈javascript的浅复制和深复制之前,我们有必要在来讨论下js的数据类型.我们都知道有Number,Boolean,String,Null ...
- CSS3火焰文字特效制作教程
原文:CSS3火焰文字特效制作教程 用一句很俗气的话概括这两天的情况就是:“最近很忙”,虽然手头上有不少很酷的HTML5和CSS3资源,但确实没时间将它们的实现过程写成教程分享给大家.今天刚完成了一个 ...
- lambda 表达式 自定义查询
遇到 这样的 问题 常用 EF . 实现 like 用 Contains("asd") 搞定 他生成的是 %asd% . 如果 我希望 生成 asd%,怎么搞呢? Start ...
- DateTime.ToString("dd/MM/yyyy");后,不能直接Convert.ToDateTime的解决:
原文:DateTime.ToString("dd/MM/yyyy");后,不能直接Convert.ToDateTime的解决: DateTime.ToString("dd ...
- web设计师和前端设计师的互动—前端工程师应该具备的三种思维
如果你是一个天才工程师(马上可以离开),可以独立完成一个很多事情,你可以是一个怪咖,因为我相信没有一个人不会不佩服你.但现实归现实,多数人都不是天才,而我们在职场上也不是单打独斗,我们需要团队合作,需 ...
- MonkeyRunner源码分析之-谁动了我的截图?
本文章的目的是通过分析monkeyrunner是如何实现截屏来作为一个例子尝试投石问路为下一篇文章做准备,往下一篇文章本人有意分析下monkeyrunner究竟是如何和目标测试机器通信的,所以最好的办 ...
- MySQL之GROUP BY用法误解
1.说明 “Group By”从字面意义上理解就是根据“By”指定的规则对数据进行分组,所谓的分组就是将一个“数据集”划分成若干个“小区域”,然后针对若干个“小区域”进行数据处理.(只是简单说明这个 ...