requirejs:让人迷惑的路径解析

接触过requirejs的童鞋可能都知道,无论是通过define来定义模块,还是通过require来加载模块,模块依赖声明都是很重要的一步。而其中涉及到的模块路径解析,对于新手来说,有的时候会让人觉得很困惑。

start up

假设我们的目录结构如下:

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加载下来后,它会做两件事件:

  1. 加载js/main.js
  2. 将baseUrl设置为data-main指定的文件所在的路径,这里是 js/
<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的那个页面所在的路径

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:让依赖更简洁、灵活

比如我们加载了下面一堆模块(好多水果。。。),看着下面一长串的依赖列表,可能你一下子就看出问题来了:

  1. 费力气:每个加载的模块前面都有长长的common/fruits
  2. 难维护:说不定哪一天目录名就变了(在大型项目中并不算罕见),想象一下目录结构变更带来的工作量
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就可以了。

requirejs.config({
    baseUrl: 'js',
    paths: {
        fruits: 'common/third-party/fruits'
    }
});

paths:简单但需要记住的要点

上一节已经举例说明了path的例子。这里再来个例子,说明下下三种情况下,匹配路径的规则
>

  1. apple:没有在paths规则里定义,于是为 baseUrl + apple.js => js/apple.js
  2. common/fruits:common已经在paths里定义,于是为baseUrl + common/fruits + apple.js => js/common/fruits/apple.js
  3. ../common/apple:common尽管已经在paths里定义,但是../common/apple并不是以common开头,于是为 baseUrl + ../common/apple.js => common/apple.js
requirejs.config({
    baseUrl: 'js',
    paths: {
        common: 'common/fruits'
    }
});

// 从左到右,加载的路径依次为 js/lib.js、 js/common/jquery/lib.js、common/lib.js
require(['apple', 'common/apple', '../common/apple'], function(){
    // do something
});

./module:让人疑惑的相对路径

应该说,这个是最让人疑惑的地方。

demo 1

js/main.js

requirejs.config({
    baseUrl: 'js/common'
});
// 实际加载的路径都是是 /lib.js
require(['./lib', 'lib'], function(Lib){
    Lib.say('hello');
});

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/lib.js

// 依赖util模块
define(['./util'], function(Util){
    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

// 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');
});

util.js

