RequireJS路径深入详解

RequireJS路径详解(深入理解)

0. 前言


由于官方文档说明甚少,导致RequireJS的路径解析逻辑就像一个谜,本文希望能帮你解开它神秘的面纱。本文将深入讲解RequireJS的路径解析原理,如果你对RequireJS路径解析的一些基本概念还不清楚,请先参考:让人迷惑的路径解析
如果有描述不对的地方,希望能帮我指出,及时修改以免以讹传讹,谢谢。

参考版本: require-2.1.11.js
官网: http://requirejs.org/docs/api.html#jsfiles

如何查看RequireJS请求文件的路径?
可以在Chrome的Network中查看,也可以故意将文件名拼错就能在报错日志中看到实际请求路径。

image

在RequireJS中,设置baseURl的方式有如下三种

  1. 用requirejs.config显示指定baseUrl;
  2. 如果指定了Entry Point(data-main)文件,则baseUrl为Entry Point所在目录;
  3. 如果上述均未指定,则baseUrl为运行RequireJS的HTML文件所在目录。

按照官方描述如果具备以下三种特性之一,则module ID会被当做普通路径处理。

  1. 应用的module ID以.js结尾;
  2. 以“/”开始(操作系统根目录/);
  3. 包含url协议:如"http:"、"https"。
原文如下
If a module ID has one of the following characteristics, the ID will not be passed through the "baseUrl + paths" configuration, and just be treated like a regular URL that is relative to the document:
1. Ends in ".js".
2. Starts with a "/".
3. Contains an URL protocol, like "http:" or "https:".

1. "主目录"的概念

指调用RequireJS的html文件所在的目录,RequireJS中并没有“主目录”的概念,本文引入该名称只是为了方面说明。

结论1.1:在RequireJS中,baseUrl的定义是“相对于主目录”的。

---后续例子的目录结构---
www/
    html/
        index-html.html
    js/
        lib/
            hello.js
        app.js
        require-2.1.11.src.js
    index.html

例子1
以index.html为例,Entry Point为app.js(www/js/app.js)。在app.js中,baseUrl被定义为js/lib。这里的js/lib是相对于“主目录”(www/)而言的,即baseUrl实际指向www/js/lib

// ----index.html----

<!DOCTYPE html>
<html lang="en">
<head>
<title>requireJS</title>

<script src="js/require-2.1.11.src.js" data-main="js/app.js"></script>

</head>

<body>
    <h1>requireJS.</h1>
</body>
// ----app.js----

requirejs.config ({
    baseUrl: 'js/lib',
});

require(['hello'], function(hello) {
    hello.hello("RquireJS");
});

例子2
index-html.html位于www/html/目录下(主目录为www/html/),Entry Point同为app.js。则app.js中的baseUrl指向主目录+baseUrl = www/html/js/lib

// ----index-html.html----

<!DOCTYPE html>
<html lang="en">
<head>
<title>requireJS html</title>

<script src="../js/require-2.1.11.src.js" data-main="../js/app.js"></script>

</head>

<body>
    <h1>requireJS. in html</h1>
</body>

结论1.2:按照普通路径处理时候,引用路径是相对于主目录的。

前言

在RequireJS中,设置baseURl的方式有如下三种

  1. 用requirejs.config显示指定;
  2. 如果指定了Entry Point(data-main),则baseUrl为data-main所指的js的目录;
  3. 如果上述均为指定,则baseUrl为运行RequireJS的HTML文件所在目录。

但是,按照官方描述如果具备以下三种特性之一,则module ID会被当做普通路径处理。

  1. module ID以.js结尾;
  2. 以“/”开始(操作系统根目录/);
  3. 包含url协议:如"http:"、"https"。
原文如下
If a module ID has one of the following characteristics, the ID will not be passed through the "baseUrl + paths" configuration, and just be treated like a regular URL that is relative to the document:
1. Ends in ".js".
2. Starts with a "/".
3. Contains an URL protocol, like "http:" or "https:".

例子1
和前文的例子一样,只是把app.js中require语句中的module ID由hello改为hello.js。按照上述描述,文件名已.js结尾不会解析baseUrl。根据结论2按照普通路径进行解析时,路径是相对于“主目录的”,所以引用文件的路径为www/hello.js
补充:将module ID改为./hello.jshello.js引用的是同一个路径。

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>requireJS</title>

<script src="js/require-2.1.11.src.js" data-main="js/app.js"></script>

</head>

<body>
    <h1>requireJS.</h1>
</body>

// app.js
requirejs.config ({
    baseUrl: 'js/lib',
});

require(['hello.js'], function(hello) {
    hello.hello("RquireJS");
});

例子2
把index.html换成index-html.html(位于www/html/目录),引用相同的app.js文件。所以引用文件的路径变成了www/html/hello.js

//index-html.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>requireJS html</title>

<script src="../js/require-2.1.11.src.js" data-main="../js/app.js"></script>

</head>

