簡單介紹下modJS
近期把團隊的項目構建遷移到fis3,同時把模塊加載器也由之前的requirejs切換到了modJS。
有些同學可能不了解modJS,這里做個簡單的介紹。
那么,modJS是什么呢?
簡單的說,modJS是百度fex-team提供的一個輕量級的模塊加載器,類似requirejs。
但modJS并不完全兼容規范amd/cmd,事實上,只支持非常簡單的全局方法define(id,factory)。
另外factory提供了3個參數require/exports/module,用于引用和導出模塊。
modJS源碼只有200行左右,相比之下requirejs的源碼達到了2000+行。
除了體量非常小之外,modJS配合fis3的fis3-hook-commonjs插件,可以在純前端項目中實現類似nodejs一樣的開發體驗。
那么,我們為什么要使用modJS呢?
有以下幾點:
- 加載器更小
上面已經提到,200+行和2000+行的差異
- 配合構建工具,開發體驗更好
之前開發時,需要將每一個模塊的代碼單獨放在define內部,并且需要申明每一個依賴。 而現在,只需要使用類似nodejs的方式編寫代碼,需要使用某個依賴模塊時,直接require('id')即可。 發布編譯時,由構建工具統一添加define包裹,自動添加模塊id(默認根據路徑生成,也可以在fis3的配置中聲明格式)。整體的開發體驗更好一些。
此外,js文件打包及異步依賴的問題,也可以通過生成resourceMap來解決。
一般情況下,modJS配合fis3已經可以滿足大部分需求,并且官方還提供了完整支持amd規范的esl-mod.js。
如果不夠用,也可以在modJS的基礎上定制一些功能來滿足需求,因為源碼只有200行,代碼也很清晰簡單。
我們的項目在切換到modJS時,由于項目比較復雜(2000+文件),一些特殊的需求最終也是通過定制modJS得到解決,這里分享幾點:
遇到哪些問題
- 同步(直接require)的依賴需要打包,異步依賴使用require.async加載
requirejs將所有依賴模塊在define方法的參數中聲明,所以可以保證先異步加載需要的模塊,最后再執行factory。
而modJS設計之初,便考慮到稍微大型點的前端項目都會打包模塊js減少請求優化性能,依賴的模塊其實早已合并打包,并不需要在define中聲明后再異步加載。
所以需要將所有異步的模塊以require.async方法來加載。
以我們的項目為例,首次加載時,會加載3個打包的js文件,分別是基礎庫(modjs、jquery、badjs)、base.js(其他打包的通用模塊,初始化一些變量)、mod.main.js(當前頁面用到的邏輯打包)。 當用戶點擊其他頁面(非刷新)時,再異步加載該頁面用到的mod.main.js(其他頁面打包合并后的js),這部分js的模塊id和url由構建工具統一打到resourceMap中。
- 不支持直接引用遠程url,包括同步和異步方式都不支持。
由于歷史原因,項目中存在一些如下形式的代碼:
define(id,['http://a','http://b'],function(a,b){ a.xxx(b) ... })
通過腳本統一替換為commonJS規范后,舊代碼變成了這樣:
var a = require('http://a'); var b = require('http://b'); a.xxx(b) ...
這種同步方式的代碼目前沒什么好的解決辦法,最后重構代碼解決。
異步的方式稍微好一些,可以通過構建將遠程url打入到resourceMap。
- 異步加載方法require.async([deps],callback)中的callback觸發
modJS加載異步模塊時,將callback回調加入一個隊列,然后將依賴模塊的script標簽打入html的head。 但是callback并不是通過script的onload事件觸發,而是通過依賴模塊的define方法觸發。
當我們只是想用require.async異步加載一個非amd規范的腳本時(例如一些第三方的組件、jquery插件等),由于該腳本并沒有被define方法包裹,所以無法觸發回調的邏輯。
雖然理論上不應該有這個問題,一個項目的模塊化方案應該統一,每一個模塊都應該使用define包裹,但現實就這樣。
- 同時異步加載多個模塊時,腳本的加載順序問題
modJS異步加載時,會將多個script腳本同時插入到head,并不會維護腳本的加載順序。
這個問題,怎么說,理論上模塊的依賴關系都應該由加載器來管理,而不應該出現腳本的加載依賴問題。但現實就這樣。
當然,如果你愿意的話也可以將源碼改成這樣的形式:
require.async(moduleA,function(){ require.async(moduleB,function(){ ...do something ...囧 ...do something }); });