近期总结:generator-web,前端自动化构建的解决方案
本文結(jié)合最近的工作經(jīng)驗(yàn),總結(jié)出一個(gè)較簡(jiǎn)潔的前端自動(dòng)化構(gòu)建方案,主張css和js的模塊化,并通過grunt的自動(dòng)化構(gòu)建,有效地解決css合并,js合并和圖片優(yōu)化等問題,對(duì)于提高前端性能和項(xiàng)目代碼質(zhì)量有一定參考價(jià)值,歡迎閱讀和點(diǎn)評(píng):)
github地址:https://github.com/liuyunzhuge/generator-web
demo地址:https://liuyunzhuge.github.io/generator-web/
有興趣的同學(xué),在閱讀文章,學(xué)習(xí)或使用demo的過程中,有任何的疑惑或者發(fā)現(xiàn)的問題盡管在評(píng)論中與我討論。
1. 概述
本項(xiàng)目是一個(gè)腳手架,應(yīng)用于前后端結(jié)合型項(xiàng)目,可以為這種項(xiàng)目提供如下服務(wù):
- 圖片壓縮,支持png,jpg,gif格式圖片
- css模塊化,不過要求使用less編寫css
- js模塊化,要求使用requirejs定義模塊
- css壓縮合并,可做到一個(gè)頁(yè)面僅包含一個(gè)css文件
- js壓縮合并混淆,可做到一個(gè)頁(yè)面僅包含一個(gè)script標(biāo)簽,僅發(fā)出2個(gè)script請(qǐng)求,其中一個(gè)是require.js,另一個(gè)則是頁(yè)面相關(guān)的js,依賴的js都通過requirejs的優(yōu)化工具,跟頁(yè)面js合并成一個(gè)
- 以上任務(wù)全部可通過grunt自動(dòng)構(gòu)建,并且在開發(fā)期間,主動(dòng)觸發(fā)編譯和文件更新,以便在瀏覽器中刷新能看到最新的效果。
前后端結(jié)合型項(xiàng)目是指javaweb,asp.net這種非前后端完全分離的項(xiàng)目,頁(yè)面通常是jsp,aspx,php等模板,部署時(shí)的更新包同時(shí)包含前后端的文件,屬于傳統(tǒng)的項(xiàng)目類型。
本腳手架應(yīng)用場(chǎng)景受限于前后端結(jié)合型項(xiàng)目,它僅能解決這類項(xiàng)目的前端構(gòu)建的工作,對(duì)于后端構(gòu)建以及項(xiàng)目打包發(fā)布的工作需要由后端來完成,如果你的是javaweb類型,后端發(fā)布可以用maven,它可同時(shí)完成后端構(gòu)建和打包的任務(wù)。之所以有本腳手架的產(chǎn)生,主要還是因?yàn)槟壳扒昂蠖私Y(jié)合型項(xiàng)目的開發(fā)方式還是非常常見,畢竟不是每個(gè)公司都有資源去做到完全的前后端分離這類的架構(gòu),尤其是小公司或者是項(xiàng)目型公司,沒有那么的人和時(shí)間等資源能讓你玩高級(jí)的東西,快速開發(fā)才是王道。但是對(duì)于任何一個(gè)web項(xiàng)目來說,前端部分的基礎(chǔ)服務(wù)都是一樣的,比如圖片優(yōu)化,css和js合并壓縮等,而且這都是在團(tuán)隊(duì)資源可行的情況下最好做的,對(duì)于項(xiàng)目質(zhì)量,只有好處沒有壞處。本腳手架的產(chǎn)生,正是從工作中總結(jié)出來的一套開發(fā)架子,基于這個(gè)架子,你可以很方便得到由它給你帶來的以下好處:
- css和js的模塊化,有助于提供項(xiàng)目的代碼質(zhì)量,便于將來的維護(hù)
- 圖片的優(yōu)化,css和js的壓縮合并,極大地減少了請(qǐng)求的數(shù)據(jù)量和請(qǐng)求數(shù),十分利于項(xiàng)目的訪問性能
- 前端任務(wù)全自動(dòng)構(gòu)建,不用擔(dān)心編譯和文件拷貝等之類的問題,遵循本項(xiàng)目的約定,你會(huì)發(fā)現(xiàn)css和js的開發(fā)結(jié)構(gòu)是如此清晰
2. 結(jié)構(gòu)
WEB-INF/ html/ img/ js/ less/ Gruntfile.js bower.json optimize.json package.json因?yàn)楸灸_手架是結(jié)合demo一起發(fā)布到github上的,demo是基于javaweb的,所以你會(huì)看到里面有個(gè)WEB-INF/文件夾,這個(gè)是javaweb必須的,html/里面的頁(yè)面都是jsp模板,這個(gè)也是javaweb必須的;所以如果你想把這個(gè)腳手架應(yīng)用于asp.net和php需要你刪掉WEB-INF這個(gè)文件夾,然后將html/里的jsp改成aspx或php模板。
另外,不管你想把這個(gè)腳手架用于什么類型的項(xiàng)目,最好的使用方式是先把demo跑起來,然后在demo的基礎(chǔ)上開發(fā),這樣能夠減少出錯(cuò)~
html/ 用來存放頁(yè)面的模板(jsp,aspx,php)或html文件 img/ 存放圖片 js/ 存放js less/ 存放less Gruntfile.js 這是grunt任務(wù)的配置文件 bower.json 這是bower的配置文件 optimize.json 這是requirejs的優(yōu)化工具的配置文件 package.json 這是grunt依賴的配置文件注:html/,img/,js/,less/這三個(gè)文件夾里面還有子文件夾,各自都有相關(guān)的約定,后續(xù)逐個(gè)介紹。在demo運(yùn)行成功之前,那幾個(gè)配置文件請(qǐng)先不要改動(dòng)。
2.1 DEMO
查看DEMO:https://liuyunzhuge.github.io/generator-web/ 支持IE9+,chrome,firefox。
該DEMO是gh-pages搭建,你可以通過查看本項(xiàng)目的gh-pages分支看到demo的文件內(nèi)容,由于github提供的這種服務(wù)僅支持靜態(tài)網(wǎng)頁(yè),所以demo的首頁(yè)其實(shí)是由html/index.jsp轉(zhuǎn)化過來的,另外位置也變了一下,index.jsp原來是放置在html/下面的,gh-pages分支里的index.html放在了跟html/同級(jí)的位置,不這么干的話,gh-pages就看不到效果了。
3. 安裝
由于本項(xiàng)目是結(jié)合javaweb一起發(fā)布的,所以以下的使用步驟均是在java的開發(fā)環(huán)境下說明的,IDE為Intellij IDEA。如果你的項(xiàng)目也是javaweb項(xiàng)目,那么推薦你用這個(gè)IDE,這是本人用過的最好的java web開發(fā)工具,集成了眾多前端工具,比如less,emmet和grunt還有bower等。如果你用它來開發(fā)項(xiàng)目,你會(huì)發(fā)現(xiàn)編寫less和使用grunt是如此順暢!純粹的后臺(tái)開發(fā)可能不挑剔開發(fā)工具,但是前端開發(fā)如果要結(jié)合后臺(tái)一起弄一下的話, 最好還是使用高級(jí)一點(diǎn)的IDE。
3.1 第一步
安裝nodejs,git,bower,grunt。windows下安裝即可,不需要linux。其中:
- nodejs,git官網(wǎng)都有windows的安裝包
- bower和grunt的安裝都通過命令行安裝,安裝方式參照各自官網(wǎng):
- bower:http://bower.io/
- grunt:http://www.gruntjs.net/getting-started
注:這一步與IDE和后端語(yǔ)言沒有任何關(guān)系。不管什么語(yǔ)言的項(xiàng)目,這個(gè)都是使用本腳手架的基礎(chǔ)。
3.2 第二步
新建web項(xiàng)目,比如我用IDEA新建一個(gè)項(xiàng)目名為generator-web-demo,它的項(xiàng)目結(jié)構(gòu)如下:
.idea/ src/ web/ generator-web-demo.iml其中.iead/和generator-web.iml都是IDEA建完項(xiàng)目以后創(chuàng)建的,可以不用管。src是java源文件的目錄,web文件夾是項(xiàng)目的web根目錄。
3.3 第三步
在github上,download zip或者用git clone本項(xiàng)目,復(fù)制本項(xiàng)目的以下文件夾或文件粘貼到你項(xiàng)目的web根目錄(前一步提到的web文件夾):
html/ img/ js/ less/ WEB-INF/ bower.json Gruntfile.js optimize.json package.jsonWEB-INF直接覆蓋原來的WEB-INF即可。如果原來的web/下有一個(gè)index.jsp,可以把它刪掉,我的習(xí)慣是把頁(yè)面都放一塊,html/已經(jīng)提供一個(gè)index.jsp了,所以原來的index.jsp多余了。
最后你的項(xiàng)目結(jié)構(gòu)應(yīng)該如下:
.idea/ src/ web/html/img/js/less/WEB-INF/bower.jsonGruntfile.jsoptimize.jsonpackage.json generator-web-demo.iml3.4 第四步
使用bower安裝bower.json中配置的庫(kù)(jquery,iCheck,requirejs,bootstrap)
bower install --save注:以上庫(kù)除requirejs是腳手架必須的外,其它均可根據(jù)實(shí)際項(xiàng)目需要進(jìn)行添加和刪除,不過為了把demo先跑起來,還是別去改它,看懂了構(gòu)建的原理再來根據(jù)項(xiàng)目需要修改也不遲。
安裝grunt和grunt插件:
npm install --save如果npm安裝速度慢,可以按下面網(wǎng)址提供的方式安裝,速度會(huì)快一些: http://npm.taobao.org/
3.5 第五步
執(zhí)行g(shù)runt的default任務(wù):
grunt default如果執(zhí)行g(shù)runt這個(gè)任務(wù)報(bào)錯(cuò),一般都是grunt-contrib-imagemin插件報(bào)的錯(cuò),你可以用下面的命令重裝grunt-contrib-imagemin
npm uninstall grunt-contrib-imagemin --save npm install grunt-contrib-imagemin --save注:以上是兩個(gè)命令,分開執(zhí)行。如果還報(bào)同樣錯(cuò)誤,可將以上命令多試幾次。
最后啟動(dòng)你的web服務(wù)器,比如tomcat。打開瀏覽器訪問應(yīng)該就能看到跟demo一致的首頁(yè)效果:)。
4. 開發(fā)
本部分介紹如何在demo的基礎(chǔ)上進(jìn)行開發(fā)。
4.1 開發(fā)頁(yè)面
每新增一個(gè)頁(yè)面都放在html/下面,目前demo內(nèi)包含:
html/base/body_end.jsphead_end.jsphead_start.jspindex.jspltIE9.html其中:base/下的是一些公共的jsp頁(yè)面,你看下index.jsp里面的那些inclue你就明白了。 ltIE9.html是一個(gè)提示頁(yè)面,如果用戶以IE8及以下的IE瀏覽器訪問就會(huì)跳到這個(gè)頁(yè)面提示更新瀏覽器或者下其它的好用的瀏覽器。
如果你的項(xiàng)目是asp.net的項(xiàng)目,請(qǐng)把這些jsp都替換成aspx。
這個(gè)html/的思路是:base/放公共的,其它頁(yè)面按模塊分,如果一個(gè)模塊只有一個(gè)頁(yè)面,那么就把它直接放在html/下面,如果一個(gè)模塊有多個(gè)頁(yè)面,可以在html/以模塊名建一個(gè)文件夾,相關(guān)頁(yè)面都放那里面。
你在開發(fā)頁(yè)面的時(shí)候,建議:html/base/下的公共jsp保留,新頁(yè)面以index.jsp為參考進(jìn)行開發(fā),可以往base/內(nèi)添加更多公共頁(yè)面,也可按模塊對(duì)新頁(yè)面進(jìn)行分類。
4.2 baseUrl的問題
這個(gè)問題是這樣的,打比方說:
- 你如果用eclispe開發(fā)項(xiàng)目,啟動(dòng)tomcat以后,打開瀏覽器訪問,必須以[http://主機(jī)名:端口/工程名/]的方式才能訪問,以generator-web-demo舉例,就必須用[http://localhost:8080/generator-web-demo/]才能訪問,當(dāng)然這個(gè)能改,我這里是拿一般情況舉例
- 如果是IDEA開發(fā),就不用考慮這個(gè)工程名的問題,因?yàn)樗膖omcat默認(rèn)配置,就是省略工程名的方式,它可以直接通過http://localhost:8080/訪問
- 這個(gè)工程名的配置就是contextpath,因?yàn)檫@個(gè)contextpath的存在可能導(dǎo)致你頁(yè)面里的css,img,和script加載時(shí)出現(xiàn)加載不到資源的情況,所以通常在前后端結(jié)合型項(xiàng)目中,一定得把這個(gè)contextpath統(tǒng)一起來,讓所有的資源加載都是相對(duì)同一個(gè)路徑進(jìn)行解析
- 常見的處理方式就是把所有資源的url的contextpath及之前的那一段設(shè)置到html的base元素上,由于base元素的作用,可以保證腳本和css還有圖片的加載都相對(duì)于這個(gè)base的href屬性進(jìn)行解析
base元素設(shè)置的代碼如下<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時(shí),直接使用相對(duì)web根目錄的路徑進(jìn)行引用:
<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根目錄的這個(gè)結(jié)構(gòu)吧:
web/ img/ css/ js/4.3 圖片
約定項(xiàng)目相關(guān)的圖片放在img文件夾下,img/的結(jié)構(gòu)為:
img/dist/src/temp/這個(gè)目錄結(jié)構(gòu)最好是不改變。其中:
- dist/存放經(jīng)過grunt圖片優(yōu)化任務(wù)之后的圖片,也是代碼中圖片的引用路徑
- src/存放待優(yōu)化的圖片源文件
- temp/按雪碧圖存放各個(gè)雪碧圖的原圖片文件,比如你制作了一張雪碧圖,你把它命名為common.png,那么就在temp/里面,新建一個(gè)common的文件夾,把你制作雪碧圖時(shí)用到的源圖都放進(jìn)去,這樣將來你需要調(diào)整雪碧圖的時(shí)候還能從這里找到源圖,雪碧圖制作推薦這個(gè)工具:http://alloyteam.github.io/gopng/ ,使用它還可以把你每次制作雪碧圖的配置導(dǎo)出到本地,下次要修改的時(shí)候,重新導(dǎo)入之前的配置,就能在上次的狀態(tài)下繼續(xù)編輯
每增加一張圖片或雪碧圖,就把它丟到img/src里面,頁(yè)面或css中引用圖片時(shí),直接使用img/dist/這個(gè)路徑去引用即可。
4.4 less
約定css都用less來編寫,less文件夾的結(jié)構(gòu)為:
less/app/icon/mixin/widget/mod/sprite/其中:
- app/icon/ 存放字體圖標(biāo)文件,以及字體圖標(biāo)的less,比如你如果用iconfont,那么就要把下載下來的iconfont的字體文件復(fù)制到app/icon/,把iconfont.css另存成iconfont.less也存到這個(gè)文件夾
- app/mixin/存放一些定義的less mixin,demo中很多mixin是從bootstrap的源碼中提取的
- app/wifdget/存放一些公共組件的less,對(duì)于一些公共的模塊,比如等header,footer,搜索框,購(gòu)物車等都可以定義成單個(gè)的模塊,哪個(gè)頁(yè)面的用到了這個(gè)模塊,import一下就好了
- sprite/存放雪碧圖相關(guān)的less,制作雪碧圖時(shí),gopng這個(gè)工具會(huì)給你提供它生成的css,你可以把它另存為less,我的做法是把雪碧圖里每個(gè)背景圖的css都定義成了mixin,這樣html元素在用到某哥背景圖時(shí),只要引入這個(gè)less文件,調(diào)一下mixin就可以了,具體的方式,請(qǐng)參考demo里面sprite/下的兩個(gè)less文件的定義方式,以及app/mod/index.less中的使用方式
- app/mod/存放頁(yè)面的less文件,基本上每個(gè)頁(yè)面一個(gè)less,也按模塊再建文件夾進(jìn)行分類,跟開發(fā)新頁(yè)面一樣的道理
grunt的less任務(wù),會(huì)把a(bǔ)pp/icon/下的字體拷貝到web/css/下,把a(bǔ)pp/mod/下的less編譯成的css也放到web/css/下,所以頁(yè)面引入css的時(shí)候,要相對(duì)css/這個(gè)文件夾引用:
<link href="css/index.css" rel="stylesheet">在這一塊需要注意的問題是字體文件和圖片文件的路徑問題,記住less編譯后的css是放在css/下面的,而圖片優(yōu)化后是放在img/dist/下面的;字體文件跟css默認(rèn)都是直接位于css/目錄下的,具體請(qǐng)多參考demo中app/mod/index.less。
4.5 js
約定js源碼都在js/src/下定義,js/src/的結(jié)構(gòu)為
js/js/src/app/mod/widget/lib/mod/common.js其中:
- common.js是requirejs的配置文件
- js/src/mod/存放各個(gè)頁(yè)面的main.js
- js/src/lib/存放依賴的js庫(kù),比如jquery,icheck,bootstrap的transition都會(huì)通過grunt的任務(wù),從bower下載的位置拷貝到這里,具體請(qǐng)看gruntfile.js的copy任務(wù),同時(shí)這個(gè)文件夾也是requirejs的baseUrl的位置
- js/src/app/mod/存放各個(gè)頁(yè)面真正執(zhí)行邏輯的js
- js/src/app/widget存放組件相關(guān)的js,比如demo中定義了四個(gè)組件,tab,carousel,icheck,radioToggle
在沒有優(yōu)化之前,grunt任務(wù)會(huì)把js/src/的文件全部拷貝到j(luò)s/dist/下,在優(yōu)化之后,grunt任務(wù)會(huì)把js/dist/mod/下的每個(gè)js依賴的所有js,都跟它合并成為一個(gè)js。不管有沒有優(yōu)化,頁(yè)面引用js文件時(shí),都要相對(duì)js/dist這個(gè)目錄引用!
一個(gè)頁(yè)面相關(guān)的js整個(gè)加載流程,以demo中的index.js為例:
- 首先頁(yè)面中先通過:<script data-main="js/dist/mod/index" src="js/dist/lib/require.js"></script>加載js/dist/mod/index.js
- 由于index.js的源碼為:
- 它會(huì)先加載common.js讀取配置,然后再加載js/dist/app/mod/index.js執(zhí)行頁(yè)面的邏輯
- 到了js/dist/app/mod/index.js后,就是加載各個(gè)依賴的模塊處理頁(yè)面邏輯的過程了,整個(gè)js的異步依賴跟加載情況就結(jié)束了。
所以開發(fā)一個(gè)js的步驟為,假設(shè)要開發(fā)一個(gè)userCenter.js:
- 首先在js/src/mod/下新建一個(gè)userCenter.js
- 在js/src/app/mod/下也新建一個(gè)userCenter.js
- 將js/src/mod下的userCenter.js的源碼改為:
- 因?yàn)閖s/src/mod/下的js都僅是一個(gè)橋梁,所以這個(gè)文件夾下的每個(gè)js都只有三行。
- 在js/src/app/mod/userCenter.js中編寫你的頁(yè)面邏輯
另外還要在optimize.json中增加一個(gè)對(duì)userCenter.js的配置項(xiàng),以便生產(chǎn)環(huán)境構(gòu)建時(shí)能把它依賴的js都跟它合并成一個(gè)js 。
4.5.1 如何配置optimize.json
簡(jiǎn)單點(diǎn)來說,照著這個(gè)模板就可以了:
[{"name": "../mod/index","include": ["app/mod/index"]} ]比如如果userCenter.js,那么就該配置成:
[{"name": "../mod/index","include": ["app/mod/index"]},{"name": "../mod/userCenter","include": ["app/mod/userCenter"]} ]具體的原理牽扯的細(xì)節(jié)就比較多了,可參考以下兩個(gè)網(wǎng)址去研究一下:
- http://www.requirejs.cn/docs/optimization.html
- https://github.com/requirejs/example-multipage
5. 構(gòu)建
5.1 開發(fā)環(huán)境
grunt default使用以上任務(wù)就會(huì)啟動(dòng)構(gòu)建,包括:
這個(gè)構(gòu)建不會(huì)壓縮css,不會(huì)合并壓縮混淆js。
5.2 生產(chǎn)環(huán)境
grunt release使用以上任務(wù),完成生產(chǎn)環(huán)境構(gòu)建:
5.3 optimize
grunt optimize這個(gè)任務(wù)可用于測(cè)試requirejs的合并是否正確,實(shí)際使用方式如下:
- 先執(zhí)行g(shù)runt default
- 再執(zhí)行g(shù)runt optimize
- 看看js/dist/mod/下的js,刷新瀏覽器測(cè)試下功能
之所以沒跟default任務(wù)合并,是因?yàn)檫@個(gè)優(yōu)化任務(wù)比較費(fèi)時(shí),不用每次開發(fā)的時(shí)候都執(zhí)行優(yōu)化
6. 打包
打包前,先執(zhí)行下grunt release,并將修改提交至代碼服務(wù)器。另外bower_components和node_modules這兩個(gè)文件夾千萬別傳到代碼服務(wù)器上去,前端用的東西,打包的后臺(tái)同事怎么會(huì)要你這個(gè)呢;還要記得提醒他們把以下文件夾從工程中排除出去:
img/src/ img/temp/ js/src/ less/這些文件是沒有必要發(fā)布出去的。至于具體怎么打包,就是后臺(tái)同事的責(zé)任了。
7. 計(jì)劃
目前已知的未解決的問題:
- requirejs異步加載js時(shí)的緩存特別嚴(yán)重,目前開發(fā)環(huán)境可通過配置requirejs的urlArgs解決,但是生產(chǎn)環(huán)境時(shí)這個(gè)urlArgs要去掉,在js/src/common.js中可以去掉,下一步應(yīng)該把這個(gè)做到grunt release任務(wù)中去
- 靜態(tài)資源更新的問題,比如js和css更新了,但是客戶端還有緩存
- 圖片和字體文件自動(dòng)base64編碼
下一步計(jì)劃就是要解決以上問題。
總結(jié)
以上是生活随笔為你收集整理的近期总结:generator-web,前端自动化构建的解决方案的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习笔记四-信息收集
- 下一篇: I.MX6 GPS JNI HAL re