前端模塊化開發方案小對比
前端采用模塊化開發,使得開發體驗大大增強,擺脫了很多需要人力去做且容易出錯的點,使得代碼管理更加清晰、規范。主要表現為以下幾點:
- 減少命名沖突,消除全局變量
- 一個模塊一個文件,組織更清晰
- 依賴自動加載,按需加載 </ol>
- CommonJs (Node.js)
- AMD (RequireJS)
- CMD (SeaJS) </ol>
- 壓縮
- 合并
- 更新版本 </ol>
- 語言擴展能力 (less, coffee, jade…)
- 前端模板預編譯
- xss自動轉義 無段手動干預
- 多域名支持,動態切換
- 編譯后自動修改版本號 (包括圖片的引用)
- 線上本地調試功能 …… </ol>
其中文件按需加載,依賴自動管理,使得更多精力去關注模塊代碼本身,開發時不需要在頁面上寫一大堆script引用,一個require初始化模塊就搞定。不需要每增加一個文件,還要到HTML或者其他地方添加一個script標簽或文件聲明。
前端模塊化規范標準
注:以下文中出現的AMD及CMD分別范指RequireJS、SeaJS。
CommonJs
CommonJS是服務器模塊的規范,Node.js采用了這個規范。根據CommonJS規范,一個單獨的文件就是一個模塊,每一個模塊都是一個單獨的作用域,在一個文件定義的變量(還包括函數和類),都是私有的,對其他文件是不可見的。CommonJS規范加載模塊是同步的,也就是說,只有加載完成,才能執行后面的操作。
var x = 5; var addX = function(value) { return value + x; };module.exports.x = x; module.exports.addX = addX;</pre>
AMD (RequireJS)
由于Node.js主要用于服務器編程,模塊文件一般都已經存在于本地硬盤,所以加載起來比較快,不用考慮非同步加載的方式,因此CommonJS規范比較適用。但是,如果是瀏覽器環境,要從服務器端加載模塊,這時就必須采用非同步模式,因此瀏覽器端一般采用AMD規范。如下規范定義及一般寫法:
//規范 define(id?, dependencies?, factory); define.amd = {}; //寫法1 define(function(require, exports, module) { var $ = require('jquery'); //code here }); //寫法2 define(['jquery'], function($) { //code here }); //寫法3 define(['require', 'jquery'], function(require) { var $ = require('jquery'); //code here });
CMD (SeaJS)
CMD規范和AMD類似,都主要運行于瀏覽器端,寫法上看起來也很類似。主要是區別在于模塊初始化時機,AMD中只要模塊作為依賴時,就會加載并初始化,而CMD中,模塊作為依賴且被引用時才會初始化,否則只會加載。如下規范定義及一般寫法:
//規范 define(factory); define.cmd = {}; //寫法1 define(function(require, exports, module) { var $ = require('jquery'); //code here }); //寫法2 define(['jquery'], function(require, exports, module) { var $ = require('jquery'); //code here });
兼容AMD、CMD及非模塊化
很多時候如果我們引用第三方組件時,并沒有采用模塊化開發,通常我們自己需要包裝一下或通過模塊加載器的shim插件支持模塊化引用依賴。現在很多第三方庫已經默認支持AMD規范的引用,根據以上模塊定義規范,開放給第三方使用的組件能兼容不同的規范,如下示例:
(function(root, factory) { if (typeof define === "function" && (define.amd || define.cmd) { define(function(require, exports, module) { return factory(root); }); } else { root.dialog = factory(root); } })(window, function(root) { //code here return dialog; });
AMD與CMD
這里不對AMD及CMD作詳細對比,前面提到一點,最大的差異在于兩者的初始化時機不一樣,這種差異導致在遇到循環引用時,CMD在某些情況下是可解的,感興趣的同學可以看下,至于執行效率上,有人專門做過測試,這里不展開說明了。
對于一般使用者來說,RequireJS、SeaJS都是不錯的選擇,對外調用API上,CMD的API設計更簡單,職能更單一,整體實現更輕量 也更傾向于CommonJS的規范寫法,提倡依賴就近聲明。前后端共享模塊時,只需要去掉define的包裝頭部就行了,雖然AMD也支持 CommonJS規范的寫法,但不是強制的。
同時對于依賴的加載順序,AMD是不保證按照書寫的順序按序初始化模塊的,而這點CMD也更接近CommonJS規范,對于使用者來說require就是同步的。
模塊化開發上線部署
不能直接壓縮:因為模塊加載器在分析模塊的依賴時,會先將模塊的工廠函數factory.toString(),然后通過正則匹配require局部變量,這樣意味著不能直接通過壓縮工具進行壓縮,若require這個變量被替換,加載器與自動化工具將無法獲取模塊的依賴。
不能直接合并:我們在開發時,通過是省略模塊的ID的,如果多個模塊直接合并成一個文件,這樣加載器無法區分不同模塊了。
所以采用模塊化開發上線部署時,壓縮前通常通過工具先提取依賴,這樣require就可以當做普通變量直接壓縮了,同時也不再需要加載器分析提取依賴,對于提升性能也是蠻有好處的。合并前同樣也需要借助工具先提取各個模塊的ID,然后才能按照合并配置進行合并。整個過程如下:
![]()
SeaJS和RequireJS官方都提供了構建工具,如SeaJS的 spm ,RequireJS的 r.js ,當然也很多grunt和glup插件可使用,區別于普通壓縮合并就是要有提取依賴及模塊ID的能力。
對比構建工具使用感受,spm的整個上手并不是那么順暢,配置太復雜。r.js使用相對簡單,只需要配置好合并規范的配置文件即可,其它grunt或glup提供的插件相對靈活,可根據自身業務靈活配置。
完成壓縮合并后,最后一步就是更新版本號上線了,通常是需要手動更新版本號,或是修改控制版本號的配置參數。這一步基本上還是需要人力手動干預一 下。當然如果合并是通過后端的combo服務就不需要了。不管怎么說,通過這些工具的組合使用,整個開發上線流程基本實現自動化了,比較方便。
FIS的集成解決方案
用過fis的同學都知道,采用fis開發,整體過程相當順暢,對于前端開發、性能、部署各方面的問題基本都考慮到了,內置的小巧mod.js加載器,就是一個特別輕量的模塊加載器,不需要做依賴分析,fis強大的編譯能力已經提前提取了依賴關系并生成jsmap.json。已經前置依賴了,一個輕量的加載器足足夠了,編譯的同時自動修改新生成的版本號,整個過程在fis下輕松完成。