目錄
本文最先發(fā)表在?DIV.IO?- 高質量前端社區(qū),歡迎大家圍觀
不要再求驗證碼了,這個blog目前有800+人訂閱,求驗證沒什么的很影響其他訂閱者,可以在div.io上申請,定期會有同學發(fā)放的。。。
一直醞釀著寫一篇關于模塊化框架的文章,因為模塊化框架是前端工程中的?最為核心的部分
?。本來又想長篇大論的寫一篇完整且嚴肅的paper,但看了?@糖餅?在?DIV.IO?的一篇文章 《再談 SeaJS 與 RequireJS 的差異》覺得可以借著這篇繼續(xù)談一下,加上最近spm3發(fā)布,在seajs的官網(wǎng)上又引來了一場?口水戰(zhàn)?,我并不想?yún)⑴c到這場論戰(zhàn)中,各有所愛的事情不好評論什么,但我想從工程的角度來闡述一下已知的模塊化框架相關的問題,并給出一些新的思路,其實也不新啦,都實踐了2多年了。
前端模塊化框架肩負著?
模塊管理
、資源加載
?兩項重要的功能,這兩項功能與工具、性能、業(yè)務、部署等工程環(huán)節(jié)都有著非常緊密的聯(lián)系。因此,模塊化框架的設計應該最高優(yōu)先級考慮工程需要。
基于?@糖餅?的文章 《再談 SeaJS 與 RequireJS 的差異》,我這里還要補充一些模塊化框架在工程方面的缺點:
合并請求
?和?按需加載
?帶來了實現(xiàn)上的矛盾:
AMD規(guī)范在執(zhí)行callback的時候,要初始化所有依賴的模塊,而CMD只有執(zhí)行到require的時候才初始化模塊。所以用AMD實現(xiàn)某種if-else邏輯分支加載不同的模塊的時候,就會比較麻煩了??紤]這種情況:
//AMD for SPA
require(['page/index', 'page/detail'], function(index, detail){
//在執(zhí)行回調之前,index和detail模塊的factory均執(zhí)行過了
switch(location.hash){
case '#index':
index();
break;
case '#detail':
detail();
break;
}
});
在執(zhí)行回調之前,已經(jīng)同時執(zhí)行了index和detail模塊的factory,而CMD只有執(zhí)行到require才會調用對應模塊的factory。這種差別帶來的不僅僅是性能上的差異,也可能為開發(fā)增加一點小麻煩,比如不方便實現(xiàn)換膚功能,factory注意不要直接操作dom等。當然,我們可以多層嵌套require來解決這個問題,但又會引起模塊請求串行的問題。
結論:以純前端方式實現(xiàn)模塊化框架?不能?同時滿足?
按需加載
,請求合并
?和?依賴管理
?三個需求。
導致這個問題的根本原因是?純前端方式只能在運行時分析依賴關系
。
由于根本問題出在?運行時分析依賴
,因此新思路的策略很簡單:不在運行時分析依賴。這就要借助?構建工具
做線下分析了,其基本原理就是:
利用構建工具在線下進行?
模塊依賴分析
,然后把依賴關系數(shù)據(jù)寫入到構建結果中,并調用模塊化框架的?依賴關系聲明接口
?,實現(xiàn)模塊管理、請求合并以及按需加載等功能。
舉個例子,假設我們有一個這樣的工程:
project
├ lib
│ └ xmd.js #模塊化框架
├ mods #模塊目錄
│ ├ a.js
│ ├ b.js
│ ├ c.js
│ ├ d.js
│ └ e.js
└ index.html #入口頁面
工程中,index.html
?的源碼內容為:
<!doctype html>
...
<script src="https://atts.w3cschool.cn/attachments/image/cimg/xmd.js"></script> <!-- 模塊化框架 -->
<script>
//等待構建工具生成數(shù)據(jù)替換 `__FRAMEWORK_CONFIG__' 變量
require.config(__FRAMEWORK_CONFIG__);
</script>
<script>
//用戶代碼,異步加載模塊
require.async(['a', 'e'], function(a, e){
//do something with a and e.
});
</script>
...
工程中,mods/a.js
?的源碼內容為(采用類似CMD的書寫規(guī)范):
define('a', function(require, exports, module){
console.log('a.init');
var b = require('b');
var c = require('c');
exports.run = function(){
//do something with b and c.
console.log('a.run');
};
});
用工具在下線對工程文件進行掃描,得到依賴關系表:
{
"a" : [ "b", "c" ],
"b" : [ "d" ]
}
工具把依賴表構建到頁面或者腳本中,并調用模塊化框架的配置接口,index.html
的構建結果為:
<!doctype html>
...
<script src="https://atts.w3cschool.cn/attachments/image/cimg/xmd.js"></script> <!-- 模塊化框架 -->
<script>
//構建工具生成的依賴數(shù)據(jù)
require.config({
"deps" : {
"a" : [ "b", "c" ],
"b" : [ "d" ]
}
});
</script>
<script>
//用戶代碼,異步加載模塊
require.async(['a', 'e'], function(a, e){
//do something with a and e.
});
</script>
模塊化框架根據(jù)依賴表加載資源,比如上述例子,入口需要加載a、e兩個模塊,查表得知完整依賴關系,配合combo服務,可以發(fā)起一個合并后的請求:
依賴分析完成后可以壓縮掉require關鍵字
require.config({...})
?相關的數(shù)據(jù)也是可以的。對于小項目,文件全部合并的情況,更加不需要deps表了,只要在入口的require.async調用之前加載所有模塊化的文件,依賴關系無需額外維護請求合并
,而不用等到一級模塊加載完成才能知道后續(xù)的依賴關系。由于采用require函數(shù)作為依賴標記,因此如果需要變量方式require,需要額外聲明,這個時候可以實現(xiàn)兼容AMD規(guī)范寫法,比如
define('a', ['b', 'c'], function(require, exports, module){
console.log('a.init');
var name = isIE ? 'b' : 'c';
var mod = require(name);
exports.run = function(){
//do something with mod.
console.log('a.run');
};
})
只要工具把define函數(shù)中的?deps
?參數(shù),或者factory內的require都作為依賴聲明標記來識別,這樣工程性就比較完備了。
但不管怎樣,?線下分析始終依靠了字面量信息
,所以開發(fā)上可能會有一定的局限性,但總的來說瑕不掩瑜。
希望本文能為前端模塊化框架的作者帶來一些新的思路。沒有必要爭論規(guī)范,工程問題才是最根本的問題。
更多建議: