一直醞釀着寫一篇關於模塊化框架的文章,由於模塊化框架是前端工程中的 最爲核心的部分
。原本又想長篇大論的寫一篇完整且嚴肅的paper,但看了 @糖餅 在 DIV.IO 的一篇文章 《再談 SeaJS 與 RequireJS 的差別》以爲能夠藉着這篇繼續談一下,加上最近spm3發佈,在seajs的官網上又引來了一場 口水戰 ,我並不想參與到這場論戰中,各有所愛的事情很差評論什麼,但我想從工程的角度來闡述一下已知的模塊化框架相關的問題,並給出一些新的思路,其實也不新啦,都實踐了2多年了。javascript
前端模塊化框架肩負着
模塊管理
、資源加載
兩項重要的功能,這兩項功能與工具、性能、業務、部署等工程環節都有着很是緊密的聯繫。所以,模塊化框架的設計應該最高優先級考慮工程須要。html
基於 @糖餅 的文章 《再談 SeaJS 與 RequireJS 的差別》,我這裏還要補充一些模塊化框架在工程方面的缺點:前端
合併請求
和 按需加載
帶來了實現上的矛盾:
//AMD for SPA require(['page/index', 'page/detail'], function(index, detail){ //在執行回調以前,index和detail模塊的factory均執行過了 switch(location.hash){ case '#index': index(); break; case '#detail': detail(); break; } });
在執行回調以前,已經同時執行了index和detail模塊的factory,而CMD只有執行到require纔會調用對應模塊的factory。這種差異帶來的不只僅是性能上的差別,也可能爲開發增長一點小麻煩,好比不方便實現換膚功能,factory注意不要直接操做dom等。固然,咱們能夠多層嵌套require來解決這個問題,但又會引發模塊請求串行的問題。java
結論:以純前端方式實現模塊化框架 不能 同時知足
按需加載
,請求合併
和依賴管理
三個需求。node
致使這個問題的根本緣由是 純前端方式只能在運行時分析依賴關係
。git
因爲根本問題出在 運行時分析依賴
,所以新思路的策略很簡單:不在運行時分析依賴。這就要藉助 構建工具
作線下分析了,其基本原理就是:github
利用構建工具在線下進行
模塊依賴分析
,而後把依賴關係數據寫入到構建結果中,並調用模塊化框架的依賴關係聲明接口
,實現模塊管理、請求合併以及按需加載等功能。json
舉個例子,假設咱們有一個這樣的工程:數據結構
project ├ lib │ └ xmd.js #模塊化框架 ├ mods #模塊目錄 │ ├ a.js │ ├ b.js │ ├ c.js │ ├ d.js │ └ e.js └ index.html #入口頁面
工程中,index.html
的源碼內容爲:框架
<!doctype html> ... <script src="lib/xmd.js"></script> <!-- 模塊化框架 --> <script> //等待構建工具生成數據替換 `__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的書寫規範):
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="lib/xmd.js"></script> <!-- 模塊化框架 --> <script> //構建工具生成的依賴數據 require.config({ "deps" : { "a" : [ "b", "c" ], "b" : [ "d" ] } }); </script> <script> //用戶代碼,異步加載模塊 require.async(['a', 'e'], function(a, e){ //do something with a and e. }); </script>
模塊化框架根據依賴表加載資源,好比上述例子,入口須要加載a、e兩個模塊,查表得知完整依賴關係,配合combo服務,能夠發起一個合併後的請求:
依賴分析完成後能夠壓縮掉require關鍵字
require.config({...})
相關的數據也是能夠的。對於小項目,文件所有合併的狀況,更加不須要deps表了,只要在入口的require.async調用以前加載全部模塊化的文件,依賴關係無需額外維護請求合併
,而不用等到一級模塊加載完成才能知道後續的依賴關係。因爲採用require函數做爲依賴標記,所以若是須要變量方式require,須要額外聲明,這個時候能夠實現兼容AMD規範寫法,好比
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函數中的 deps
參數,或者factory內的require都做爲依賴聲明標記來識別,這樣工程性就比較完備了。
但無論怎樣, 線下分析始終依靠了字面量信息
,因此開發上可能會有必定的侷限性,但總的來講瑕不掩瑜。