記一次前端工程構建

需求背景

我所在的項目組主要負責公司的A產品A1模塊的界面開發。通過上半年緊鑼密鼓、加班加點地開發以後,終於在7月份在國內的L局點成功上線。當時那個激動啊,苦逼的生活終於過去了,你們都跟我high起來!但是到了下半年,因爲公司市場人員的給力表現,又在海外開拓了D局點和T局點,真是喜(yu)大(ku)普(wu)奔(lei)啊!javascript

因爲L局點的需求尚未明確,因此L局點的事情先按住不表,先說說D局點的需求。
其實,客戶的實際要求也很少,對於界面來講,無非是總體風格要與客戶現有的產品保持一致。因此最終預計的工做量就是換換主題色而已,一切都是如此的easy,你們接着high!css

簡單修改以後就給一線發了一個聯調版本。html

在一線將版本安裝完成以後,與客戶聯調過程當中(咱們的產品是要嵌入到客戶系統中,做爲客戶系統的一個模塊工做),客戶對界面提了不少意見(該局點的客戶都比較嚴(jiao)謹(zhen))。前端

目前來看,頁面的國際化文件要準備兩份(國內的L局點中文+英文,海外的D局點英文+德文),樣式要準備兩套(L局點一套,D局點一套)。若是客戶再對頁面結構有必定的要求,好比要變動頁面的佈局、元素等,那麼html也要準備兩份了。
最終分析來看,最好是能再作一套頁面出來。java

按照公司如今的策略,代碼分支只能有一個,作出來的版本不能有任何對於局點的定製,也就是說這個版本要在全部局點都能安裝使用。webpack

如今問題來了,如何在一套代碼中支持兩套頁面呢?git

方案制定

咱們知道,一旦在頁面中引用一個靜態資源,那麼這個資源的路徑就定死了,不能改動。好比,咱們的頁面上要使用到公司的logo,這個logo圖片的路徑一旦肯定,就不能再修改了,可是D局點要求將logo換成客戶的logo。angularjs

既然公司不容許在作版本的時候進行任何的定製,那麼怎麼解決這個問題,想了半天,對現有代碼目錄作一下調整,最終肯定了以下方案:github

webapp  |
        | public |
                 |  base  |
                          | js
                          | theme |
                                  | default   |
                                              | css
                                              | image 
                                              | font  
                                  | skin2     |
                 |  uiwidget
                 
                 | custom | js
                          | theme |
                                  | default   |
                                              | css
                                              | image 
                                              | font
                                  | skin2     |
                 | pageL  |  js
                          | theme |
                                  | default   |
                                              | css
                                              | image 
                                              | font
                                  | skin2     |
                 | pageD  |  js
                          | theme |
                                  | default   |
                                              | css
                                              | image 
                                              | font
                                  | skin2     |
  • public目錄 存放全部對外開放的靜態資源文件,public之外的其餘目錄外界不可訪問。
  • base目錄 主要放置與後臺的數據接口模塊,也就是公共Model層。另外還放置公共的、框架性的樣式文件以及一些工具類。
  • uiwidget目錄 放置組件庫
  • custom目錄 頁面最終的工做目錄
  • pageL目錄 L局點要求的頁面
  • pageD目錄 D局點要求的頁面

剩餘要作的:
一是在打包的時候將pageL的內容拷貝到custom目錄中,打一個L局點的ui包,再將pageD的內容拷貝到custom目錄中,打一個D局點的UI包。最終將這兩個ui包打入一個war包中。
二是新增一個腳本,在安裝完成以後,根據局點決定使用哪一個ui包,刪除無用的ui包。web

先說明一下爲什麼要這樣劃分目錄。
除了頁面變更之外,D局點的另外一個重要的需求就是要對公司產品的功能進行一些裁剪,把不須要的功能去掉。所以,不須要的接口也要通通屏蔽掉。因此,此處將全部頁面目錄都規劃到public下,方便後面接口管理。
其餘頁面的劃分,主要仍是基於以下圖方案的考慮:

對於頁面來講,後臺的接口是不會變得,因此將與後臺的交互模塊獨立出來,放進base/js目錄中,做爲公共的Model層並對頁面暴露公共的數據API。其餘還有一些公共的css、圖片等都放到base/theme目錄下,做爲基礎樣式。另外,一些公共的utils工具也能夠放到base/js目錄下。
PageL/PageD 就是根據具體頁面具體開發了。咱們組使用AngularJS進行開發,使用RequireJS進行JS模塊化管理,具體的開發內容不在本篇範圍內,按住不表。

下面就要說說具體的構建過程了。

工程構建