<body>
    <h1>requireJS. in html</h1>
</body>

2.路径的级联处理


当A模块使用(require语句)了B模块,B模块的定义中指定了对C模块的依赖(define语句),则B模块中的define语句如何根据A模块中的require语句查找C的路径的过程称作路径的级联处理(非官方命名,只是为了便于说明)。

在定义模块时会明确指出该模块依赖了哪些模块。按理说,一个模块依赖了哪些模块、从哪里获取这些模块应该模块的编写者明确指定,模块的使用者无需关心。然而,在RquireJS中并非如此:在使用模块时,指定该模块的路径的方式可能会影响到被使用的模块如何去查找它自身依赖的模块。——只是是我们使用RequireJS时应该特别注意的地方,个人觉得也是RquireJS应该改进的地方。

---后续例子的目录结构---
www/
    js/
        lib/
            log.js
            hello.js
        app.js
        require.js
    index.html

例如,app.js引用了hello模块, hello模块依赖了log.js。下面列举了3种引用hello模块的方式,每种方式均能正确地找到hello.js。但是hello模块查找log.js的路径各不相同,分别是: www/js/js/lib/log.jswww/js/lib/log.jswww/js/log.js

// ---hello.js---

define(['./log'], function(log) {
    var hello = function(msg) {
            log.log('www/js/lib/hello.js: ' + msg);
        };
    return {
        hello: hello
    };
});

// [引用hello.js的三种方式]
// ---方式1: 使用普通路径(不使用baseUrl)---
require(['js/lib/hello.js'], function(hello) {
    hello.hello("RquireJS");
});

// ---方式2: 使用baseUrl---
requirejs.config ({
    baseUrl: 'js/lib',
});

require(['hello'], function(hello) {
    hello.hello("RquireJS");
});

// ---方式3: 使用paths定义---
requirejs.config ({
    baseUrl: 'js',
    paths: {
        "hello": "lib/hello"
    }
});

require(['hello'], function(hello) {
    hello.hello("RquireJS");
});

为什么改变app.js中引用hello模块的方式会影响到hello模块查找log.js的路径?如何才能使得hello模块查找log.js的路径不受app.js的影响?请看后文。

结论2.1:在一个模块的定义内寻找依赖时候会首先会进行./替换

