• 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类库,比如jQuerybootstrap等,这样的话其他的页面就不需要对这些类库再引用一遍,提高了编码的效率。然而,不同的页面终究会依赖不同的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:
    ode"
    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示例程序

  • 相关阅读:
    spring学习总结009 --- 重复id或name的bean定义允许覆盖allowBeanDefinitionOverriding
    spring学习总结008 --- IOC流程图
    spring学习总结007 --- IOC容器级生命周期接口
    spring学习总结006 --- Bean级生命周期接口
    spring学习总结005 --- IOC容器启动源码(事件机制)
    字体图标
    pycharm永久激活
    Linux常用命令
    Android Studio 更新后导入旧项目Bug解决
    Ubuntu更新源
  • 原文地址:https://www.cnblogs.com/P_Chou/p/require-apply-to-asp-net-mvc.html
Copyright © 2020-2023  润新知