下圖是一個單頁面多模塊的工程目錄結構圖:css
. ├── Gruntfile.js ├── package.json ├── build └── src ├── base │ ├── base.sass │ └── global.js ├── mods │ ├── preference │ │ ├── index.js │ │ ├── index.sass │ │ └── index.xtpl.html │ ├── promo │ ├── qr │ └── response └── index.js
咱們把源碼放在 src 文件夾裏面,公共的文件(iconfont 、sprite 圖片、CSS 和 JS 等)放到 base 目錄下,頁面中的每一個模塊都會在 mods 下新建一個文件夾,使用 index.js
來管理模塊的渲染。html
// index.js define(function(require){ var Lazyload = require('lazyload'); var Preference = require('./mods/preference/index'); var Qr = require('./mods/qr/index'); var Promo = require('./mods/promo/index'); var Response = require('./mods/response/index'); new Response(); if(xxx){ new Promo(); } Lazyload(function(){ new Qr(); new Preference(); }); });
這樣的工程結構是十分通用,結構也比較清晰的,不過在模塊的管理上,這裏會存在兩個問題:前端
其實說到底仍是模塊的耦合度太高,只要模塊之間存在交集,一個模塊的改動就可能會影響到其餘模塊。多人開發中,這裏還存在其餘方面的問題:git
index.js
中註釋了這個模塊的初始化。日積月累,冗餘代碼便會滲入到項目的各個地方…去耦合的方式就是讓模塊之間共用的東西減小,當模塊之間不存在共用內容時,耦合度基本就是零了。編程
. ├── init.js ├── build └── src ├── preference <git> │ ├── index.js │ ├── index.sass │ └── index.xtpl.html ├── promo <git> ├── qr <git> └── response <git>
如上圖所示,與以前的結構相比,已經少了不少東西:json
index.js
初始化模塊的東西不見了,多了一個 init.js
base
目錄不見了先看看 init.js
在幹啥:緩存
// init.js var $mods = $("[tb-mods]"); $mods.each(functon($mod){ if($mod.attr("finish") !== FINISH_TAG) { $mod.attr("finish", FINISH_TAG); // 須要懶加載便懶加載 if($mod.attr("lazyload")){ Lazyload($mod); return; } // 不然直接初始化 S.use($mod.attr("path"), function(S, Mod){ new Mod($mod); }); } }); function Lazyload(){ // code here.. }
init.js
再也不對模塊進行精確初始化,文檔從上往下遍歷,找到模塊便直接初始化,若是須要懶加載就加入到懶加載隊列,開發者不用理會頁面上有多少模塊,更不用理會各個模塊叫作什麼名字。sass
index.js
中 require 不少不少模塊,每次添加一個模塊或者刪除模塊都要改動這個文件,而是用init.js
不會存在這個問題。架構
<!-- index.xtpl.html --> <div tb-mods lazyload path="tb/promo/1.0.0"></div> <div tb-mods lazyload path="tb/qr/2.0.0"></div> <div tb-mods lazyload path="tb/preference/2.2.1"></div> <div tb-mods path="tb/response/3.0.2"></div>
頁面上的 DOM 就是標識,存在 DOM 屬性標識就執行這個標識對應的腳本,執行順序就是 DOM 的擺放順序。less
每一個模塊代碼都使用單個 git 倉庫管理,這樣可以更好地追蹤單個模塊的修改記錄和版本,也能夠解決上面提出的問題(依次修改 ABC 模塊,並上線了三次,若是須要回滾 A 模塊,則 BC 模塊的修改也要跟着滾回去)。
修改一個模塊後,只須要修改他在 DOM 的版本號便可上線。若是遇到 ABTest 的需求,那也十分好辦了:
<!-- index.xtpl.html --> {{#if condition}} <div tb-mods lazyload path="tb/promo/1.0.0"></div> {{else}} <div tb-mods path="tb/promo/2.0.0"></div> {{/if}} <div tb-mods lazyload path="tb/qr/2.0.0"></div> <div tb-mods path="tb/response/3.0.2"></div>
tb/promo
目前有兩個版本,1.0.0 和 2.0.0,需求是兩個版本以 50% 的機率出現,直接在index.xtpl.html
作如上修改,程序是十分清晰的。
那麼,公共的代碼跑哪裏去了?其實咱們並不但願有公共的代碼產生,上一節中已經提出了耦合給咱們帶來的維護問題,可是一個項目中必然會有大量可複用的東西,尤爲是當頁面出現不少類似模塊的時候。
1)模塊的複用
一個模塊的渲染,須要兩樣東西,渲染殼子(模板) + 數據
,渲染的殼子多是同樣的,只是數據源不同,不少狀況下咱們能夠複用一套 CSS 和 JS 代碼,經過下面的方式:
<!-- index.xtpl.html --> <div tb-mods lazyload path="tb/promo/1.0.0" source="data/st/json/v2"></div> <div tb-mods lazyload path="tb/promo/1.0.0" source="data/wt/json/v1"></div>
在兩個類似模塊中,咱們使用的是同一套 js - tb/promo/1.0.0
,可是使用了兩個不一樣的數據源data/st/json/v2
, data/wt/json/v1
。
// init.js $mods.each(functon($mod){ if($mod.attr("finish") !== FINISH_TAG) { //... S.use($mod.attr("path"), function(S, Mod){ // 將數據源傳入 new Mod($mod, $mod.attr("source")); }); //... } });
在初始化腳本中,咱們將模塊須要用到的數據源傳入到模塊初始化程序中,這樣頁面就成功的複用了tb/promo/1.0.0
的資源。
2)CSS 的複用問題使用 less 的 mixin 處理
@a: red; @b: white; .s1(){ color: @a; background: @b; } .s2 { color: @a; background: @b; }
LESS 是 CSS 的預處理語言,上面的代碼打包以後,.s1
是不存在的,只有 .s2
會被打包出來,可是二者均可以 mixin 到其餘類中:
.s { .s1; .s2; }
利用這個特色,咱們能夠把共用的 css 都包裝成相似 .s1
的 less 代碼,模塊中須要的時候就 mixin,不須要的話,放在那裏也不要緊,不會形成代碼冗餘。
3)JavaScript 的代碼複用問題
頁面級別的 JS 代碼其實並很少,好比咱們平時用的比較頻繁的有 Slide、Lazyload、Tab、Storage 等,但這些東西都是以組件的形式引入到頁面中。仔細想想,JS 中哪些代碼是須要頁面共用的?相對整個項目的文件大小,共用的部分又有多少?
咱們使用的基礎庫方法並不全面,好比:沒有對 URL 解析的 unparam
方法,而這個方法用的也比較多,但願放到公共部分中去。回頭想一想,這樣的小函數實現起來有啥難度麼,三四行代碼就能寫出來的東西,建議放到組件內部搞定。這會形成必定的代碼冗餘,可是帶來的解耦收益與費力寫幾行代碼的成本相比,這徹底是能夠接受的。
頁面共用的統計代碼、錯誤收集代碼、數據緩存方案、組件通信代碼等,這些量比較大、使用頗爲頻繁的內容,能夠封裝成組件,以組件形式引入進來。
這裏還須要不少思考…
模塊之間的通信最讓人糾結的是,A 模塊想跟 B 模塊說話,可是 B 模塊尚未初始化出來。因此咱們須要引入一箇中間人 S,每一個模塊初始化成功以後都去問一問 。
// B 給 A 留言,若是 A 存在,則直接將 msg 發給 A // 若是不存在則送入 S 的消息隊列 S.tell("A", { from : "B", msg: {} }); // A 模塊初始化的時候,獲取其餘模塊的留言 S.getMessage("A", function(msg){ // dosomething... });
還有不少東西不在主題的討論範圍內,就不一一列舉出來了。
項目開發參與的人越多,代碼就越難維護,約束只是一時的,編程方式、編碼格式等的約束並不能從根本上解決問題,一旦約束的點未覆蓋,結構就會開始散亂,最後必然又會迎來一次總體的重構。
方法和結果不能改變習慣,因此咱們應該從模式出發。