本文來自 “簡時空”:《【Yeoman】熱部署web前端開發環境》(自動同步導入到博客園)
1、序言
記得去年的暑假看RequireJS的時候,曾少不更事般地驚為前端利器,寫了《Speed up! 提速你的網站訪問速度[壓縮JS與CSS]》。隨着學習的深入,發現前端的還有許多東西需要整合,純手工勞動無疑降低了開發效率。這四天的工作,真是把這兩年所學習到的知識綜合應用了一番:
軟實力層面包括:使用Photoshop+Bootstrap3+Grid System 設計頁面UI圖;
工具語言包括:CoffeeScript、LESS、Handlebars等;
圖形庫的使用:Highchart、jvectorMap、D3等這些一年前就開始用的庫
>>>>> 頁面效果:http://www.janscon.com/weibo/index.html <<<<
當然重點不在於這個,這次學習的重點在於使用Yeoman熱部署了前端環境,使用Grunt、NPM、Bower等工具 起“穿針引線”作用將這些技術互相聯系起來,使得前端的開發從未如此“一氣呵成”~
既然這些工具都把重要的工作做了,那么作為程序猿的我意義何在呢?
呃~ OK,“我們不生產代碼,我們只是英文字母的搬運工而已啦”
這幾天的工作還是留下遺憾的,就是沒能用上前端自動化測試工具——以后得好好學習Qunit+Mocha+ Selenium這些玩意兒了
2、工作准備
如果讀者對Grunt、Yeoman還不是很了解,建議先參看這幾篇文章,非常適合入門:
① Xianjing.《Grunt - 基於任務的Javascript構建工具》. 2013-05-16
② RIA之家. 《前端項目可以更簡單—Yeoman入門指南》.2013-4-25
③ 阮一峰. 《任務管理工具Grunt》.
上面這三篇文章已經將Yeoman、Grunt等語法講解非常明了了,所以我的文章里就不在這些方面多費口舌。這里將只重點講解我工作的流程,作文以記之。
找到目標:如何使用Yeoman搭建單頁面、多頁面的開發環境
使用工具:Bootstrap(基於LESS)、Handlebars、CoffeeScript,使用RequireJS組織JS代碼
示例代碼:
本文所講的程序代碼可以從這兒下載:
① 單頁面前端環境搭建示例代碼:jscon-single-page.zip (百度雲盤)
② 多頁面前端環境搭建示例代碼:jscon-multi-pages.zip (百度雲盤)
3、構建單頁面開發環境
3.1、使用Bootstrap-less生成器
使用Yeoman入門的時候,使用的webapp這個生成器,不過里面的Bootstrap是基於SASS的。個人傾向於使用LESS語言的,畢竟它是基於我熟悉的Node環境而非Ruby。
Step 1: 安裝Bootstrap-less生成器
npm install generator-bootstrap-less
Step 2: 生成程序腳手架
yo bootstrap-less
Step 3: 代碼熱部署
grunt server
看到這個自動跳出來的 “Allo,“Allo! 頁面,說明已經成功搭建環境了。可以開始在這個基礎上編寫代碼,只是我還有使用Handlebars以及RequireJS,所以還得自己安裝這些組件。
在繼續之前,在這里順便對比一下webapp與bootstrap-less這兩個腳手架的區別:
如果除去我想要的Bootstrap之外,bootstrap-less生成器是一無所有啊(Bootstrap的JS文件和FontAwesome都勾上吧,因為都要用到的),而webapp還有RequireJS和Modernizr呢,顯然是“高富帥”一枚。
不過我還是選bootstrap-less,因為它使用的是LESS而不是SASS(我難道有強迫症?);至於RequireJS和Modernizr的使用可以借鑒webapp生成的index.html中寫法即可——我就是這么干的!
3.2、引入Handlebars
引入Handlebars是看中了它使用方便且能夠預編譯這兩優點的。一般使用bower工具引入所需要的包,不過Handlebar是個例外,這是因為官方Github並不提供現成的前端頁面的Handlebar.js文件,需要通過其文檔主頁到Amazon的S3平台(http://handlebarsjs.com/)上下載;所以不要使用bower install handlebar.js命令
在模板預編譯的時候是需要借助Node環境,所以使用npm安裝Handlebars插件:
Step 1: 使用npm,下載contrib模塊
npm install --save-dev grunt-contrib-handlebars
Step 2: 同時在Gruntfile.js中注冊下面的Task:
(在Gruntfile.js文件中修改)
handlebars:{ dist: { options: { namespace: "JST", wrapped:true }, // files: {"<%= yeoman.app %>/hbs/templates.js":["<%= yeoman.app %>/hbs/*.hb"]} expand:true, src:"<%= yeoman.app %>/hbs/*.hb", ext:".js" } }
默認的namespace是“Handlebars.templates”,后期使用uglify.js進行優化的時候會將Handlebars用變量“a”(或者其他名字)代替,從而提示該變量沒有templates屬性;因此推薦像上面那樣使用“JST”等作為命名空間。
Step 3: 還需要配置編譯及livereload功能
首先在watch任務中添加對.hb文件的監視即可
(在Gruntfile.js文件中修改)
watch: { handlebars:{ files:['<%= yeoman.app %>/hbs/{,*/}*.hb'], tasks:['handlebars'] }, …. livereload: { files: [ '<%= yeoman.app %>/*.html', '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css', '{.tmp,<%= yeoman.app %>}/{scripts,hbs}/{,*/}*.js', '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' ], tasks: ['livereload'] } },
Step 4: 注冊預編譯任務
在concurrent任務中注冊handlebars任務,確保項目發布的時候所有模板都經過編譯:
(在Gruntfile.js文件中修改)
concurrent: { dist: [ 'handlebars', 'coffee', 'recess', 'imagemin', 'svgmin', 'htmlmin' ] }
配置完后,這里就簡單的舉個例子表明如何使用:
Step 1:創建模板
創建hbs文件夾,並在其下面新建一個messages.hb文件,此時文件夾結構如下:
messages.hb內容如下:
{{#messages}} <div class="message"> <h2 class="name">{{name}}</h2> <div class="msgContent">{{msgContent}}</div> <div class="msgTime">{{msgTime}}</div> </div> {{/messages}}
Step 2:添加JS代碼
先在index.html頁面中添加ID為“list”的空白DIV:
<div id="list"></div>
再在index.html文件中添加runtime.js官方文件,以及messages.js文件(注意不是messages.hb文件,熱部署的時候會自動調用Node將其編譯成messages.js文件):
(在app/index.html文件中修改)
<!-- build:js scripts/main.js --> <script src="bower_components/jquery/jquery.js"></script> <script src="scripts/lib/handlebars/handlebars.runtime-v1.1.2.js"></script> <script type="text/javascript" src="hbs/messages.js"></script> <script src="scripts/main.js"></script> <!-- endbuild -->
其中的main.js是邏輯實現代碼:
app/scripts/main.js
$(function(){ var data = { messages: [ {name:"Zhang",msgContent:"I'm San",msgTime:"Yesterday"}, {name:"Li",msgContent:"I'm Si",msgTime:"Today"}, {name:"Wang",msgContent:"I'm Wu",msgTime:"Tomorrow"} ], name:"jscon" }; var template = JST["app/hbs/messages.hb"]; console.log(template) $("#list").html(template(data)); });
這里下划線標出的:
var template = JST["app/hbs/messages.hb"];
需要注意兩個地方,一個是命名空間“JST”要與配置文件中保持一致;另外一個當調用模板的時候注意路徑是相對app的路徑。至於如何去掉“app/hbs”這個路徑,目前還不知道如何解決。
Step 3:查看效果
在程序根目錄下運行:
grunt server
發現還是之前的頁面,調出chrome console會提示找不到“messages.js”文件;好吧下面見證奇跡的時刻,打開messages.hb文件,直接按下“Ctrl+S”保存文件,觸發watch任務,其中就包括執行handlebars任務(其他的還有coffee、recess任務),然后自動執行livereload任務刷新頁面。現在看看效果頁面:
參考文章:
[1] 官方Github文檔《grunt-contrib-handlebars》
[2] 官方Grunt文檔:https://npmjs.org/package/grunt-contrib-handlebars
3.3、使用RequireJS組織JS文件
我們看看現在的index.html頁面的截圖:
可以看到這么簡單的頁面里面有一大串的JS文件需要加載,主要包括:
1) 個人的JS文件,比如上節講的模板文件(message.js)和邏輯文件(main.js)。
2) 官方的JS庫文件,比如jQuery、還有許多BootStrap需要的組件JS文件;
通過RequireJS組織JS文件,到時就只用一句話就夠了:
<!-- build:js scripts/main.js --> <script data-main="scripts/main" src="bower_components/requirejs/require.js"></script> <!-- endbuild -->
Step 1:安裝RequireJS包
bower install --save requirejs
執行此語句之后,就會自動更新bower.json文件,同時在app/bower_components中下載官方的requirejs組件。
Step 2:添加RequireJS的Node模塊
npm install grunt-contrib-requirejs --save-dev
這樣能夠將grunt-contrib-requirejs組件自動下載到node_modules文件夾下,同時因為使用了--save-dev會自動更新package.json文件。
官方文檔:https://github.com/gruntjs/grunt-contrib-requirejs
Step 3:修改Gruntfils.js文件,配置requirejs任務
為了能夠利用RequireJS的r.js文件對輸出文件進行優化,需要在Gruntfiles.js中注冊相應任務。關於r.js的詳細配置可以參考官方示例:
(在Gruntfile.js文件中修改)
requirejs: { dist: { // Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js options: { // `name` and `out` is set by grunt-usemin baseUrl: yeomanConfig.app + '/scripts', mainConfigFile:'<%= yeoman.app %>/scripts/config.js', optimize: 'none', // TODO: Figure out how to make sourcemaps work with grunt-usemin // https://github.com/yeoman/grunt-usemin/issues/30 //generateSourceMaps: true, // required to support SourceMaps // http://requirejs.org/docs/errors.html#sourcemapcomments preserveLicenseComments: false, useStrict: true, wrap: true //uglify2: {} // https://github.com/mishoo/UglifyJS2 } } },
這里的config.js是專門的RequireJS配置文件,主要是所有文件中的依賴關系,配置了paths和shim項:
app/scripts/config.coffee
require.config paths: ## jQuery 'jquery':'../bower_components/jquery/jquery' ## BootStrap 'bootstrap-affix': "../bower_components/bootstrap/js/affix" 'bootstrap-transition': "../bower_components/bootstrap/js/transition" "bootstrap-alert": "../bower_components/bootstrap/js/alert" "bootstrap-button": "../bower_components/bootstrap/js/button" "bootstrap-collapse": "../bower_components/bootstrap/js/collapse" "bootstrap-dropdown": "../bower_components/bootstrap/js/dropdown" "bootstrap-modal": "../bower_components/bootstrap/js/modal" "bootstrap-tooltip": "../bower_components/bootstrap/js/tooltip" "bootstrap-popover": "../bower_components/bootstrap/js/popover" "bootstrap-scrollspy": "../bower_components/bootstrap/js/scrollspy" "bootstrap-tab": "../bower_components/bootstrap/js/tab" "bootstrap-carousel": "../bower_components/bootstrap/js/carousel" ## Handlebars runtime 'runtime':'lib/handlebars/handlebars.runtime-v1.1.2' ## Templates 'messages':'../hbs/messages'
Step 4:將requirejs添加build任務中
為了能夠在發布時,使用r.js進行頁面優化(合並、壓縮等),需要將requirejs任務作為build任務的子任務:
(在Gruntfile.js文件中修改)
grunt.registerTask('build', [ 'clean:dist', 'useminPrepare', 'concurrent:dist', 'autoprefixer', 'requirejs', 'concat', 'cssmin', 'uglify', 'modernizr', 'copy:dist', 'rev', 'usemin' ]);
Step 5:重新組織index.html頁面中的js文件
接下來刪除Figure 9中的所有script標簽,代之以下面的語句:
(在app/index.html文件中修改)
<!-- build:js scripts/main.js --> <script data-main="scripts/main" src="bower_components/requirejs/require.js"></script> <!-- endbuild -->
入口文件還是main.js,只是現在改成RequireJS要求的格式:
app/scripts/main.coffee
require.config paths: 'jquery':'../bower_components/jquery/jquery', 'runtime':'lib/handlebars/handlebars.runtime-v1.1.2', 'messages':'../hbs/messages' require ['jquery','runtime','messages','bootstrap'],($)-> 'use strict'; $ -> data = messages:[ {name:"Zhang",msgContent:"I'm San",msgTime:"Yesterday"} {name:"Li",msgContent:"I'm Si",msgTime:"Today"} {name:"Wang",msgContent:"I'm Wu",msgTime:"Tomorrow"} ] name:"jscon" template = JST["app/hbs/messages.hb"] $("#list").html(template(data))
注意這里的bootstrap依賴文件用來配置需要哪些bootstrap組件用的,可以自己定制所需要的插件內容,挺方便的。這里給出最全的配置,內容如下:(參考自https://gist.github.com/taxilian/4790603)
app/scripts/bootstrap.coffee
require.config paths: 'jquery':'../bower_components/jquery/jquery', 'bootstrap-affix':"../bower_components/bootstrap/js/affix", 'bootstrap-transition':"../bower_components/bootstrap/js/transition", "bootstrap-alert":"../bower_components/bootstrap/js/alert", "bootstrap-button":"../bower_components/bootstrap/js/button", "bootstrap-collapse":"../bower_components/bootstrap/js/collapse", "bootstrap-dropdown":"../bower_components/bootstrap/js/dropdown", "bootstrap-modal":"../bower_components/bootstrap/js/modal", "bootstrap-tooltip":"../bower_components/bootstrap/js/tooltip", "bootstrap-popover":"../bower_components/bootstrap/js/popover", "bootstrap-scrollspy":"../bower_components/bootstrap/js/scrollspy", "bootstrap-tab":"../bower_components/bootstrap/js/tab", "bootstrap-carousel":"../bower_components/bootstrap/js/carousel" shim: "bootstrap-affix": ["jquery"], "bootstrap-transition": ["bootstrap-affix"], "bootstrap-alert": ["bootstrap-transition"], "bootstrap-button": ["bootstrap-alert"], "bootstrap-collapse": ["bootstrap-button"], "bootstrap-dropdown": ["bootstrap-collapse"], "bootstrap-modal": ["bootstrap-dropdown"], "bootstrap-tooltip": ["bootstrap-modal"], "bootstrap-popover": ["bootstrap-tooltip"], "bootstrap-scrollspy": ["bootstrap-popover"], "bootstrap-tab": ["bootstrap-scrollspy"], "bootstrap-carousel": ["bootstrap-tab"] define ['jquery', "bootstrap-affix", "bootstrap-transition", "bootstrap-alert", "bootstrap-button", "bootstrap-collapse", "bootstrap-dropdown", "bootstrap-modal", "bootstrap-tooltip", "bootstrap-popover", "bootstrap-scrollspy", "bootstrap-tab", "bootstrap-carousel" ],($)->
至此配置完成,在命令行中輸入 grunt server 就可以看到以前熟悉的頁面了,沒錯,you make it!
3.4、發布程序
程序的發布,使用
grunt build --force 或者 grunt --force
加force選項的目的是為了在執行任務時的出現warning提示時,並不中斷任務的執行而是繼續執行到完成(或出現Error)。
此時你會在根目錄下出現一個dist文件夾, 這個文件夾的結構和app文件夾相似
可以發現只有一個js文件和css文件,都是經過壓縮的。js的壓縮是RequireJS、concat和uglify共同的作用結果,css的壓縮則是concat和cssmin的作用結果。使用RequireJS會根據入口文件main.js中找到所有的依賴,然后合並成一個大的main.js文件:
這個dist文件夾就是可以發布的版本了,(改個名字后)扔到服務器上就可以了。
4、構建多頁面前端環境
還記得小時候看過的童話故事中的那只偷香油的小老鼠么?有時候,一種優勢在另外一種情況下就成了弊端。上面我們講的usemin就是這個一個情況。
我們回過頭來看發布時命令窗口中的幾行提示:
這些反饋信息都來自usemin的工作,usemin組件能夠自動更新Gruntfile中諸如concat、uglify、requirejs的配置文件,所以前面我們在單頁面環境時根本不用考慮如何合並、壓縮文件,因為usemin默默地幫你完成了——真是人民的好公仆呢。
usemin所做的工作對單頁面來講非常有用,增加了配置過程的自動化。不過在RequireJS用於多頁面開發時,需要使用dir以及modules配置,但是usemin“仍然不知情”,還是會自動給requirejs任務添加name屬性和out屬性(單頁面配置),從而導致配置沖突。
要想自動部署多頁面,那么只能忍痛割愛拋棄usemin組件,需要自己寫concat和uglify任務,還好這些都不難。在前面單頁面環境搭建的基礎上,配置多頁面環境也是非常方便快捷的。
4.1、拋棄usemin任務
因為usemin和requirejs任務都是用於發布時執行的,所以只要在build任務中除名即可,除名之后build任務的配置如下:
grunt.registerTask('build', [ 'clean:dist', 'copy:server', 'concurrent', 'requirejs', 'cssmin', 'concat', 'uglify', 'copy' ]);
4.2、修改requirejs任務
修改requirejs任務,添加用於多任務的dir和modules配置:
(在Gruntfile.js文件中修改)
requirejs: { dist: { … baseUrl:'<%= yeoman.app %>/scripts', mainConfigFile:'<%= yeoman.app %>/scripts/config.js', optimize: 'none', dir: '.tmp/scripts/requirejs/', modules:[ {name:"main"}, {name:"main2"} ], … } },
注意
① 輸出的文件暫時放在 .tmp臨時文件夾下,這樣到時clean任務會清空這個臨時文件夾。
② 這里的modules中模塊的名字都是以“main”開頭的,是為了方便后面concat任務找文件。
4.3、修改concat、uglify任務
移走了usemin任務之后,concat和uglify需要自己定制。其實concat任務更像是copy功能,因為合並的工作已經由requirejs做了。
(在Gruntfile.js文件中修改)
concat:{ dist:{ expand:true, cwd:".tmp/scripts/requirejs/", src:['main*.js'], dest:'<%= yeoman.dist %>/scripts/', ext:'.js' }, dep:{ files:{ "<%= yeoman.dist %>/scripts/vendor/modernizr.js":["<%= yeoman.app %>/bower_components/modernizr/modernizr.js"], '<%= yeoman.dist %>/bower_components/requirejs/require.js':['<%= yeoman.app %>/bower_components/requirejs/require.js'] } } },
這里的“dist”任務是用來搬運的之前requirejs放在.tmp中的入口文件(main*.js)到結果文件夾dist下。而“dep”任務則是搬運其他需要的js文件,這里是modernizr.js和require.js兩個文件。
由於requirejs和concat已經把該合並的文件都放到目標文件下面去了,所以uglify的任務就非常簡單了,把這些目標文件下的文件“就地”壓縮一下即可:
uglify: { src:['<%= yeoman.dist %>/{,*/}*.js'] },
4.4、測試
把index.html另存一份為index2.html,並將其中的入口文件改成main2:
<script data-main="scripts/main2" src="bower_components/requirejs/require.js"></script>
然后把main.js另存為一份main2.js。接下去使用 grunt --force 發布程序,可以看到效果。
參考文獻:
[1] 科學的愛情. 《Grunt + RequireJS with multi-page website》. 2013-03-11
[2] Xianjing《玩轉Grunt(一): Minification》.2013-10-7
最后友情提醒一句,本文所講的程序代碼可以從這兒下載:
單頁面前端環境搭建示例代碼:jscon-single-page.zip
多頁面前端環境搭建示例代碼:jscon-multi-pages.zip
~~The End~~