前端的朋友都知道,出於對頁面性能以及用戶體驗的考慮,一般要對進行靜態資源進行壓縮、合併、指定緩存策略等,具體措施網上有不少的討論,google一下能夠翻不少頁,這裏也按住不表。
咱們組採起的措施主要是將css、js文件壓縮合並,並對每一個文件進行簽名並配置永久緩存。圖片的話,主要是將圖標類的進行合併,而後經過css sprite進行分割。

在構建工程時,以前一直使用的是公司內別人基於gulp開發的的maven插件進行構建,最後由maven負責打包。每次須要修改什麼東西的時候都要求助於工具開發者,實在是有一種寄人籬下的感受。並且功能實在有限,集成到maven中又缺少足夠的靈活性,已經適應不了蓬勃的需求變化啦。
如今前端構建工具這麼多,何不本身搞呢,後面也能自由修改和管理。因而就google了一翻。

如今構建工具備不少,主流的主要有gulpgruntwebpack等,因而我選擇了grunt。沒有什麼別的緣由,就是由於grunt的logo比較酷炫。看下圖:

前面說了,咱們對grunt的需求主要就是js、css壓縮合並,靜態資源文件簽名。另外還有一點須要注意下,就是文件簽名後,文件名前面會多一串hash值,因此全部的原來引用這些資源的地方都要修改成簽名後的文件名。RequireJS的路徑配置也要修改。

看了一下grunt的插件列表,基本知足要求,惟獨沒有一個靠譜的替換文件名的插件(也有多是我沒有找到)。怎麼辦,本身動手豐衣足食。

綜合考慮了一下,決定採用以下grunt工做流。

clean:build copy  cssmin  filerev replaceRefrence mkRequirePath clean:package uglify

其中clean、copy、cssmin、filerev和uglify都有現成的grunt插件,並且除了filerev之外都是grunt團隊出品的插件,應該比較可靠。其餘的兩個,replaceRefrence和mkRequirePath,就是我本身開發的了。

replaceRefrence的想法很簡單,就是經過filerev插件生成的簽名先後的文件映射grunt.filerev.summary和正則表達式來替換目標目錄中的每一個文件中的引用。正則表達式也很簡單,就是匹配某一路徑開頭,以具體的文件後綴(.js.html.css.png等)結尾的文件路徑字符串。正則表達式的簡要形式以下:

var regExp = /(\/start\/)(\.js|\.html|\.css|\.png|\.jpg|\.gif)/g

其中start就是路徑開頭。
有了正則表達式,而後經過String.replace(regExp, function(){})將匹配到的字符串替換掉。具體樣例以下:

var fileData = fs.readFileSync(filePath, {encoding:"utf8"});
var newFileData = fileData.replce(regExp, function (str) {
    return fileMap[str] ? fileMap[str] : str;
});

注意: 這裏的fileMap是grunt.filerev.summary通過必定的處理以後的結果。

經過以上方法就能夠將html、css、js文件中引用到的其餘文件替換成簽名後的文件了。

完成上面的工做以後,就只剩一個任務了,就是如何讓RequireJS認識這些簽名後的文件?個人想法是,經過

requirejs.config({
    paths: {
        module1: "module1/js",
        module2: "module2/js"
    }
});

的方式,將簽名後的文件路徑配置進來。
思路與replaceRefrence同樣,仍是經過grunt.filerev.summary來作。具體的思路以下:
如上面的代碼所示,咱們通常都會在paths中設置某個路徑的快捷方式,即

require("module1/a.js") 等同於 module1/js/a.js

因此,咱們只須要將這些原始的路徑配置拿到以後,根據這些原始配置與grunt.filerev.summary中的具體路徑進行一次匹配處理,就能夠將grunt.filerev.summary輸出做爲RequireJS的路徑配置對象了。

具體的方法以下:
原來grunt.filerev.summary中一條記錄多是

"module1/js/a.js": "module1/js/1234567.a.js"

如今咱們知道,代碼中經過

require("module1/a")

加載的js模塊,最終要別識別成

module1/js/1234567.a.js

那咱們只須要生成這樣一條記錄:

"module1/a": "module1/js/1234567.a.js"

具體作法就是用原始路徑配置中的kv對中的v匹配grunt.filerev.summary每條記錄中的key,若是key以v開頭,則將key作以下處理:

key = k + key.substring(v.length);

這樣就生成了正確的路徑配置了。
最後一步要作的就是將生成的路徑配置保存爲一個manifest.js文件,插入到RequireJS的下方,如:

<script type="text/javascript" src="path1/require.js"></script>
<script type="text/javascript" src="path2/manifest-2015-11-15.js"></script>

到這裏,整個構建過程就基本OK了。

相關文章
相關標籤/搜索