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閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,826評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,733評論 25 708
  • 導語: 之前一直有聽說RequireJS,但是一直都沒機會去了解,只知道它是一個給js做模塊化的API。最近在做R...
    wuqke閱讀 40,945評論 11 78
  • 土匪在簡書閱讀 182評論 0 0
  • 一早起來,冒雨就去把車定了,我跟老公一直改,不是改顏色就是改配置,現在終于定下來交了資料,然后就帶著老媽來...
    FAB楊言娜閱讀 149評論 0 0