define(['./util'], function(Lib){
    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)什么时候是相对当前路径

加个@todo,这个估计只有作者和看过源码的人知道了,好像文档里也没明确说到~todo下先

// @todo

写在后面

啰啰嗦嗦写了一大堆,requirejs中的路径解析整体上不复杂,但./module这种形式的路径解析,对于刚接触requirejs的人来说稍微有些费解。也许,当你从requirejs设计者的角度来看,问题可能相对好理解一些。

时间: 2024-08-04 09:16:54

requirejs:让人迷惑的路径解析的相关文章

XML文件DTD路径解析问题(in Eclipse)

1.xml文件声明的dtd文件路径如下: <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN""http://java.sun.com/dtd/ejb-jar_2_0.dtd"><ejb-jar >

解密6个让人迷惑的文件服务器NTFS权限问题

基于Windows平台的文件服务器是个简单易行的方案,而NTFS是Windows文件服务器最关键的权限机制 .正如我们所知,NTFS提供了一套有效的文件(文件夹)安全访问机制,它可以让我们对用户如何读取.写 入以及以其它方式来操作系统中的文件进行严格的控制.但NTFS权限是复杂的,通常情况下,管理员虽然 在文件服务器上按要求设置了文件或文件夹权限,但是却未能得到我们所预期的效果,即使是一个经验丰 富的管理员也会遇到这样的困惑.是什么原因呢?笔者认为主要原因是他们不知道或者忽略了NTFS的某些 安

BAT三巨头掘“金”路径解析

近年来我国的实体经济一直处于相对疲软的状态,经济空心化的现象日益明显.反观金融却是另一番景象,中国企业联合会发布的<2012中国500强发展报告>中显示:5银行的利润超过272家制造企业的总和.另有统计表明互联网BAT三巨头2012年净利润的总和居然远远低于地方性的上海浦发银行,就更别提工农中建交了.难怪连民生银行行长洪崎都曾表示"银行利润那么高,有时候自己都不好意思公布". 互联网正在实现对各行各业的颠覆与重塑,金融则是其中之一.从2012年下半年开始,互联网金融逐渐成为

Servlet 工作原理解析

从 Servlet 容器说起 要介绍 Servlet 必须要先把 Servlet 容器说清楚,Servlet 与 Servlet 容器的关系有点像枪和子弹的关系,枪是为子弹而生,而子弹又让枪有了杀伤力.虽然它们是彼此依存的,但是又相互独立发展,这一切都是为了适应工业化生产的结果.从技术角度来说是为了解耦,通过标准化接口来相互协作.既然接口是连接 Servlet 与 Servlet 容器的关键,那我们就从它们的接口说起. 前面说了 Servlet 容器作为一个独立发展的标准化产品,目前它的种类很多

Nodejs基础:路径处理模块path总结

本文摘录自<Nodejs学习笔记>,更多章节及更新,请访问 github主页地址. 模块概览 在nodejs中,path是个使用频率很高,但却让人又爱又恨的模块.部分因为文档说的不够清晰,部分因为接口的平台差异性. 将path的接口按照用途归类,仔细琢磨琢磨,也就没那么费解了. 获取路径/文件名/扩展名 获取路径:path.dirname(filepath) 获取文件名:path.basename(filepath) 获取扩展名:path.extname(filepath) 获取所在路径 例子

一键解析XML文件(利用Digester实现可配置)

大部分程序员,平时工作中除了与Bug相伴之外,想必也会很多种多样的文件打交道吧.当然,XML 就是其中之一,获取交互数据,创建规则等等,都离不开他.XML是个非常强大的描述语言,相比而言,txt之流则功力较弱了些.      XML那么重要,单解析XML的工作却繁杂无聊.原因如下:解析XML工具繁杂,每个人掌握的东西不一样;学习新工具又浪费时间;解析规则只是个体力活.     程序员的辛苦,也就体现于此了.一般程序员总是抱怨忙啊忙,累啊累.我们作为程序员来说,更应该想一想想法设法吧东西提炼一下,

一篇文章掌握RequireJS常用知识_javascript技巧

本文采取循序渐进的方式,从理论到实践,从RequireJS官方API文档中,总结出在使用RequireJS过程中最常用的一些用法,并对文档中不够清晰具体的内容,加以例证和分析,分享给大家供大家参考,具体内容如下 1. 模块化 相信每个前端开发人员在刚开始接触js编程时,都写过类似下面这样风格的代码: <script type="text/javascript"> var a = 1; var b = 2; var c = a * a + b * b; if(c> 1)

photoshop照片处理成液体人制作教程

给各位photoshop软件的使用者们来详细的解析分享一下照片处理成液体人的制作教程. 教程分享: 原图   教程最终效果   具体的制作步骤如下: 1.打开原图素材,先用钢笔工具把人物勾出来,按Ctrl + Enter 把路径转为选区,按Ctrl + J 把人物复制到新的图层.   2.按Ctrl + M 对抠出的人物调整曲线,参数设置如下图.   3.新建一个图层,填充颜色:#0A6A60.   4.把人物皮肤到头部这上半部分抠出来,复制到新的图层.   5.把前景颜色设置为翠绿色,背景设置

地下城与勇士红眼装备提升养成路径详解

给各位地下城与勇士游戏里的玩家们来的详细的解析分享一下红眼装备提升养成路径. 分享一览: 对于红眼来说有几条路径: 1.6+3. 2.强散. 3.ss套. 4.py交易 下面重点说下6+3和强散 6+3:这套装备在安图恩表现不错,强大的续航能力毋庸置疑,胜任除炮舰任何位置.但终究是异界提升有限,且颇为无聊,而且没有高双刀的支持,对于目前没有技能宝珠的玩家不推荐走.喜欢的玩家可以坚持,毕竟以后高血量的地图,ss套一套带不走的,你也可以喷死. 强散:(火战士,暗散,冰散,光散)总归是有一些强力的ss