標籤:子頁面 應用 二次 好處 獨立 服務 jquery html5+ 第三方類庫
近日來我有幸主導了一個典型的web app開發。該項目從產品層次來說是個典型的CRUD應用,故而我毫不猶豫地採用了grunt + boilerplate + angularjs + bootstrap + D3 + requirejs 的架構來實現它。angularjs早在去年6月份我就有所接觸,將它應用在實驗室項目的個別頁面中,11月份在新浪的時候也將其推薦給了所在雲事業部項目組。項目組老大程輝等人都是很有技術敏感性的人,大膽地採納了我的建議,將之應用於原本使用dojo開發的項目前端模組上。然而,由於前端模組很大,並且已經用dojo開發了很長時間了,故而angularjs一開始也是應用在一些頁面和子模組上,至我離開新浪之前還完成全部的重構。雖然之後我也做過一些angularjs的外掛程式之類的事,但終究沒有用angularjs完整地搞一個大型項目的經驗。故而這次實驗室交予我一整個項目,且不管所得利益幾何,對於一個對前端有愛的人而言,這是一次從頭開始完整地進行angularjs項目實戰的機會,我怎麼可能放過。期間種種我都想用部落格記錄下來,以備後用。
開發大型javascript應用和做幾個demo頁面玩一玩最大的區別在於,要對一開始的檔案組織和模組劃分要有更清晰的認識。這項工作前期花了我較多的時間去處理。故而第一篇文章就寫模組劃分和目錄組織,這方面本人經驗不多,不足之處敬請指出。
項目需求和資源評估
沒有什麼模組劃分是必須的和通用的,模組劃分都是在對項目需求、手頭資源(員工資源、時間資源和物質資源)有充分的評估,同時參考大量範型的基礎上作出決定。所以先大致介紹一下我接手的項目需求。這是一個典型的B/S架構的呈報系統軟體。要求有使用者-服務台-辦事人員-系統管理員四級的角色架構,針對不同的角色,顯示不同介面,每種介面內含子頁面大約20個不等,子頁面互相有重複。需求要求系統有足夠的穩定性,一般的並發性。前端要求有設計感,足夠美觀,快速響應,對一些特定統計內容有相應的可視化呈現。不需支援IE6。項目成型後,作為軟體安裝部署,二次開發可能性較小。人力資源方面,加上我幹活的有三個,其中前端熟練工一個(俺),有一定開發經驗的一個,編寫代碼一般可視化程式操作優良踏實肯乾的一個。時間資源大約3人*60個工作日。
根據項目需求可知,項目從使用者角色可劃分為四個大模組:使用者、服務台、辦事人員、系統管理員。大部分頁面邏輯較為單一,易於劃分。對於重複子頁面可另建立通用模組。考慮到人力方面,2/3是非熟練工,美工設計人員基本沒有(本來我是,但是現在的工作量不允許我做這個了),工程量大時間緊,故而要求將設計的工作量降到最低。使用的技術和模組劃分要盡量易上手,減少重複勞動,提高並發效率。由於前端人員較多,後台較為簡單,故而整體架構上,後台只須提供REST風格的API,將工作重心前移,前台負責主要邏輯模組的實現。
前端初始架構設計
在確認了需求和資源評估之後,本人接下來的工作是在github上遍覽使用了angularjs的各種項目,以及google相關文檔。其中github上的幾個project給我了我較大啟發,它們分別是:
- https://github.com/angular/angular-seed
- https://github.com/zhangdiwaa/angular-coffee-AMD-seed
- https://github.com/elsom25/angular-requirejs-html5boilerplate-seed
- https://github.com/mz121star/NJBlog (一個使用 Mongodb+Nodejs+express+angularjs寫的部落格程式)
- https://github.com/zhangdiwaa/ng-grid(一個基於angularjs寫的grid外掛程式)
根據這些參考內容,我設計了初始的技術路線,:
簡要說明一下:
web server使用的是node.js。同時配以自動化工具grunt做一些端到端測試、代碼壓縮和合并之類的工作。
framwork當然用的是angularjs做MVVM架構。同時用了一些UI小外掛程式,如angular-ui-bootstrap, angular-ui和我自己寫的表格外掛程式angrid(https://github.com/zhangdiwaa/anGrid)。因為表格外掛程式對於本項目來說比較重要,這裡採用自己寫的表格外掛程式主要是為了便於定製(想當年為了定製dojo的表格外掛程式改得要吐)。在模組劃分上,一開始是模仿angular-seed的方式,就用一個myApp作為程式進入點,direcvtives、filters、service作為檔案夾,存放對應的js檔案。
基礎的工具集採用jquery以及bootstrap。bootstrap這個東西的好處就在於可以快速地讓不懂設計的人也能快速開發出能看的介面,搞定不挑剔的使用者足夠了。何況國內還有很多軟體系統的介面遠不如bootstrap好看。這裡bootstrap我們沒有使用其開發包而是直接使用它的工程包,並且摒棄了less。這樣做因為考慮到需求我們並不需要對bootstrap做多少定製化處理。直接使用原始設計,將樣式設計的工作量降到最低。less這個東西,只在需要非常頻繁變更樣式設計的時候方顯本事,其他時候還不如直接用CSS。
其他工具集主要是前端資料視覺化工具包D3,D3的強大無需說明。另外把boilerplate單獨說一下是因為這東西我確實很喜歡,它是製作符合html5+CSS3規範的網頁程式的捷徑
初始架構設計的問題1:angularjs沒有諸如AMD或者CMD的機制。
當我企圖以現有方案繼續的時候,發現在從myApp劃分子模組開始就進行不下去了。問題如下:angularjs沒有諸如AMD或者CMD的機制。從myApp開始,myApp所有的依賴都必須先行載入。雖然angularjs可以使用路由按需載入模板,但是控制器卻要先行載入(如下面的代碼所示)。
$route.when(‘/view1‘, {template: ‘partials/partial1.html‘, controller: MyCtrl1});
我的項目裡有可分4個角色模組(使用者、服務台、辦事人員、系統管理員),對於一個進入點壓力已經很大了,每個模組還有20多個頁面,難道所有的控制器、以來模組都要全部載入嗎?這顯然不科學。必須按需載入。
於是我在網上搜了半天,首先找到的方法如下:
$routeProvider.when(‘/phones‘, { templateUrl: ‘partials/phone-list.html‘, controller: PhoneListCtrl, resolve: PhoneListCtrl.resolve})
function PhoneListCtrl($scope) {//本身不用管,該怎麼弄怎麼弄}
PhoneListCtrl.resolve = { delay: function($q) {var delay = $q.defer(), load = function(){ $.getScript(‘/js/xxxxx.js‘,function(){ delay.resolve();});}; load();return delay.promise;}}
這個辦法相當於使用angularjs內建的ajax方法,消極式載入了js檔案。然而此法並不好用。在每個控制函數後面都寫個消極式載入屬性方法顯得很笨,當然,你可以把它寫到prototype去或者改angularjs源碼,但那樣做很有可能是給另外兩個非熟練開發人員埋地雷。總之消極式載入controller問題多多,尤其是在顯示的時候,具體你試試就知道了。
於是我想到了require.js這個AMD工具。之所以用require.js而不是國產的seajs並非我歧視國產(事實上我上一個項目就用的是seajs),而是因為require.js文檔豐富,在github上有很多常式,適合教其他兩個開發人員使用。除此以外require.js的AMD模式跟dojo的AMD按需載入方式如出一轍,因此使用過dojo開發的我對require.js更有好感。於是我最終還是使用了require.js做AMD方式的按需載入。
更進一步地,github上還有人基於RequireJS寫了在angular路由時動態載入templete,controller,和directives的外掛程式。在模組較少較清晰的情況下,這樣已經夠用了。 https://github.com/matys84pl/angularjs-requirejs-lazy-controllers
初始架構設計的問題2:我們的程式真的只需要一個app做進入點嗎?
在angularjs的教程中,總是使用一個入口模組(通常是叫myApp)來組織整個程式。不論是angular-seed,phonecat這樣的樣本程式還是像NJBlog這樣的比較大的應用都是一個進入點。但是,我們的項目跟這些都不同,是一個比他們都大的多、複雜的多的項目。由於本項目中的角色之間功能是嚴格分離的,所以產生了四大角色模組使用者、服務台、辦事人員、系統管理員,它們之間是不會串門的。所以這4個模組完全可以用4個app做進入點來組織程式。最後再加上一個登陸模組,做判定和跳轉即可。使用requirejs最怕的問題就是依賴關係太多從而產生混亂,而這樣劃分5個入口的方式決定了每個app的依賴都不是太多,整個程式的功能邏輯一目瞭然。
雖然事實上,採用一個進入點,然後通過內部機制判斷,從而在各個模組之間跳轉也是可行的,但是那樣程式複雜度就要高的多,還要給另外兩個搭檔解釋都要解釋半天,所以還是算了吧。
初始架構設計的問題3:按模組組織檔案
一開始的時候,我的檔案目錄完全是照書抄書仿照angular-seed進行組織的。考慮到檔案較多較複雜,於是就設定了幾個direcvtives、filters、service作為檔案夾,存放對應的js檔案。於是檔案目錄看起來就像下面這個樣子:
- controllers/
- LoginController.js
- RegistrationController.js
- ProductDetailController.js
- SearchResultsController.js
- directives.js
- filters.js
- models/
- CartModel.js
- ProductModel.js
- SearchResultsModel.js
- UserModel.js
- services/
- CartService.js
- UserService.js
- ProductService.js
但是這樣真的好用嗎?
看起來檔案排列的是很整齊,但是叫我的搭檔來看,他依然不清楚這些對象的依賴關係。尤其當要重用某些模組的時候,他必須從各個檔案夾中搜集相關檔案,而且常常會遺漏某些檔案夾中的對象。
事實上,在快速開發中確實很少會在新項目中重用很多代碼(重複代碼倒有很多),但很可能需要重用登陸系統這樣的整個模組。
所以,不如按照功能模組去組織檔案夾。最終,我的目錄是這麼排列的:
- build/(工程目錄)
- css/
- img/
- js/
- appAdmin/ (獨立模組,以app名字開頭,各app模組內容近似)
- controller/ (相關的子模板的controller.js存放在這裡)
- directives/ (相關的directive.js存放在這裡)
- admin_app.js (app模型定義和路由設定檔)
- admin_main.js (requirejs的入口和設定檔)
- admin_services.js (app的相關服務組態檔)
- appCustomer/
- appHelpdesk/
- appRepairer/
- appLogin/
- common/ (通用模組庫)
- angular_filter/ (一些通用的過濾器)
- common_plugin.js (一些非基於angular的但比較重要組件,例如console plugin)
- utils/ (其他組件)
- lib/ (包含所有第三方類庫)
- templete/ (子模板檔案夾,其內容按模組類型分類)
- common/
- tplAdmin/
- tplCustomer/
- tplHelpdesk/
- tplRepairer/
- 404.html
- admin.html (admin模組的入口html)
- customer.html (customer模組的入口html)
- helpdesk.html (helpdesk模組的入口html)
- login.html (login模組的入口html)
- index.html (程式的總進入點,用以根據配置跳轉到各個模組入口)
- repairer.html (repairer模組的入口html)
- node_modules/ (grunt)
- src/ (未經grunt處理的源檔案)
- test/ (端到端測試)
- gruntfile.js (grunt)
- human.txt
- package.json (grunt)
- README.md
顯然,這裡我將js和templete裡面的檔案都按模組劃分為一個個子檔案夾,在build的根目錄下留下了數個模組進入點。這樣已經足夠清晰了。
還有更為激進的做法,就是將屬於同一個模組的templete、css、js全放在一個模組目錄下,我上一個項目就是這麼做的。但是上一個項目未能清晰地劃分通用模組和功能模組,造成了一定混亂。我暫時不好比較這兩種劃分的優劣。至少以目前的劃分,已經足夠可用了。
作者資訊:張迪,中國傳媒大學醬油男,主要研究領域為網路儲存,資料視覺效果。有3年前端開發經驗。個人愛好遊泳,世界曆史,美術插畫,電腦CG。
來源: http://community.angular.cn/A08q
angularJs項目實戰!01:模組劃分和目錄組織