本文結合最近的工作經驗,總結出一個較簡潔的前端自動化構建方案,主張css和js的模塊化,並通過grunt的自動化構建,有效地解決css合並,js合並和圖片優化等問題,對於提高前端性能和項目代碼質量有一定參考價值,歡迎閱讀和點評:)
github地址:https://github.com/liuyunzhuge/generator-web
demo地址:https://liuyunzhuge.github.io/generator-web/
有興趣的同學,在閱讀文章,學習或使用demo的過程中,有任何的疑惑或者發現的問題盡管在評論中與我討論。
1. 概述
本項目是一個腳手架,應用於前后端結合型項目,可以為這種項目提供如下服務:
- 圖片壓縮,支持png,jpg,gif格式圖片
- css模塊化,不過要求使用less編寫css
- js模塊化,要求使用requirejs定義模塊
- css壓縮合並,可做到一個頁面僅包含一個css文件
- js壓縮合並混淆,可做到一個頁面僅包含一個script標簽,僅發出2個script請求,其中一個是require.js,另一個則是頁面相關的js,依賴的js都通過requirejs的優化工具,跟頁面js合並成一個
- 以上任務全部可通過grunt自動構建,並且在開發期間,主動觸發編譯和文件更新,以便在瀏覽器中刷新能看到最新的效果。
前后端結合型項目是指javaweb,asp.net這種非前后端完全分離的項目,頁面通常是jsp,aspx,php等模板,部署時的更新包同時包含前后端的文件,屬於傳統的項目類型。
本腳手架應用場景受限於前后端結合型項目,它僅能解決這類項目的前端構建的工作,對於后端構建以及項目打包發布的工作需要由后端來完成,如果你的是javaweb類型,后端發布可以用maven,它可同時完成后端構建和打包的任務。之所以有本腳手架的產生,主要還是因為目前前后端結合型項目的開發方式還是非常常見,畢竟不是每個公司都有資源去做到完全的前后端分離這類的架構,尤其是小公司或者是項目型公司,沒有那么的人和時間等資源能讓你玩高級的東西,快速開發才是王道。但是對於任何一個web項目來說,前端部分的基礎服務都是一樣的,比如圖片優化,css和js合並壓縮等,而且這都是在團隊資源可行的情況下最好做的,對於項目質量,只有好處沒有壞處。本腳手架的產生,正是從工作中總結出來的一套開發架子,基於這個架子,你可以很方便得到由它給你帶來的以下好處:
- css和js的模塊化,有助於提供項目的代碼質量,便於將來的維護
- 圖片的優化,css和js的壓縮合並,極大地減少了請求的數據量和請求數,十分利於項目的訪問性能
- 前端任務全自動構建,不用擔心編譯和文件拷貝等之類的問題,遵循本項目的約定,你會發現css和js的開發結構是如此清晰
2. 結構
WEB-INF/ html/ img/ js/ less/ Gruntfile.js bower.json optimize.json package.json
因為本腳手架是結合demo一起發布到github上的,demo是基於javaweb的,所以你會看到里面有個WEB-INF/文件夾,這個是javaweb必須的,html/里面的頁面都是jsp模板,這個也是javaweb必須的;所以如果你想把這個腳手架應用於asp.net和php需要你刪掉WEB-INF這個文件夾,然后將html/里的jsp改成aspx或php模板。
另外,不管你想把這個腳手架用於什么類型的項目,最好的使用方式是先把demo跑起來,然后在demo的基礎上開發,這樣能夠減少出錯~
html/ 用來存放頁面的模板(jsp,aspx,php)或html文件 img/ 存放圖片 js/ 存放js less/ 存放less Gruntfile.js 這是grunt任務的配置文件 bower.json 這是bower的配置文件 optimize.json 這是requirejs的優化工具的配置文件 package.json 這是grunt依賴的配置文件
注:html/,img/,js/,less/這三個文件夾里面還有子文件夾,各自都有相關的約定,后續逐個介紹。在demo運行成功之前,那幾個配置文件請先不要改動。
2.1 DEMO
查看DEMO:https://liuyunzhuge.github.io/generator-web/ 支持IE9+,chrome,firefox。
該DEMO是gh-pages搭建,你可以通過查看本項目的gh-pages分支看到demo的文件內容,由於github提供的這種服務僅支持靜態網頁,所以demo的首頁其實是由html/index.jsp轉化過來的,另外位置也變了一下,index.jsp原來是放置在html/下面的,gh-pages分支里的index.html放在了跟html/同級的位置,不這么干的話,gh-pages就看不到效果了。
3. 安裝
由於本項目是結合javaweb一起發布的,所以以下的使用步驟均是在java的開發環境下說明的,IDE為Intellij IDEA。如果你的項目也是javaweb項目,那么推薦你用這個IDE,這是本人用過的最好的java web開發工具,集成了眾多前端工具,比如less,emmet和grunt還有bower等。如果你用它來開發項目,你會發現編寫less和使用grunt是如此順暢!純粹的后台開發可能不挑剔開發工具,但是前端開發如果要結合后台一起弄一下的話, 最好還是使用高級一點的IDE。
3.1 第一步
安裝nodejs,git,bower,grunt。windows下安裝即可,不需要linux。其中:
- nodejs,git官網都有windows的安裝包
- bower和grunt的安裝都通過命令行安裝,安裝方式參照各自官網:
- bower:http://bower.io/
- grunt:http://www.gruntjs.net/getting-started
注:這一步與IDE和后端語言沒有任何關系。不管什么語言的項目,這個都是使用本腳手架的基礎。
3.2 第二步
新建web項目,比如我用IDEA新建一個項目名為generator-web-demo,它的項目結構如下:
.idea/ src/ web/ generator-web-demo.iml
其中.iead/和generator-web.iml都是IDEA建完項目以后創建的,可以不用管。src是java源文件的目錄,web文件夾是項目的web根目錄。
3.3 第三步
在github上,download zip或者用git clone本項目,復制本項目的以下文件夾或文件粘貼到你項目的web根目錄(前一步提到的web文件夾):
html/ img/ js/ less/ WEB-INF/ bower.json Gruntfile.js optimize.json package.json
WEB-INF直接覆蓋原來的WEB-INF即可。如果原來的web/下有一個index.jsp,可以把它刪掉,我的習慣是把頁面都放一塊,html/已經提供一個index.jsp了,所以原來的index.jsp多余了。
最后你的項目結構應該如下:
.idea/ src/ web/ html/ img/ js/ less/ WEB-INF/ bower.json Gruntfile.js optimize.json package.json generator-web-demo.iml
3.4 第四步
使用bower安裝bower.json中配置的庫(jquery,iCheck,requirejs,bootstrap)
bower install --save
注:以上庫除requirejs是腳手架必須的外,其它均可根據實際項目需要進行添加和刪除,不過為了把demo先跑起來,還是別去改它,看懂了構建的原理再來根據項目需要修改也不遲。
安裝grunt和grunt插件:
npm install --save
如果npm安裝速度慢,可以按下面網址提供的方式安裝,速度會快一些: http://npm.taobao.org/
3.5 第五步
執行grunt的default任務:
grunt default
如果執行grunt這個任務報錯,一般都是grunt-contrib-imagemin插件報的錯,你可以用下面的命令重裝grunt-contrib-imagemin
npm uninstall grunt-contrib-imagemin --save npm install grunt-contrib-imagemin --save
注:以上是兩個命令,分開執行。如果還報同樣錯誤,可將以上命令多試幾次。
最后啟動你的web服務器,比如tomcat。打開瀏覽器訪問應該就能看到跟demo一致的首頁效果:)。
4. 開發
本部分介紹如何在demo的基礎上進行開發。
4.1 開發頁面
每新增一個頁面都放在html/下面,目前demo內包含:
html/ base/ body_end.jsp head_end.jsp head_start.jsp index.jsp ltIE9.html
其中:base/下的是一些公共的jsp頁面,你看下index.jsp里面的那些inclue你就明白了。 ltIE9.html是一個提示頁面,如果用戶以IE8及以下的IE瀏覽器訪問就會跳到這個頁面提示更新瀏覽器或者下其它的好用的瀏覽器。
如果你的項目是asp.net的項目,請把這些jsp都替換成aspx。
這個html/的思路是:base/放公共的,其它頁面按模塊分,如果一個模塊只有一個頁面,那么就把它直接放在html/下面,如果一個模塊有多個頁面,可以在html/以模塊名建一個文件夾,相關頁面都放那里面。
你在開發頁面的時候,建議:html/base/下的公共jsp保留,新頁面以index.jsp為參考進行開發,可以往base/內添加更多公共頁面,也可按模塊對新頁面進行分類。
4.2 baseUrl的問題
這個問題是這樣的,打比方說:
- 你如果用eclispe開發項目,啟動tomcat以后,打開瀏覽器訪問,必須以[http://主機名:端口/工程名/]的方式才能訪問,以generator-web-demo舉例,就必須用[http://localhost:8080/generator-web-demo/]才能訪問,當然這個能改,我這里是拿一般情況舉例
- 如果是IDEA開發,就不用考慮這個工程名的問題,因為它的tomcat默認配置,就是省略工程名的方式,它可以直接通過http://localhost:8080/訪問
- 這個工程名的配置就是contextpath,因為這個contextpath的存在可能導致你頁面里的css,img,和script加載時出現加載不到資源的情況,所以通常在前后端結合型項目中,一定得把這個contextpath統一起來,讓所有的資源加載都是相對同一個路徑進行解析
- 常見的處理方式就是把所有資源的url的contextpath及之前的那一段設置到html的base元素上,由於base元素的作用,可以保證腳本和css還有圖片的加載都相對於這個base的href屬性進行解析
base元素設置的代碼如下<head_start.jsp>:
<% String base = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath(); base += base.endsWith("/") ? "" : "/"; request.setAttribute("base", base); request.setAttribute("rnd", "?v=0.0.1"); %> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <base href="${base}"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!--[if lt IE 9]> <meta http-equiv="Refresh" content="0; url=${base}html/ltIE9.html"/> <![endif]-->
引用css,圖片和js時,直接使用相對web根目錄的路徑進行引用:
<link href="css/index.css" rel="stylesheet"> <img src="img/dist/logo.png" alt="LOGO"> <script data-main="js/dist/mod/index" src="js/dist/lib/require.js"></script>
還記得web根目錄的這個結構吧:
web/ img/ css/ js/
4.3 圖片
約定項目相關的圖片放在img文件夾下,img/的結構為:
img/ dist/ src/ temp/
這個目錄結構最好是不改變。其中:
- dist/存放經過grunt圖片優化任務之后的圖片,也是代碼中圖片的引用路徑
- src/存放待優化的圖片源文件
- temp/按雪碧圖存放各個雪碧圖的原圖片文件,比如你制作了一張雪碧圖,你把它命名為common.png,那么就在temp/里面,新建一個common的文件夾,把你制作雪碧圖時用到的源圖都放進去,這樣將來你需要調整雪碧圖的時候還能從這里找到源圖,雪碧圖制作推薦這個工具:http://alloyteam.github.io/gopng/ ,使用它還可以把你每次制作雪碧圖的配置導出到本地,下次要修改的時候,重新導入之前的配置,就能在上次的狀態下繼續編輯
每增加一張圖片或雪碧圖,就把它丟到img/src里面,頁面或css中引用圖片時,直接使用img/dist/這個路徑去引用即可。
4.4 less
約定css都用less來編寫,less文件夾的結構為:
less/ app/ icon/ mixin/ widget/ mod/ sprite/
其中:
- app/icon/ 存放字體圖標文件,以及字體圖標的less,比如你如果用iconfont,那么就要把下載下來的iconfont的字體文件復制到app/icon/,把iconfont.css另存成iconfont.less也存到這個文件夾
- app/mixin/存放一些定義的less mixin,demo中很多mixin是從bootstrap的源碼中提取的
- app/wifdget/存放一些公共組件的less,對於一些公共的模塊,比如等header,footer,搜索框,購物車等都可以定義成單個的模塊,哪個頁面的用到了這個模塊,import一下就好了
- sprite/存放雪碧圖相關的less,制作雪碧圖時,gopng這個工具會給你提供它生成的css,你可以把它另存為less,我的做法是把雪碧圖里每個背景圖的css都定義成了mixin,這樣html元素在用到某哥背景圖時,只要引入這個less文件,調一下mixin就可以了,具體的方式,請參考demo里面sprite/下的兩個less文件的定義方式,以及app/mod/index.less中的使用方式
- app/mod/存放頁面的less文件,基本上每個頁面一個less,也按模塊再建文件夾進行分類,跟開發新頁面一樣的道理
grunt的less任務,會把app/icon/下的字體拷貝到web/css/下,把app/mod/下的less編譯成的css也放到web/css/下,所以頁面引入css的時候,要相對css/這個文件夾引用:
<link href="css/index.css" rel="stylesheet">
在這一塊需要注意的問題是字體文件和圖片文件的路徑問題,記住less編譯后的css是放在css/下面的,而圖片優化后是放在img/dist/下面的;字體文件跟css默認都是直接位於css/目錄下的,具體請多參考demo中app/mod/index.less。
4.5 js
約定js源碼都在js/src/下定義,js/src/的結構為
js/ js/src/ app/ mod/ widget/ lib/ mod/ common.js
其中:
- common.js是requirejs的配置文件
- js/src/mod/存放各個頁面的main.js
- js/src/lib/存放依賴的js庫,比如jquery,icheck,bootstrap的transition都會通過grunt的任務,從bower下載的位置拷貝到這里,具體請看gruntfile.js的copy任務,同時這個文件夾也是requirejs的baseUrl的位置
- js/src/app/mod/存放各個頁面真正執行邏輯的js
- js/src/app/widget存放組件相關的js,比如demo中定義了四個組件,tab,carousel,icheck,radioToggle
在沒有優化之前,grunt任務會把js/src/的文件全部拷貝到js/dist/下,在優化之后,grunt任務會把js/dist/mod/下的每個js依賴的所有js,都跟它合並成為一個js。不管有沒有優化,頁面引用js文件時,都要相對js/dist這個目錄引用!
一個頁面相關的js整個加載流程,以demo中的index.js為例:
- 首先頁面中先通過:
<script data-main="js/dist/mod/index" src="js/dist/lib/require.js"></script>
加載js/dist/mod/index.js - 由於index.js的源碼為:
requirejs(['../common'], function (common) { requirejs(['app/mod/index']); });
- 它會先加載common.js讀取配置,然后再加載js/dist/app/mod/index.js執行頁面的邏輯
- 到了js/dist/app/mod/index.js后,就是加載各個依賴的模塊處理頁面邏輯的過程了,整個js的異步依賴跟加載情況就結束了。
所以開發一個js的步驟為,假設要開發一個userCenter.js:
- 首先在js/src/mod/下新建一個userCenter.js
- 在js/src/app/mod/下也新建一個userCenter.js
- 將js/src/mod下的userCenter.js的源碼改為:
requirejs(['../common'], function (common) { requirejs(['app/mod/userCenter']); });
- 因為js/src/mod/下的js都僅是一個橋梁,所以這個文件夾下的每個js都只有三行。
- 在js/src/app/mod/userCenter.js中編寫你的頁面邏輯
另外還要在optimize.json中增加一個對userCenter.js的配置項,以便生產環境構建時能把它依賴的js都跟它合並成一個js 。
4.5.1 如何配置optimize.json
簡單點來說,照着這個模板就可以了:
[ { "name": "../mod/index", "include": [ "app/mod/index" ] } ]
比如如果userCenter.js,那么就該配置成:
[ { "name": "../mod/index", "include": [ "app/mod/index" ] }, { "name": "../mod/userCenter", "include": [ "app/mod/userCenter" ] } ]
具體的原理牽扯的細節就比較多了,可參考以下兩個網址去研究一下:
5. 構建
5.1 開發環境
grunt default
使用以上任務就會啟動構建,包括:
- 優化圖片,並將優化后的圖片存放至img/dist/
- 編譯less/mod下的less,並存放至css/
- 將less/app/icon下的字體文件,拷貝到css/
- 將js/src/下所有內容,拷貝至js/dist/
- 監控img/src/,less/,js/src/內的文件變化,一有變化,自動執行相應的1,2,3
這個構建不會壓縮css,不會合並壓縮混淆js。
5.2 生產環境
grunt release
使用以上任務,完成生產環境構建:
- 在開發環境構建基礎上,進行css壓縮
- 使用requirejs的優化工具優化頁面的js,即將它所依賴的模塊全部合並成一個js,並做壓縮混淆處理
5.3 optimize
grunt optimize
這個任務可用於測試requirejs的合並是否正確,實際使用方式如下:
- 先執行
grunt default
- 再執行
grunt optimize
- 看看js/dist/mod/下的js,刷新瀏覽器測試下功能
之所以沒跟default任務合並,是因為這個優化任務比較費時,不用每次開發的時候都執行優化
6. 打包
打包前,先執行下grunt release
,並將修改提交至代碼服務器。另外bower_components和node_modules這兩個文件夾千萬別傳到代碼服務器上去,前端用的東西,打包的后台同事怎么會要你這個呢;還要記得提醒他們把以下文件夾從工程中排除出去:
img/src/ img/temp/ js/src/ less/
這些文件是沒有必要發布出去的。至於具體怎么打包,就是后台同事的責任了。
7. 計划
目前已知的未解決的問題:
- requirejs異步加載js時的緩存特別嚴重,目前開發環境可通過配置requirejs的urlArgs解決,但是生產環境時這個urlArgs要去掉,在js/src/common.js中可以去掉,下一步應該把這個做到grunt release任務中去
- 靜態資源更新的問題,比如js和css更新了,但是客戶端還有緩存
- 圖片和字體文件自動base64編碼
下一步計划就是要解決以上問題。