转:requirejs:让人迷惑的路径解析(~~不错)
接触过requirejs的童鞋可能都知道,无论是通过define来定义模块,还是通过require来加载模块,模块依赖声明都是很重要的一步。而其中涉及到的模块路径解析,对于新手来说,有的时候会让人觉得很困惑。 ~~~通常用require加载模块 , define定义模块, requirejs == require //true
假设我们的目录结构如下:
demo.html
js/main.js
js/lib.js
js/util.js
js/common/lib.js
js/common/jqury/lib.js
common/lib.js
下面的这两个例子,看着很简单吧,但应该大部分的人跟我一样没办法一眼就识别出来依赖模块最终转化成的路径。原因在于,这里压根就没有提供足够的上下文信息。。。(= =b 别打我)
require例子:
// main.js
require(['./util'], function(){
// do sth
});
define例子:
define(['./util'], function(){
// do sth
});
下面,我们再一步步通过具体的例子来看下,requirejs在不同的场景下,是如何解析模块路径的。
baseUrl
:基础中的基础
在requirejs的模块路径解析里, baseUrl
是非常基础的概念,离开了它,基本就玩不转了,所以这里简单介绍一下。简单的说, baseUrl
指定了一个目录,然后requirejs基于这个目录来寻找依赖的模块。
举个栗子,在demo.html里加载requirejs,同时在requirejs所在的script上声明 data-main
属性,那么,requirejs加载下来后,它会做两件事件:
~~~baseUrl的值,可为绝对路径,可为相对路径,绝对路径不用解释,相对路径是相对谁的呢?引入requirejs的页面还是require.config代码所在模块的路径?
- 加载js/main.js
- 将baseUrl设置为data-main指定的文件所在的路径,这里是 js/
~~~data-main指定入口模块路径,入口模块所在的目录(js/)设置为baseUrl的值 (baseUrl: 'js'),所以baseUrl为相对路径的话是相对引入requirejs的页面的
<script src="js/require.js" data-main="js/main.js"></script>
那么,下面依赖的lib模块的实际路径为 js/lib.js
main.js
require(['lib'], function(Lib){
// do sth
});
当然,除了 data-main
属性,你也可以手动配置 baseUrl
,比如下面例子。需要强调的是:
如果没有通过 data-main
属性指定 baseUrl
,也没有通过config的方式显示声明 baseUrl
,那么 baseUrl
默认为加载requirejs的那个页面所在的路径
~~~baseUrl是始终会被设置的,要么require.config指定一个相对html页面的路径作为baseUrl,要么为入口模块的路径即data-main指定的路径,要么为引入requirejs的html页面的路径。 baseUrl是进行模块加载时模块路径的相对路径,所以必须存在。
demo.html
<script src="js/require.js"></script>
<script src="js/main.js"></script>
main.js
requirejs.config({
baseUrl: 'js'
});
require(['lib'], function(Lib){
// do sth
});
baseUrl
+ path
:让依赖更简洁、灵活
比如我们加载了下面一堆模块(好多水果。。。),看着下面一长串的依赖列表,可能你一下子就看出问题来了:
- 费力气:每个加载的模块前面都有长长的
common/fruits
- 难维护:说不定哪一天目录名就变了(在大型项目中并不算罕见),想象一下目录结构变更带来的工作量
requirejs.config({
baseUrl: 'js'
});
// 加载一堆水果
require(['common/fruits/apple', 'common/fruits/orange', 'common/fruits/grape', 'common/fruits/pears'], function(Apple, Orange, Grape, Pears){
// do sth
});
对一个模块加载器来说,上面说的这两点问题显然需要考虑进去。于是requirejs的作者提供了 paths
这个配置项。我们看下修改后的代码。
requirejs.config({
baseUrl: 'js',
paths: {
fruits: 'common/fruits'
}
});
// 加载一堆水果
require(['fruits/apple', 'fruits/orange', 'fruits/grape', 'fruits/pears'], function(Apple, Orange, Grape, Pears){
// do sth
});
其实就少了个 common
前缀,也没节省多少代码,但当项目结构变更时,好处就体现了。假设 common/fruits
某一天突然变成了 common/third-party/fruits
,那很简单,改下 paths
就可以了。 ~~~paths一般用于定义baseUrl路径之外的其他路径,为这些其他路径设置别名,当然也可以为baseUrl的子路径定义别名
requirejs.config({
baseUrl: 'js',
paths: {
fruits: 'common/third-party/fruits'
}
});
paths
:简单但需要记住的要点
~~~~模块路径解析最容易迷惑的是 为相对路径时,到底是相对谁而言的,有3种情况。相对引入requirejs的html页面,相对baseUrl, 相对模块自身路径
~~~~ baseUrl + paths解析模式下,路径是相对baseUrl的 什么时候是这种模式呢?
~~~ require(['a', 'foo/b', '../c'],function(..){}); //加载模块时 define(['a','foo'],function(..){}) //定义模块 依赖模块名为纯名字 无斜杠时
~~~ define(['./a','../b'],function(...){}); //定义模块 模块为相对路径时,路径是相对模块自身路径的
~~~ require(['a.js', '../b.js', '/foo/c.js'], function(){}); 不自动添加后缀时,相对路径是相对html页面的
上一节已经举例说明了path的例子。这里再来个例子,说明下下三种情况下,匹配路径的规则
requirejs.config({
baseUrl: 'js',
paths: { common: 'common/fruits' }
});
// 从左到右,加载的路径依次为 js/apple.js、 js/common/fruits/apple.js、common/apple.js
require(['apple', 'common/apple', '../common/apple'], function(){
// do something
});
apple
:没有在paths规则里定义,于是为 baseUrl + apple.js => js/apple.js ~~~加载模块时,paths规则中无匹配,则模块路径为baseUrl + moduleIDcommon/apple
:common已经在paths里定义,于是为baseUrl + common/fruits + apple.js => js/common/fruits/apple.js ~~~加载模块时,paths规则有匹配,则模块路径为 baseUrl + paths + moduleID../common/apple
:common尽管已经在paths里定义,但是../common/apple
并不是以common开头,于是为 baseUrl + ../common/apple.js => common/apple.js
./module
:让人疑惑的相对路径
应该说,这个是最让人疑惑的地方。
demo 1
js/main.js
requirejs.config({
baseUrl: 'js/common'
});
// 实际加载的路径都是是 js/common/lib.js
require(['./lib', 'lib'], function(Lib){
Lib.say('hello');
}); ~~~~模块加载,即require时 相对路径是相对baseUrl的
demo 2
简单改下上面的例子,可以看到:
通过 define
定义模块A时,模块A依赖的模块B,如果是 ./module
形式,则基于模块A所在目录解析模块B的路径。
js/main.js
requirejs.config({
baseUrl: 'js'
});
// 依赖lib.js,实际加载的路径是 js/common/lib.js,而lib模块又依赖于util模块('./util'),解析后的实际路径为 js/common/util.js
require(['common/lib'], function(Lib){
Lib.say('hello');
});
js/common/lib.js
// 依赖util模块
define(['./util'], function(Util){ //~~~定义模块时,即define时,相对路径是相对当前模块所在路径的,这样即使没有加载入口,模块之间的依赖关系已经是明确的
return {
say: function(msg){
Util.say(msg);
}
};
});
demo 3
demo2实际上会有特例,比如下面,lib模块依赖的util模块,最终解析出来的路径是 js/util.js
main.js
requirejs.config({
baseUrl: 'js',
paths: {
lib: 'common/lib'
}
});
// 实际加载的路径是 js/common/lib.js
require(['lib'], function(Lib){
Lib.say('hello');
});
lib.js
~~~通过别名映射过来的当前模块,其模块定义中的依赖模块为相对路径 则是相对baseUrl的,至于为什么 不解
// util模块解析后的路径为 js/util.js
define(['./util'], function(Lib){
return {
say: function(msg){
Lib.say(msg);
}
};
});
demo 4
上面讲到通过paths指定的模块路径加载模块时, ./module
路径解析就会按照 baseUrl
+ moduleName
的方式,但稍微修改下main.js,发现结果就不一样了。此时,util模块对应的路径为 js/common/util.js
main.js
requirejs.config({
baseUrl: 'js',
paths: {
common: 'common'
}
});
// 实际加载的路径是 js/common/lib.js ~~~这个别名定义不定义是一样的
require(['common/lib'], function(Lib){
Lib.say('hello');
});
lib.js
define(['./util'], function(Lib){ // ~~ js/common/util.js
return {
say: function(msg){
Lib.say(msg);
}
};
});
各种疑问
为什么require(['./module'], callback)不是相对于当前路径解析
如下面例子所示,我们可能会疑惑,为什么不是相对于 main.js
所在的路径解析呢?其实很简单,作者也不知道你这段代码出现在哪个文件呀亲(~~~这里有点不正确吧?)。所以,只能通过 baseUrl
js/main.js
require(['./lib', function(Lib){// ~~~~要是这段代码被解析,说明代码所在文件能被访问 ,这个文件的路径是已知的
// do sth
}]);
define(['./module'], callback)什么时候是相对当前路径 ~~~加载模块依据baseUrl确定模块路径,定义模块依据模块文件所在路径确定依赖模块的路径
加个@todo,这个估计只有作者和看过源码的人知道了,好像文档里也没明确说到~todo下先
// @todo
写在后面
啰啰嗦嗦写了一大堆,requirejs中的路径解析整体上不复杂,但 ./module
这种形式的路径解析,对于刚接触requirejs的人来说稍微有些费解。也许,当你从requirejs设计者的角度来看,问题可能相对好理解一些。