RequireJS介绍
异步模块定义(AMD)
谈起RequireJS,你无法绕过提及JavaScript模块是什么,以及AMD是什么。
JavaScript模块只是遵循SRP(Single Responsibility Principle单一职责原则)的代码段,它暴露了一个公开的API。在现今JavaScript开发中,你可以在模块中封装许多功能,而且在大多数项目中,每个模块都有其自己的文件。这使得JavaScript开发者日子有点难过,因为它们需要持续不断的关注模块之间的依赖性,按照一个特定的顺序加载这些模块,否则运行时将会放生错误。
当你要加载JavaScript模块时,就会使用script标签。为了加载依赖的模块,你就要先加载被依赖的,之后再加载依赖的。使用script标签时,你需要按照此特定顺序安排它们的加载,而且脚本的加载是同步的。可以使用async和defer关键字使得加载异步,但可能因此在加载过程中丢失加载的顺序。另一个选择是将所有的脚本捆绑打包在一起,但在捆绑的时候你仍然需要把它们按照正确的顺序排序。
AMD就是这样一种对模块的定义,使模块和它的依赖可以被异步的加载,但又按照正确的顺序。
本规范只定义了一个函数 “define”,它是全局变量。函数的描述为:
define(id?, dependencies?, factory);
名字
第一个参数,id,是个字符串,它指的是定义中模块的名字,这个参数是可选的。如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
依赖
第二个参数,dependencies,是个定义中模块所依赖模块的数组。依赖模块必须根据模块的工厂方法优先级执行,并且执行的结果应该按照依赖数组中的位置顺序以参数的
依赖的模块名如果是相对的,应该解析为相对定义中模块。换句话来说,相对名解析为相对与模块的名字,并非相对于寻找该模块的名字的路径。
本规范定义了截然不同的三种特殊的依赖关键字。如果”require”,”exports”, 或 “module”出现在依赖列表中,参数应该按照CommonJS模块规范自由变量去解析。
依赖参数是可选的,如果忽略此参数,它应该默认为[“require”, “exports”, “module”]。然而,如果工厂方法的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。
工厂方法
第三个参数,factory,为模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。
如果工厂方法返回一个值(对象,函数,或任意强制类型转换为true的值),应该为设置为模块的输出值。
RequireJS?
RequireJS是一个Javascript 文件和模块框架,可以从 http://requirejs.org/下载,如果你使用Visual Studio也可以通过Nuget获取。它支持浏览器和像node.js之类的服务器环境。使用RequireJS,你可以顺序读取仅需要相关依赖模块。
RequireJS所做的是,在你使用script标签加载你所定义的依赖时,将这些依赖通过head.appendChild()函数来加载他们。当依赖加载以后,RequireJS计算出模块定义的顺序,并按正确的顺序进行调用。这意味着你需要做的仅仅是使用一个“根”来读取你需要的所有功能,然后剩下的事情只需要交给RequireJS就行了。为了正确的使用这些功能,你定义的所有模块都需要使用RequireJS的API,否者它不会像期望的那样工作。
RequireJS暴露了requirejs,require,define三个对象。requirejs和require是一样的,只是为了在别的库也用到“require”名字,可以用requirejs代替。Export require as a global, but only if it does not already exist. require/requirejs 是函数对象,但他们还有一个“define函数”属性。
简单来讲,require用于引用模块,define用于定义模块。
Requirejs的用法
很简单,在页面的HTML中加载require.js和它的配置文件就可以了,然后页面加载的时候会自动加载依赖的模块。但是我们这个例子的做法稍微有点不一样,在这个例子中,我们把包括require.js和require.config.js在内的文件都打包到app.js中,所以我们在HTML中就只加载app.js。配置文件在我们的例子中为require.config.js:
/*global requirejs*/ requirejs.config({ baseUrl: 'src', waitSeconds: 120, deps: ['app'], //enforceDefine: true, wrapShim: true, shim: { jquery: { exports: 'jQuery' }, lodash: { exports: '_' }, angular: { exports: 'angular', deps: ['jquery'] }, 'angular-animate': { deps: ['angular'] }, 'angular-cookies': { deps: ['angular'] }, 'angular-csp': { deps: ['angular'] }, 'angular-loader': { deps: ['angular'] }, 'angular-local-storage': { deps: ['angular'] }, 'angular-mocks': { deps: ['angular'] }, 'angular-messages': { deps: ['angular'] }, 'angular-resource': { deps: ['angular'] }, 'angular-route': { deps: ['angular'] }, 'angular-sanitize': { deps: ['angular'] }, 'angular-scenario': { deps: ['angular'] }, 'angular-touch': { deps: ['angular'] }, 'angular-ui-bootstrap': { deps: ['angular'] }, 'angular-ui-router': { deps: ['angular'] }, 'd3': { exports: 'd3' }, polyglot: { exports: 'Polyglot' } } });
baseUrl: 模块所在目录为 /src/
deps: 依赖项数组。An array of dependencies to load. Useful when require is defined as a config object before require.js is loaded, and you want to specify dependencies to load as soon as require() is defined. Using deps is just like doing a require([])
call, but done as soon as the loader has processed the configuration. It does not block any other require() calls from starting their requests for modules, it is just a way to specify some modules to load asynchronously as part of a config block. [懒得翻译了…] 简单说,就是在用
<script src="js/lib/require.js"></script> <script src="js/config.js"></script>
这种模式的时候(Patterns-for-separating-config-from-the-main-module),deps比较有用,它可以用来指定我们项目要加载的模块。(另外,deps也用在shim中,代表模块的依赖项)。
但是,我们也可以在requirejs.js中用name字段定义项目要加载的模块。我试验了下,这两种方式都可以:在requirejs.js中使用name: “app”和require.config.js中使用deps: [“app”] 是一样的效果。
waitSeconds: 加载js的超时时间,默认7秒
path: 模块名和路径名对应关系。这个放到grunt-contrib-requirejs的配置项中去了。
shim: 定义依赖关系。注意:shim配置只是用来配置non-AMD模块的,AMD模块不要使用shim来配置。另外一点就是export出来的名字可以不在define中显示声明也可以使用:
define([ 'angular'], function(angular) { _.assign({ 'a': 1 }, { 'b': 2 }, { 'c': 3 }); });
这里我们没有在define得第一参数写成 [ ‘angular’, ‘lodash’ ]也能在内部使用loadsh。这是因为我们使用了shim。
Requirejs Task
这个task用到了Grunt插件grunt-contrib-requirejs. 它使用了r.js来优化requirejs项目,requirejs让我们把项目模块化,然后r.js又可以吧这些独立的文件全部打包成一个JS文件。
Task的配置文件requirejs.js为:
var path = require('path'); module.exports = function(grunt) { var buildDir = require('../buildDir.js')(); grunt.loadNpmTasks('grunt-contrib-requirejs'); var paths = { "main":"modules/main/js", "core":"modules/core/js", "angular": "lib/angular", "angular-animate": "lib/angular-animate", "angular-cookies": "lib/angular-cookies", "angular-csp": "lib/angular-csp", "angular-loader": "lib/angular-loader", "angular-local-storage": "lib/angular-local-storage", "angular-messages": "lib/angular-messages", "angular-mocks": "lib/angular-mocks", "angular-resource": "lib/angular-resource", "angular-route": "lib/angular-route", "angular-sanitize": "lib/angular-sanitize", "angular-scenario": "lib/angular-scenario", "angular-touch": "lib/angular-touch", "angular-ui-bootstrap": "lib/ui-bootstrap-tpls-0.12.1", "angular-ui-router": "lib/angular-ui-router", "d3": "lib/d3", "jade": "lib/jade", "jquery": "lib/jquery-2.1.3", "lodash": "lib/lodash", "polyglot": "lib/polyglot", "views": path.resolve('..',buildDir, 'tmp','views') }; var src = 'src'; var target = path.join('target', 'build', 'webapp'); var pathToConfig = path.join('src', 'config', 'require.config.js'); optimizeOptions = grunt.option('build-requirejs-optimize') || 'none'; grunt.config('requirejs', { dev: { options: { name: 'app', optimize: 'none', baseUrl: 'src', paths: paths, mainConfigFile: 'src/config/require.config.js', out: 'target/build/webapp/app.js', include: ['lib/require.js','config/require.config.js'] } }, production: { options: { name: 'app', optimize: 'uglify2', preserveLicenseComments: false, baseUrl: 'src', paths: paths, mainConfigFile: 'src/config/require.config.js', out: 'target/build/webapp/app.js', include: ['lib/require.js','config/require.config.js'] } } }); };
path参数本来是放在require.config.js中的,但我们也可以把它放到task的配置中。
out是最终打包成的文件的路径。
mainConfigFile存放requirejs配置文件的路径。
name是要打包成一个文件的模块名,即模块的文件名(不带.js后缀名部分)。
include是除了app模块及其依赖的模块外,还要额外依赖的模块:require.js和require.config.js。这样,我们只需要在页面的HTML中只加载app.js就好了。
最后查看生成的app.js,里面包含了所有依赖的模块文件。值得注意的一点是在这个整合的文件中,requirejs的源码放在app.js的最开头,而require.config.js得代码则放在最后。这个我觉得可能的原因是:我们在requirejs Task的配置文件requirejs.js中,我们已经把
Leave a Reply