在定义模块时(define语句)声明的依赖中如果使用了./,在路径解析时./会被替换成使用该模块(require语句)时的路径前缀(路径中最后一个"/"前的所有部分),如果没有前缀则不进行替换。

  1. 如果写成:require(['js/lib/hello.js'],则路径的前缀为"js/lib/";
  2. 如果写成:require['hello.js'],则表示没有路径前缀。

例子2.1

// ---app.js---

require(['js/lib/hello.js'], function(hello) {
    hello.hello("RquireJS");
});

// ---hello.js---
define(['./log.js'], function(log) {
    var hello = function(msg) {
            log.log('www/js/lib/hello.js: ' + msg);
        };

    return {
        hello: hello
    };
});

为了降低问题的复杂度先以普通路径(module ID包含.js后缀名,避免baseUrl的影响)为例进行说明。

  1. 本例中app.js中require语句的路径前缀为js/lib/,则在hello.js中./log.js会被替换为js/lib/log.js
  2. 根据前文描述,以.js结尾则不进行baseUrl处理,即最终的路径为“主目录”/js/lib/log.js,即:www/js/lib/log.js

附加实验
如果将./log.js改为log.js,则不进行./替换,最终指向www/log.js

baseUrl处理

例子2.2

// hello.js
define(['./log'], function(log) {
    var hello = function(msg) {
            log.log('www/js/lib/hello.js: ' + msg);
        };

    return {
        hello: hello
    };
});

在级联处理中,仍然会进行baseUrl处理。将上述例子中hello.js内log.js改成log,则产生log.js的路径的步骤如下:

  1. 进行./替换,./log->js/lib/log
  2. 拼接baseUrl,得到js/js/lib/log(没有显示指定baseUrl,则baseUrl为data-main指定的js文件所在目录,即js)
  3. 拼接“主目录”,得到www/js/js/lib/log

paths替换

例子2.3

// app.js
requirejs.config ({
    baseUrl: "./",
    paths: {
        "lib": "js/lib"
    }
});

require(['lib/hello'], function(hello) {
    hello.hello("RquireJS");
});

在级联处理中,仍然会做paths替换。app.js中定义了pathlib(指向js/lib)。在hello模块中log.js的路径解析步骤如下:

  1. 进行./替换(./->lib),得到lib/log
  2. 进行paths替换(lib->js/lib),得到js/lib/log
  3. 进行baseUrl拼接,得到./js/lib/log
  4. 进行“主目录”拼接,得到www/./js/lib/log。最终指向www/js/lib/log.js(非级联处理中./表示当前目录,可以省略)

附加实验

  1. 如果将./log改为./log.js,则不会进行paths处理和baseUrl替换,则会指向www/lib/log.js文件。
  2. 如果将./log改为log,则不会进行./替换,最终指向www/log.js

3. 路径解析逻辑


结论3.1:RequireJS中路径的处理流程如下图所示

综上所述,RequireJS中路径解析过程如上图所示。在级联处理中会首先进行./替换操作;然后再针对不以.js结尾的进行paths替换和baseUrl拼接;最后拼接上“主目录”。

前文app.js的3种写法对应的log.js的路径解析过程如下

方式1

  1. 进行'./'替换,./log->js/lib/log
  2. 拼接baseUrl(baseUrl为data-main指定的app.js所在目录),得到js/js/lib/log
  3. 拼接“主目录”,得到www/js/js/lib/log,最终指向www/js/js/lib/log.js

方式2

  1. 路径前缀为空,不进行'./'替换;
  2. 拼接baseUrl,得到js/lib/log
  3. 拼接“主目录”,得到www/js/lib/log,最终指向www/js/lib/log.js

方式3

  1. 进行'./'替换,./log->lib/log
  2. 进行paths替换,得到js/lib/log
  3. 拼接baseUrl(baseUrl为data-main指定的app.js所在目录),得到js/js/lib/log
  4. 拼接“主目录”,得到www/js/js/lib/log,最终指向www/js/js/lib/log.js

4. 思考

为什么下述两种方式引用的log.js不同?

// ---方式3
requirejs.config ({
    baseUrl: './',
    paths: {
        "hello": "js/lib/hello"
    }
});

require(['hello'], function(hello) {
    hello.hello("RquireJS");
});

// ---方式4
requirejs.config ({
    baseUrl: "./",
    paths: {
        "lib": "js/lib"
    }
});

require(['lib/hello'], function(hello) {
    hello.hello("RquireJS");
});

回答让人迷惑的路径解析博主的问题。

下述两种写法引用的log.js分别为www/log.jswww/js/lib/log.js,路径解析步骤如下:

方式3

  1. 没有前缀,不进行./替换;
  2. ./log进行baseUrl处理,得到././log;
  3. 进行”主目录拼接“,得到www././log,最终指向www/log.js

方式4

  1. 进行./替换,./log->lib/log;
  2. 进行paths替换,得到js/lib/log;
  3. 进行baseUrl处理,得到./js/lib/log;
  4. 进行”主目录“拼接,得到www./js/lib/log,最终指向www/js/lib/log.js

方式3和方式4关键的区别在于前者没有路径前缀,看一下方式4的./替换和paths替换流程:

1. ./替换前
name: ./log

2. ./替换后
name: lib/log

3. paths替换前
name: lib/log

4. paths替换后
name: js/lib/log


最后两句还进行了baseUrl拼接和后缀名拼接。

  对于方式4而言,在查找`./log`的时候RequireJS先将require(['lib/hello'])中的`lib/hello`路径最后一部分去掉,将得到的路径(`lib`)拼接到`./`前,就得到了`lib/log`,然后再对lib/进行paths展开操作。对于方式3而言,由于得到的路径为空所以就没有后续的paths展开过程了。  
  问题的关键在于RequireJS先进行了`./`替换,再进行paths展开。这里个人认为是RequireJS的错误,将`./`替换和paths替换顺序反过来即可,应当先展开paths,将`hello`展开成`js/lib/hello`,然后再将前缀`js/lib`拼接到`./log`的前面,得到`js/lib/log`。  

——目前认为是RequireJS的bug,不知道是不是RequireJS故意为之。

说明,其实并没有”主目录“的概念,只是为了便于说明。因为JS代码最终被加载到HTML中运行,所以HTML所在的目录即为JS运行时的目录,因此JS中指定的路径均是相对于该目录而言的。


诚邀英才~

快来,支付宝前端高配团队有坑位了!

我们是谁?我们是支付宝前端技术部-支付业务前端技术组,技术和业务双核心。
我们是做什么的?负责支付宝最重要的支付相关业务(收银台/收付款码/转账/嵌入式支付等),服务亿级用户,连接千万商家。
我们高配在哪?有50星王者的H5动效专家,有追求移动端极致体验的浮潜女神,有自主研发单片机JavaScript运行时的哈雷忠粉,有通读Linux内核源码的单板滑雪爱好者,还有一群快乐的动森er~
还有____(填坑题,等你来填)

我们招人条件?只要你有想法,带上简历快联系我。 团队详情请>
邮箱:kevinliu.lj@alipay.com,微信号:janneo

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,601评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,520评论 25 707
  • 导语: 之前一直有听说RequireJS,但是一直都没机会去了解,只知道它是一个给js做模块化的API。最近在做R...
    wuqke阅读 40,898评论 11 78
  • 土匪在简书阅读 176评论 0 0
  • 一早起来,冒雨就去把车定了,我跟老公一直改,不是改颜色就是改配置,现在终于定下来交了资料,然后就带着老妈来...
    FAB杨言娜阅读 141评论 0 0