RequireJS路徑詳解(深入理解)
0. 前言
由于官方文檔說明甚少,導致RequireJS的路徑解析邏輯就像一個謎,本文希望能幫你解開它神秘的面紗。本文將深入講解RequireJS的路徑解析原理,如果你對RequireJS路徑解析的一些基本概念還不清楚,請先參考:讓人迷惑的路徑解析
如果有描述不對的地方,希望能幫我指出,及時修改以免以訛傳訛,謝謝。
參考版本: require-2.1.11.js
官網: http://requirejs.org/docs/api.html#jsfiles
如何查看RequireJS請求文件的路徑?
可以在Chrome的Network
中查看,也可以故意將文件名拼錯就能在報錯日志中看到實際請求路徑。
在RequireJS中,設置baseURl的方式有如下三種
- 用requirejs.config顯示指定baseUrl;
- 如果指定了Entry Point(data-main)文件,則baseUrl為Entry Point所在目錄;
- 如果上述均未指定,則baseUrl為運行RequireJS的HTML文件所在目錄。
按照官方描述如果具備以下三種特性之一,則module ID會被當做普通路徑
處理。
- 應用的module ID以.js結尾;
- 以“/”開始(
操作系統根目錄/
); - 包含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的方式有如下三種
- 用requirejs.config顯示指定;
- 如果指定了Entry Point(data-main),則baseUrl為data-main所指的js的目錄;
- 如果上述均為指定,則baseUrl為運行RequireJS的HTML文件所在目錄。
但是,按照官方描述如果具備以下三種特性之一,則module ID會被當做普通路徑處理。
- module ID以.js結尾;
- 以“/”開始(
操作系統根目錄/
); - 包含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.js
和hello.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.js
、www/js/lib/log.js
、www/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語句)時的路徑前綴(路徑中最后一個"/"前的所有部分),如果沒有前綴則不進行替換。
- 如果寫成:require(['js/lib/hello.js'],則路徑的前綴為"js/lib/";
- 如果寫成: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的影響)為例進行說明。
- 本例中app.js中require語句的路徑前綴為
js/lib/
,則在hello.js中./log.js
會被替換為js/lib/log.js
; - 根據前文描述,以.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的路徑的步驟如下:
- 進行
./
替換,./log
->js/lib/log
- 拼接baseUrl,得到
js/js/lib/log
(沒有顯示指定baseUrl,則baseUrl為data-main指定的js文件所在目錄,即js
) - 拼接“主目錄”,得到
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的路徑解析步驟如下:
- 進行
./
替換(./
->lib
),得到lib/log
- 進行paths替換(
lib
->js/lib
),得到js/lib/log
- 進行baseUrl拼接,得到
./js/lib/log
- 進行“主目錄”拼接,得到
www/./js/lib/log
。最終指向www/js/lib/log.js
(非級聯處理中./
表示當前目錄,可以省略)
附加實驗
- 如果將
./log
改為./log.js
,則不會進行paths處理和baseUrl替換,則會指向www/lib/log.js
文件。 - 如果將
./log
改為log
,則不會進行./
替換,最終指向www/log.js
3. 路徑解析邏輯
結論3.1:RequireJS中路徑的處理流程如下圖所示
綜上所述,RequireJS中路徑解析過程如上圖所示。在級聯處理中會首先進行./
替換操作;然后再針對不以.js結尾的進行paths替換和baseUrl拼接;最后拼接上“主目錄”。
前文app.js的3種寫法對應的log.js的路徑解析過程如下
方式1
- 進行'./'替換,
./log
->js/lib/log
- 拼接baseUrl(baseUrl為data-main指定的app.js所在目錄),得到
js/js/lib/log
- 拼接“主目錄”,得到
www/js/js/lib/log
,最終指向www/js/js/lib/log.js
方式2
- 路徑前綴為空,不進行'./'替換;
- 拼接baseUrl,得到
js/lib/log
- 拼接“主目錄”,得到
www/js/lib/log
,最終指向www/js/lib/log.js
方式3
- 進行'./'替換,
./log
->lib/log
- 進行paths替換,得到
js/lib/log
- 拼接baseUrl(baseUrl為data-main指定的app.js所在目錄),得到
js/js/lib/log
- 拼接“主目錄”,得到
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.js
和www/js/lib/log.js
,路徑解析步驟如下:
方式3
- 沒有前綴,不進行
./
替換; -
./log
進行baseUrl處理,得到././log
; - 進行”主目錄拼接“,得到
www././log
,最終指向www/log.js
方式4
- 進行
./
替換,./log
->lib/log
; - 進行paths替換,得到
js/lib/log
; - 進行baseUrl處理,得到
./js/lib/log
; - 進行”主目錄“拼接,得到
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