【Yeoman】熱部署web前端開發環境

本文來自 “簡時空”:《【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

 Yeoman默認的歡迎界面 

看到這個自動跳出來的 “Allo,“Allo! 頁面,說明已經成功搭建環境了。可以開始在這個基礎上編寫代碼,只是我還有使用Handlebars以及RequireJS,所以還得自己安裝這些組件。

        在繼續之前,在這裏順便對比一下webapp與bootstrap-less這兩個腳手架的區別: 

除了基本的Bootstrap外,webapp有Modernizr和RequireJS, 而bootstrap-less則只有FontAwesome

如果除去我想要的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文件,此時文件夾結構如下:

創建hbs文件夾,與index.html處於同一級 並在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頁面的截圖:

簡單的index.html頁面需要加載2項個人JS文件,14項庫文件

可以看到這麼簡單的頁面裏面有一大串的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文件夾相似

可以發佈的dist文件夾與開發時的app文件夾結構基本相似

        可以發現只有一個js文件和css文件,都是經過壓縮的。js的壓縮是RequireJS、concat和uglify共同的作用結果,css的壓縮則是concat和cssmin的作用結果。使用RequireJS會根據入口文件main.js中找到所有的依賴,然後合併成一個大的main.js文件: 

運行requirejs任務時會根據依賴合併成一個總的main.js文件 

這個dist文件夾就是可以發佈的版本了,(改個名字後)扔到服務器上就可以了。

 

4、構建多頁面前端環境

        還記得小時候看過的童話故事中的那隻偷香油的小老鼠麼?有時候,一種優勢在另外一種情況下就成了弊端。上面我們講的usemin就是這個一個情況。

        我們回過頭來看發佈時命令窗口中的幾行提示:

提示正在更新Gruntfile.js的cssmin、concat、uglify和requirejs任務配置的內容

這些反饋信息都來自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~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章