最近兩年前端圈子猶如春秋戰國:羣雄並起,中原未定,就連各路大神也紛紛感嘆最近兩年技術選型難作。javascript
在模塊化開發的問題上,一方面以AMD/CMD爲表明的規範在過去幾年間極大地提高了前端生產力。另外一方面,隨着ES六、Web Components的臨近,開發者們面臨着承前啓後的巨大挑戰。css
對前端而言,模塊化並不像後端語言那樣簡單,它涉及到不少工程問題與歷史包袱,讓人很難保持清晰的思路。在此記錄下本身對於模塊化的一些學習與認識,但願能進一步理清思路。html
要談模塊化,首先要知道模塊化的意義,開門見山地說:前端
不管任何語言任何項目,上述兩個方面的意義均可以認爲是模塊化的核心價值。java
在前端項目中常用到的如jQuery、underscore.js等庫,其實就能夠看做是公共模塊,他們對經常使用的、工具性的代碼提供了抽象。node
我曾接觸過一個老舊的項目,在N個js文件的頂部都寫了一份對Function.prototype.bind的polyfill,看得出來出自不一樣人之手:由於實現方式不同。毫無疑問,這樣的代碼徹底不具有可維護性,若是你不提個公共文件出來,就只能在用這幾個js以前沐浴薰香,祈禱他們不要出bug。webpack
把公共代碼封裝成模塊促進了代碼的複用,但在不少時候,爲了知足高內聚低耦合,咱們也須要將並不具有複用價值的代碼抽離成相互獨立的模塊,以此來提高可維護性。有不少關於函數最好不要超過XX行,文件不要超過XXX行這樣的「經驗」,其實就是在督促人們多作有意義的代碼拆分。git
總結一下:程序員
深刻到前端組件上,@民工精髓V 的 2015前端組件化框架之路 有很是精彩的講解,這裏就不贅述了。github
前端的模塊化,因爲如下幾個方面的難點,始終和工程化的命題牢牢耦合:
下面一條條細說
與後端項目代碼便是一切
不一樣,前端因爲面向展示層,在資源上呈現高度的複雜性,一個典型的前端組件可能包含html、css、js、img、swf 等資源中的一種或多種。最多見的狀況就是js與css並存
這給資源的管理、聚合帶來了極大的難度。
假設咱們有tooltip
組件,用僞代碼描述理想的狀況:
include tooltip
而事實上的狀況大概是這個樣子的
<link rel="stylesheet" href="./path/to/tooltip.css"/> <!-- 省略 --> <script type="text/javascript" src="./path/to/tooltip.js"></script>
簡直像是洪荒時代的作法……很不幸的事實是,這在目前的前端業內其實很廣泛。
固然也有難以忍受這種情況的前輩,作出了不少嘗試和努力。最多見的思路是把資源依賴都交由js打理,由js做爲模塊的入口:
//login.js require("./login.css"); //usage require("./path/to/login.js");
至於怎麼解析這種依賴並轉化成瀏覽器能識別的輸出,方法各類各樣。但共同點不變:以js爲入口,把資源的依賴關係管理並聚合起來。
其實這個辦法不錯,對資源的靜態分析處理加上js在瀏覽器端的配合,理應能走出一條康莊大道來。
可是,咱們遇到了要說的第二個問題:
不管是W3C的標準,仍是事實上的標準,有標準總好過沒標準。
前面說了,對資源作一遍預分析和處理,再輔以瀏覽器端js寫的各類loader
,理應能很好的應對前端資源的複雜性。
但隨之而來的大坑就是標準問題,前述的例子login是一個業務模塊,業務模塊有個好處,就是代碼限定在本項目內,怎麼寫,用什麼標準均可以掌控。但若是咱們有個公共模塊tooltip,仍是按前述的方式引入:
//usage require("tooltip");
若是是nodejs,我天然知道應該去node_modules
下面找/tooltip/package.json
。
但在前端模塊化上,連一個普遍承認的包管理器都沒有,已知的有components規範、有spm的sea_modules、有bower的bower_components,還有不少跑在瀏覽器端的框架和庫在臉上寫着nodejs的npm上託管。
公共模塊放哪兒?怎麼放?你們各玩一套。
這裏又引出了另外的問題:對資源作一遍預分析和處理
這在目前是屬於工程化層面的問題,爲了解析方便,現有的工程化方案每每只適配有限的包管理體系,例如基於F.I.S的Scrat以components做爲組件規範。
spm則比較特別,它做爲seajs的包管理體系,爲本身量身定作了一套工程化的方案。
可是,不管是工程化適應包體系仍是包體系衍生工程化,相互之間這種不優雅而又不得已的強相關總歸是提升了各自的使用門檻和將來風險。
js//tooltip.js //組件本身聲明對css的依賴 require("../css/tooltip.css");
在這樣背景下,上面這種更優雅的代碼是不可能出如今公共模塊中的,由於沒有相關的規範,沒法應對不一樣工程化方案的不一樣解讀,好比有的可能須要下面這樣寫:
js//tooltip.js require("css!../css/tooltip.css");
對於公共模塊來講,爲了保證通用性,最好的辦法是:隨便大家吧,老子不玩了。
恩,換個文明點的說法,就是迴歸原始狀態,js歸js,css歸css,想引用?本身伸手來拿,因而在spm、webpack中,代碼是這樣的
//引入模塊入口 require("tooltip") //引用模塊下的其它資源 require("tooltip/css/tooltip.css")
這麼作固然很差,依賴模塊內部的路徑明顯不符合開閉原則
。若是哪天模塊目錄結構或者文件名變動,等待你的只能是報錯。但這倒是目前爲止不得已而爲之的辦法,別無其它選擇。
爲何模塊的依賴不能在執行期解析,非要在工程化層面作?除了對多種資源的處理以外,瀏覽器環境的異步性也是很是重要的因素。
先簡化問題:不考慮多種資源,僅考慮javascript。當我執行
//my_module.js define(function(require){ require("moduleA"); //do something });
時,若是是Node.js(固然Node.js不會有define這一段),只要去磁盤上讀文件就行了,整個過程是同步的,可是在瀏覽器端,moduleA的代碼可能還在服務器上,須要先去下載,再回來執行代碼,整個過程變成了異步。
以require.js爲例,看看發生了什麼:
require(xxx)
,發現依賴項seajs大同小異,主要的差異在於執行依賴的時機。
上述過程,歸納就是:加載--分析--加載--分析--執行。在成功加載文件以前,它的依賴是沒法肯定的。但這樣不只用戶會付出大量的等待時間,並且咱們發現,在運行時分析依賴是一種浪費,由於依賴關係在開發期就已經肯定了
。
生而與重複勞動爲敵的程序員,是絕對不能容忍千千萬萬臺用戶電腦浪費資源幹這種事的。
爲了協調異步環境下模塊開發與性能間的矛盾,咱們必須在工程階段就具有依賴分析的能力,把具有依賴關係的資源進行打包。就算不打包,也但願像 F.I.S 那樣有個記錄依賴關係的map.json,能夠照單抓藥,一次性地把須要的依賴項加載下來。
所以,在開發期解析模塊依賴、合併代碼、甚至重寫資源路徑都得在工程化的層面完成。工程化和模塊化變成了容易耦合且不得不耦合的兩個話題。
這也使得咱們在制定模塊化或工程方案時,必須兩頭兼顧,全面考量。拋開模塊談工程、拋開工程談模塊,都是耍流氓。
to be continue……