經(jīng)常收到 seajs.use
某具名模塊時(shí)發(fā)現(xiàn)其引用為 null 的問題,或是移動(dòng)了文件位置導(dǎo)致引用為 null
或者 object is not function
的問題。比如這個(gè) #954 ,這個(gè) #888 ,這個(gè) #879 ,這個(gè) #739 ,這個(gè) #696 ,還有這個(gè) seajs/examples#12 。
這些問題都指向 Sea.js 的一個(gè)基本約定原則:ID 和路徑匹配原則
。
所謂 ID 和路徑匹配原則
是指,使用 seajs.use
或 require
進(jìn)行引用的文件,如果是具名模塊(即定義了 ID 的模塊),會(huì)把 ID 和 seajs.use
的路徑名進(jìn)行匹配,如果一致,則正確執(zhí)行模塊返回結(jié)果。反之,則返回 null
。例如:
seajs.use('lib/jquery', function($) { // use $});
或者在模塊中 require :
define(function(require, exports, module) { var $ = require('lib/jquery'); // use $});
當(dāng) jQuery 文件是下面的情況時(shí),上述的變量 $
能拿到正確的返回結(jié)果。
// 文件路徑是 lib/jquery.js// ID 和實(shí)際路徑匹配了(.js 后綴會(huì)自動(dòng)補(bǔ)上)define('lib/jquery', function(require, exports, module) { // jquery code});
下面的代碼則返回 null:
// 文件路徑是 lib/jquery.js// 但是 ID 是 lib/jquery.min.js// ID 和路徑不匹配define('lib/jquery.min', function(require, exports, module) { // jquery code});
而匿名模塊始終能正確返回結(jié)果:
// lib/jquery.js// 匿名模塊,不需要進(jìn)行匹配// 但是文件中只能有一個(gè) define 塊define(function(require, exports, module) { // jquery code});
注意這里用于匹配的 ID 都是經(jīng)過 alias 和 path 解析并且補(bǔ)完后綴之后的。
回答這個(gè)問題前,請(qǐng)先閱讀這篇文章:#426 。
首先,Sea.js 的模塊啟動(dòng)接口秉承的是路徑即 ID 的設(shè)計(jì)原則。seajs.use
的方法的第一個(gè)參數(shù)被規(guī)定為文件路徑(而不是 ID),這樣的設(shè)計(jì)減輕了記憶模塊 ID 的負(fù)擔(dān),無論是匿名模塊還是具名模塊,開發(fā)者只需要知道文件放在哪兒就行了。
進(jìn)一步的,之所以有這個(gè) ID 和路徑匹配原則
,是因?yàn)樵?CMD 的書寫規(guī)范中,一個(gè)文件對(duì)應(yīng)一個(gè)模塊,所有的模塊都是匿名模塊(即 define(factory)
的形式)。那么當(dāng) seajs.use
某模塊時(shí),這個(gè)模塊對(duì)應(yīng)的文件里的唯一的 define 方法理所當(dāng)然的是這個(gè)模塊的執(zhí)行代碼,這時(shí)可以正確返回結(jié)果。
但是在生產(chǎn)環(huán)境下,靜態(tài)文件不可避免地需要進(jìn)行合并打包或者進(jìn)行 combo,以優(yōu)化請(qǐng)求數(shù)提高頁面性能。這時(shí),一個(gè) js 文件可能有很多 define()
方法。
define(funtion(require, exports, module) { // module a});
define(funtion(require, exports, module) { // module b});
define(funtion(require, exports, module) { // module c});
那么請(qǐng)問,當(dāng) seajs.use
這個(gè)文件時(shí),應(yīng)該返回哪個(gè)模塊?
所以這時(shí)候 ID 就派上了用場(chǎng),我們可以這樣寫:
// path/a.jsdefine('path/a', funtion(require, exports, module) { // module a});
define('path/b', funtion(require, exports, module) { // module b});
define('path/c', funtion(require, exports, module) { // module c});
我們定義好每個(gè)模塊的 id ,在 Sea.js 里,那個(gè)和文件路徑匹配的 ID 的模塊就是這個(gè)文件的主模塊。此時(shí):
seajs.use('path/a', function(a) { // got a, not b or c});
這個(gè)原則保證了我們能夠自由合并模塊來優(yōu)化性能,seajs-combo 和 spm-build 的構(gòu)建機(jī)制都是基于此原則。
在 RequireJS 中,也有類似的原則:http://requirejs.org/docs/errors.html#mismatch
可能有人要問為啥一定要把 ID 定為文件路徑,Sea.js 不是可以自定義 ID 嗎,像下面這樣:
define('module-id', funtion(require, exports, module) { // module id});// 然后就可以seajs.use('module-id', function(Module) { // Module});
上面的代碼當(dāng)然可以運(yùn)行。但是有一點(diǎn),任何一個(gè)模塊的運(yùn)行都涉及到兩個(gè)步驟:模塊定義
和 模塊執(zhí)行
,上面的代碼兩個(gè)步驟都包括在內(nèi)。而使用了 Sea.js ,我們不希望用戶去手動(dòng)寫 script
標(biāo)簽引用模塊。希望只需要 seajs.use
模塊的文件路徑即可(入口唯一):
seajs.use('path/to/module', function(Module) { // Module});
Sea.js 會(huì)自動(dòng)插入 script 標(biāo)簽,完成定義步驟,然后執(zhí)行模塊,拿到模塊的輸出。所以當(dāng)一個(gè)文件里有多個(gè) define 時(shí),只能用 ID 是否匹配 use 中的路徑來判斷是否主模塊。
當(dāng)然可以回避掉這個(gè)原則,你只需要自己負(fù)責(zé)模塊的定義部分,再自己 seajs.use
之前定義好的模塊 ID 就行。
<!-- 各種模塊的定義 define define define --><script src="http://example.com/modules.js"></script><script>// 這時(shí) use 的第一個(gè)參數(shù)就可以不必是文件路徑了,因?yàn)橐呀?jīng)有定義好的模塊 ID 了seajs.use('jquery', function($) { // $});</script>
或者通過 alias 來幫助 ID 匹配上最終的路徑,這樣就和 RequireJS 的方案基本一致了。
// lib/jquery-1.7.2.js 的內(nèi)容如下define('$', funtion(require, exports, module) { // jQuery});
這樣就不需要自己去引用上面的文件,可以直接通過 seajs.use 調(diào)用。
seajs.config({
alias: {
$: 'lib/jquery-1.7.2.js'
}
});
seajs.use('$', function() { // Got $ !});
我們推薦使用配套的構(gòu)建工具來打包模塊。
在 spm-build 中,所有的匿名模塊通過標(biāo)準(zhǔn)的 transport 流程,會(huì)打包成具有實(shí)際 ID 的具名模塊,而主模塊(在 package.json 中指定輸出的文件)的 ID 和實(shí)際路徑是匹配的,符合ID 和路徑匹配原則
。
如果沒有使用官方工具,你需要在自己的打包和部署過程中保證這個(gè)原則。
實(shí)際上在版本 1.3.1
之前,有一個(gè)特性叫做 firstModuleInPackage
,即當(dāng)一個(gè)文件里有多個(gè) define 時(shí),默認(rèn)將第一個(gè) define 里的模塊作為主模塊進(jìn)行返回。由于各種原因我們?nèi)サ袅诉@個(gè)特性,可以參見:#438。
ID 和路徑匹配原則
是 Sea.js 實(shí)現(xiàn)中的一個(gè)約定,這個(gè)約定幫助我們減少了對(duì) ID 的記憶負(fù)擔(dān),同時(shí)增加了構(gòu)建的復(fù)雜度。
同樣的,這也是一把雙刃劍,目前還沒有『完美』的處理方案,都會(huì)在某些地方存在取舍和權(quán)衡。如果這方面你有好的想法,歡迎與我們交流。
更多建議: