記一次前端工程構建


需求背景

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

由於L局點的需求還沒有明確,所以L局點的事情先按住不表,先說說D局點的需求。
其實,客戶的實際要求也不多,對於界面來說,無非是整體風格要與客戶現有的產品保持一致。所以最終預計的工作量就是換換主題色而已,一切都是如此的easy,大家接着high!

簡單修改之后就給一線發了一個聯調版本。

在一線將版本安裝完成之后,與客戶聯調過程中(我們的產品是要嵌入到客戶系統中,作為客戶系統的一個模塊工作),客戶對界面提了很多意見(該局點的客戶都比較嚴(jiao)謹(zhen))。

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

按照公司現在的策略,代碼分支只能有一個,做出來的版本不能有任何對於局點的定制,也就是說這個版本要在所有局點都能安裝使用。

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

方案制定

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

既然公司不允許在做版本的時候進行任何的定制,那么怎么解決這個問題,想了半天,對現有代碼目錄做一下調整,最終確定了如下方案:

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包。

先說明一下為何要這樣划分目錄。
除了頁面變動以外,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了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM