Posts match “ javascript ” tag:

Grunt 入門


GruntGrunt 擴充套件 都是透過 npm 來安裝和管理。Grunt 這個工具大概就等於 Ruby 中的 rake ,它可以透過一些設定讓你輕鬆完成一些例行性的任務,例如壓縮檔案,編譯 coffee less,搬移到目標目錄,單元測試等等。之後就可以透過一個指令,就把所有的事情做好。

安裝


為了能夠使用 Grunt 你必須安裝 grunt-cli 就是 Grunt 的 Command Line Interface 到您的系統。
安裝過程可能會需要 sudo 或這在 windows 底下使用管理者身份執行。

npm install -g grunt-cli

npm 使用 -g 就會把該套件裝至系統路徑,這樣您就可以在任何目錄使用 grunt-cli
注意:安裝了 grunt-cli 並不是安裝 Grunt task runnergrunt-cli 本身的任務非常單純,他就只是使用你已經安裝的 Grunt 版本去執行 Gruntfile.js。這樣你就可以在同一台機器,運行不同版本的 grunt 了。

CLI如何運作


每一次執行 grunt,他會用require()去引入本地的 grunt,注意不是 cli。也因次你可以在不同專案目錄底下運行不同版本的 grunt
一旦 grunt 被載入了,接著就會載入 Gruntfile 然後根據裡面的設定去運行任務。
如果你想理解原理可以閱讀程式碼 https://github.com/gruntjs/grunt-cli/blob/master/bin/grunt

如何使用已經存在的 Grunt 專案

假如您的 cli 已經安裝了,並且有一個專案也已經設定好 package.jsonGruntfile.js。那你可以非常輕鬆地就開始使用 grunt

  1. 切換到專案的根目錄
  2. 使用 npm install 安裝相依的套件。
  3. 執行 grunt 就可以開始跑了。

如果你想瞭解更多關於 grunt 的命令,你可以使用 grunt --help 會列出例如:clean, coffee。這些都是靠 Grunfile 定義的。
預設是空的沒有任何任務


Sails 建立的 Gruntfile

建立一個新的 Grunt 專案


要建立一個 Grunt 專案涉及到最重要的兩個檔案的建立 package.jsonGruntfile.js

  • package.json : 這是 npm 用來儲存 matadata 的檔案,簡單說就是 npm 對於這個專案相關的設定擋。包含相依的套件,一旦在這邊設定,您就可以透過 npm install 來安裝相依的套件。一般在使用時,我們也會用 npm install --save 在安裝後順便把設定加到 package.json。你也會在這個檔案設定 grunt 和 grunt 套件相依的一些函式庫或元件。通常會像下面程式碼片段,指的是開發階段使用的套件。

     "devDependencies": {
      "mocha": "*",
      "request": "*",
      "wrench": "~1.5.1"
      }
    
  • Gruntfile : 這個檔案通常會是 Gruntfile.js 或者 Gruntfile.coffee 他被用來設定任務,載入外掛。

package.json


package.json 通常和 Gruntfile 都在專案的根目錄。而且必須和你的專案一起被送交。在根目錄下 npm install 就會根據 package.json 去安裝相依的套件。而且還能指定版本。下面提供一些簡單的方式快速建立 package.json

  • 大部份的 grunt-init 模版都會自動建立 package.json。而 grunt-init 這個指令是需要在安裝的。

  • npm init 直接執行 npm init 就會一步一步詢問你相關的參數,協助您建立 package.json

  • 手動建立

    {
      "name": "my-project-name",
      "version": "0.1.0",
      "devDependencies": {
        "grunt": "~0.4.1",
        "grunt-contrib-jshint": "~0.6.3",
        "grunt-contrib-nodeunit": "~0.2.0",
        "grunt-contrib-uglify": "~0.2.2"
      }
    }
    

安裝 Grunt 和 Grunt 套件


安裝 Grunt 和 Grunt 套件並且加入 package.json 最簡單的方式就是執行 npm install <module> --save-dev
它不只會安裝該套件模組還會新增或更新 package.json 。
範例:

npm install grunt --save-dev
/* 測試透過 npm 安裝同一個套件,不同版本*/

npm install jquery@1.7.2 --save-dev
npm install jquery@1.8.3 --save-dev

Gruntfile


Gruntfile.jsGruntfile.coffee 都必須是合法正確的 javascript 或 coffee 。他們通常放在專案的根目錄下。跟 package.json 同一層,也一樣必須要被提交。
一個 Gruntfile 包含下面這幾個部分:

  • 把所有動作包起來的 function
  • 專案和任務的設定
  • 載入的 Grunt 套件和任務(別人已經寫好的)
  • 自訂的任務

Gruntfile 範例


在下面的範例中,關於 project 的 metadata 等資料是透過讀取 package.json 取得,並在 grunt 中使用。
使用 grunt-contrib-uglify 套件的uglify任務是用來設定壓縮檔案並根據專案的 metadata 產生一些註解。當 grunt 開始執行,預設就會執行 uglify 任務

    module.exports = function(grunt) {
      // Project configuration.
      grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        uglify: {
          options: {
            banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
          },
          build: {
            src: 'src/<%= pkg.name %>.js',
            dest: 'build/<%= pkg.name %>.min.js'
          }
          }
      });

    // Load the plugin that provides the "uglify" task.
    grunt.loadNpmTasks('grunt-contrib-uglify');

    // Default task(s).
    grunt.registerTask('default', ['uglify']);

    };

整個 Gruntfile 大致上就如上面這樣,後續我們將一步一步介紹每個組成部份。

The "wrapper" function (把所有動作包起來的 function)


每一個 Gruntfile 或者 Grunt 套件都使用這個基本的定義格式,而你所有要讓 Grunt 執行的程式碼都必須被包在這個 function 中。

     module.exports = function(grunt){
       // Do grunt-related things in here
     }

專案和任務設定


絕大多數的 Grunt 任務都透過定義一個包含所有設定資料的物件,然後把物件傳給 grunt.initConfig 方法來設定的。
在上面的範例中,grunt.file.readJSON('package.json') 匯入了一個 JSON 格式的檔案,JSON 讀取至 javascript 中便是一個 object。接著在 Gruntfile 中可以使用 <% %> 這樣的語法,中間可以帶入其他設定的屬性,例如檔案路徑。
你可能會隨便存一些資料到設定裡面,只要屬性不要衝突即可,否則會被忽略。這其實也是因為在 Javascript 中,你不能去限制 JSON

就像大部份的任務,grunt-contrib-uglifyuglify 任務預計會在同名的屬性中取得設定,在下面的範例中,我們在 options 設定了 banner,然後設定了 build 裡面指定了 src 和 dest 。整段範例的意思就是取得 package.json 中的 name ,接著去 src 目錄中取得 name.js 檔案並且把它壓縮成 name.min.js 。到這邊為止的 code 都只是設定而已。

     // Project configuration.
     grunt.initConfig({
       pkg: grunt.file.readJSON('package.json'),
       uglify: {
         options: {
           banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
         },
         build: {
           src: 'src/<%= pkg.name %>.js',
           dest: 'build/<%= pkg.name %>.min.js'
         }
       }
     });

載入 Grunt 和 Grunt 套件


大部份的任務例如串聯檔案,壓縮,都已經有現成的套件可以使用了,只要在 package.json 中設定並安裝,就可以使用。
安裝完之後,就是在 Gruntfile 中載入

    // Load the plugin that provides the "uglify" task.
    grunt.loadNpmTasks('grunt-contrib-uglify');

載入之後透過 grunt --help 就可以列出目前可以執行的任務,注意:initConfig 是在 grunt 初始化時針對任務做一些設定。於是真正在執行的時候就會去套用設定,例如上面我們載入了 grunt-contrib-uglify,之後我們就可以下達指令:grunt uglify。就會照著我們設定的把一支跟專案名稱一樣的 .js 壓縮成 .min.js。

自訂任務


您可以在預設就讓 grunt 執行多個任務,只要透過 default 設定,接著我們執行 grunt 的時候就會照著預設任務去執行。如下範例,我們把 uglify 註冊到 default 。這樣我們就不用特別單獨執行 grunt uglify ,只要執行 grunt 或者 grunt default 就會執行下面壓縮檔案的這個任務。需要指定的任務可以加入下面程式碼 ['uglify'] 中的這個陣列。它可以執行多個任務。

    // Default task(s).
    grunt.registerTask('default', ['uglify']);

不過如果你想要執行的任務或需求並沒有任何 Grunt 套件,您也可以自訂在 Gruntfile 裡面

    module.exports = function(grunt) {

      // A very basic default task.
      grunt.registerTask('default', 'Log some stuff.', function() {
        grunt.log.write('Logging some stuff...').ok();
      });

    };

以上我們就完成了 Grunt 入門的使用了

何謂 sails.js



sails 其實就跟 RailsASP.NET MVC 類似,是一個 MVC 的 Web 框架。目標是協助您快速的用 nodejs 開發出企業等級的網站(註:由於開發團隊仍在開發中,所以要採用前請先審慎評估XDD)。所以就跟其他框架一樣,他讓我們不用再重新造輪子,整合一般網站開發常用的功能,並且重點是支援許多 nodejs 特有的強項,特別是在開發聊天室這類的應用程式更顯得出效能。本篇文章僅僅透過實作一些基礎,讓我們快速的感受一下 sails 。

安裝 sails.js


            $ npm -g install sails  // -g:安裝到系統路徑


建立專案


            $ sails new [project_name]
            $ sails new [project_name] --linker // 使用 linker 用來整和壓縮 css 和 js 的套件
            $ sails new [project_name] --template=jade // 預設樣板引擎是 `ejs` 可以換成 `jade`


安裝相依套件


            $ cd [project_name]
            $ npm install
            $ npm install jade --save  // 安裝 jade 並且更新 package.json


啓動測試網站


    $ sails lift



依照指示說明在瀏覽器輸入 http://localhost:1337 就可以看到預設畫面。

初識路由和目錄結構



路由設定在 config/routes.js ,打開該檔案我們會看到如下程式碼:

            module.exports.routes = {
                '/' : {
                    view: 'home/index'
                }
            }





嘗試編輯



view: 'home/index' 改成 view: 'static/index' 。然後我們在 views 目錄底下建立一個 static 目錄,接著在裡面再建立一個 index.ejsindex.jade 。隨便輸入一些資料,例如

            h2 Hello, This is our page using Jade

或者

            <h2>Hello, This is our page using EJS</h2>

注意:這邊我們先不做太多解釋與設定,純粹先實作讓大家感受一下 Sails 這個框架。如果使用 jade 請記得整個 view 應該是這樣:

            extends ../layout

            block body
                h2 Hello, This is our page using Jade

接著我們使用 sails lift 指令來觀看我們的修改。

加入 bootstrap



到目前為止我們只有使用到 view ,我們來嘗試組織一下專案加入 bootstrap。到 bootstrap3 官網下載 zip 並且把 bootstrap.css 放到 assets/styles/ 目錄底下。

編輯 views/static/index.jadeviews/static/index.ejs 加入

            extends ../layout

            block body
                div.container
                    div.jumbotron
                    h1 activityOverlord
                    h2 ...tracking app activity better than the NSA since 1899.
                    a(href='/user/new', class='btn btn-lg btn-success') Sign up now!

或者

            <div class="container">
                <div class="jumbotron">
                    <h1>activityOverlord</h1>
                    <h2>...tracking app activity better than the NSA since 1899.</h2>
                    <a href="/user/new" class="btn btn-lg btn-success">Sign up now!</a>
                </div>
            </div>


修改 Layout



Sails 裡面跟其他 MVC 框架一樣預設都幫您提出一個 layout 樣板,而每個 view 則會嵌入到 layout 的 body 中。例如 jade 是 block bodyejs<%- body %> 。如此一來相同的部分我們就可以放入 layout 之後如果需要修改,就只要修改一次。

layout.jade 如下


            !!!
            html
                head
                    title= title

                    // Viewport mobile tag for sensible mobile support
                    meta(name="viewport",content="width=device-width, initial-scale=1, maximum-scale=1")

                    link(rel="stylesheet",href="/styles/bootstrap.css")
                    link(rel="stylesheet",href="/styles/custom.css")
                    block styles


                body
                    div.navbar.navbar-inverse.navbar-fixed-top
                        div.container
                            a.navbar-brand(href="/") Activity Overload  

                    block body

                    div.container
                        hr
                        footer.footer.pull-right
                            div
                                a(href="http://sailsjs.com/") Sails.js
                            div tutorial by irl nathan, with a bunch of help from cody, gabe, heather, mike, scott and zoli
                    // A few key dependencies, linked in order

                    // Bring in the socket.io client
                    script(type="text/javascript", src="/js/socket.io.js")

                    // then beef it up with some convenience logic for talking to Sails.js'
                    script(type="text/javascript", src="/js/sails.io.js")

                    // listen on socket.io for incoming messages
                    script(type="text/javascript", src="/js/app.js")




其他 ejs 範例可以透過 ActivityOverloadd範例 下載
原文影片教學在此 連結,這篇文章讓我們先感受一下使用 Sails.js 這個 nodejs 框架使用起來的感覺。後續會補上教學。

這篇教學將會解釋如何使用 Gruntfile 為你的專案設定任務。如果你還不知道什麼是 Gruntfile 請回到上一篇 Grunt 入門 閱讀。

Grunt 設定



關於任務,你可以把任務當作就是一連串需要被執行的動作,通常這些設定都在 Gruntfile 這個檔案的 grunt.initConfig 這個方法中。
接著各個任務的設定都會在屬性名稱底下,換個說法, initConfig 需要你傳遞一個物件{}參數進去如下:

            grunt.initConfig({property:{ // task configuration here. }});

之後這個物件的每一個屬性通常就對應著一個任務,這只是大部份的狀況,它有可能是任意的資料。只要屬性不衝突即可,一旦衝突就會被忽略。接著讓我們來看看一個比較完整的範例:

            grunt.initConfig({
                concat: { 
                    // concat task configuration goes here.
                    // 整合檔案的任務
                },
                uglify: {
                    // uglify task configuration goes here.
                    // 壓縮檔案的任務
                },
                // Arbitrary non-task-specific properties.
                // 任意的資料
                my_property: 'whatever',
                my_src_files: ['foo/*.js', 'bar/*.js'],
            });


任務設定和目標(Targets)



當任務開始執行, Grunt 會去尋找設定檔裡面的同名屬性,不同的任務配合不同的屬性,屬性可以設定不同的任務目標。我們用下面的範例來說明: concat 任務有 foo, bar 兩個目標,uglify 任務有 bar 一個目標。

            grunt.initConfig({
            concat: {
                foo: {
                // concat task "foo" target options and files go here.
                },
                bar: {
                // concat task "bar" target options and files go here.
                },
            },
            uglify: {
                bar: {
                // uglify task "bar" target options and files go here.
                },
            },
            });

上一篇我們說過可以透過 grunt uglify 來執行任務,那當我們有不同的目標時就可以透過 grunt concat:foo 來執行該目標。不過在這邊要再次提醒上面範例只是設定,並無法拿來直接執行。如果直接下 grunt concat 則會把所有目標都執行一遍。這邊還有另一點要注意的是如果使用 grunt.renameTask 修改了任務的名稱,那 grunt 就會自動根據新名稱去找設定。

Options



在每一項任務設定中options屬性也許是用來覆寫內建的預設規則,此外每個target目標也可能會有一個 options 屬性。目標的 options 屬性會覆蓋任務的 options ,最後就是關於 options 這個屬性是可以省略的,他不是必須的。讓我們來看看範例:

            grunt.initConfig({
            concat: {
                options: {
                // Task-level options may go here, overriding task defaults.
                        // 任務等級的 options
                },
                foo: {
                options: {
                    // "foo" target options may go here, overriding task-level options.
                            // 『目標』等級的 options 如果設定有重複,它會覆寫任務等級的設定。
                },
                },
                bar: {
                // No options specified; this target will use task-level options.
                },
            },
            });


檔案



因為大部份的任務都涉及操作檔案, Grunt 對於檔案的定義宣告與操作有非常強及高度的抽象化。它提供了一些方法定義 src-dest (來源-目的地)如何對應 ,就是取得來源檔案,然後處理後產生至目的地目錄。提供了不同程度的操作,任何任務都會針對某種格式的檔案來做操作,編譯等等。你可以選擇你需要的格式。

所有的檔案格式都支援 srcdest 兩種屬性設定,不過簡潔格式和檔案陣列有一些額外的屬性可用。下面會說明什麼是簡潔格式和檔案陣列。額外支援的屬性如下:

  • filter 任何一個 fs.States 的方法名稱如:stats.isFile()你可以用'isFile',或者任何一個 function 輸入src` 路徑後可以判斷回傳 true/false ,都可以拿來設定,用來過濾。
  • nonull 當一個比對沒有被找到的時候,就會回傳一個包含其 pattern 的列表,否則就會回傳一個空的列表。搭配 --verbose 可以用在關於檔案路徑的除錯。

  • dot 允許檔案名稱的匹配模式用 . 開始 ,即使設定沒有在開頭使用 .
  • matchBase 一旦設定這種匹配模式就不會比對 / 舉例來說會變成 *.js 等於 **/*.js 簡單來說就是只比對最後檔名的部分。例如,a?b 會匹配 /xyz/123/acb 但不匹配 /xyz/acb/123
  • expand 處理動態的 srcdest 檔案對應。
  • 其他屬性都會傳入底層當作匹配的參數


簡潔格式



這種格式允許每一個『目標』設定單一屬性的對應,比較常用在讀取檔案,例如 grunt-contrib-jshint 只需要 src ,而不需要 dest 。看下面範例會更清楚。

            grunt.initConfig({
            jshint: {
                foo: {
                        src: ['src/aa.js', 'src/aaa.js']
                },
             },
            concat: {
                bar: {
                src: ['src/bb.js', 'src/bbb.js'],
                  dest: 'dest/b.js',
               },
             },
            });


檔案物件格式



這種格式允許每個目標對應多個檔案,前面的屬性名稱會是目的地的檔案名稱。然後來源檔案可以是多個。可以用這種方式設定多組檔案的對應。一旦採用這種設定方式就不能再加入其他屬性了。看看下列程式碼:

            grunt.initConfig({
            concat: {
                foo: {
                files: {
                    'dest/a.js': ['src/aa.js', 'src/aaa.js'],
                    'dest/a1.js': ['src/aa1.js', 'src/aaa1.js'],
                },
                },
                bar: {
                files: {
                    'dest/b.js': ['src/bb.js', 'src/bbb.js'],
                    'dest/b1.js': ['src/bb1.js', 'src/bbb1.js'],
                },
                },
            },
            });


檔案陣列格式



這種方式一樣支援多組 src-dest 對應,但可以多加入其他屬性。就跟一開始說的一樣 簡潔格式檔案陣列 格式可以增加其他的屬性。直接看範例比較清楚。

            grunt.initConfig({
            concat: {
                foo: {
                files: [
                    {src: ['src/aa.js', 'src/aaa.js'], dest: 'dest/a.js'},
                    {src: ['src/aa1.js', 'src/aaa1.js'], dest: 'dest/a1.js'},
                ],
                },
                bar: {
                files: [
                    {src: ['src/bb.js', 'src/bbb.js'], dest: 'dest/b/', nonull: true},
                    {src: ['src/bb1.js', 'src/bbb1.js'], dest: 'dest/b1/', filter: 'isFile'},
                ],
                },
            },
            });


舊版的格式



直接在任務底下,就是沒有 files 屬性是舊版本在執行多檔案操作的一種過渡期的方式。直接讓最後目的地的路徑等於目標(target) 名稱,很不幸的因為目標名稱就是路徑,所以當你要執行 grunt task:target 的時候就顯得很糟糕。而且你也不能在設定 目標等級options 或加入其他屬性。因此請儘量避免使用這種格式設定。

            grunt.initConfig({
            concat: {
                'dest/a.js': ['src/aa.js', 'src/aaa.js'],
                'dest/b.js': ['src/bb.js', 'src/bbb.js'],
            },
            });


自訂過濾 Function



filter 這個屬性可以讓你針對檔案設定更多細節,最簡單的方式就是使用 fs.States` 的 method 名稱來設定,例如下面的程式碼我們就可以輕鬆的確認是否為一個實體檔案,然後把tmp` 暫存的檔案都清光。

            grunt.initConfig({
            clean: {
                foo: {
                src: ['tmp/**/*'],
                filter: 'isFile',
                },
            },
            });

或者是你只要建立一個 function 回傳 true/false 就可以了

            grunt.initConfig({
            clean: {
                foo: {
                src: ['tmp/**/*'],
                filter: function(filepath) {
                    return (grunt.file.isDir(filepath) && require('fs').readdirSync(filepath).length === 0);
                },
                },
            },
            });


模式匹配與符號說明(Global Pattern)



一般來說一個路徑一個路徑設定在實務上來說是不切實際的,非常麻煩。所以 Grunt 支援透過內建的函式庫來做檔名路徑的匹配,或者說透過設定 Pattern 規則來選取檔案。這並不是專門的模式匹配的教學,我們只需要學習常用的符號即可。

  • * 任意數量的字元,但不包含 /
  • ? 單一字元,不包含 /
  • ** 任何數量的字元,包含 / 只要是路徑的一部分就可以。
  • {}, 分隔列出清單,只要其中一個符合即可(OR)。
  • ! 用在開頭,排除的意思。

大部份的人都知道 /foo/*.js 會選取到所有在 /foo 目錄底下的 js 檔案。但是 foo/**/*.js 則會包含任何在 /foo 目錄底下子目錄的 js 檔案。要注意 /foo/*.js 只有一層,就是在 /foo 底下,至於其它如 /foo/subfolider 就沒有。
此外,為了簡化複雜的匹配, Grunt 也允許你使用檔案陣列的方式如 ['a.js', '/foo/b.js'] ,模式匹配是按照順序的,! 會排除匹配的檔案,其結果是唯一的。

            // 指定單一檔案
            {src: 'foo/this.js', dest: …}
            // 使用檔案陣列指定多個檔案
            {src: ['foo/this.js', 'foo/that.js', 'foo/the-other.js'], dest: …}
            // 使用模式匹配來選取檔案
            {src: 'foo/th*.js', dest: …}

            // node-glob 模式,對應 foo 目錄底下 a 或 b 開頭的 js
            {src: 'foo/{a,b}*.js', dest: …}
            // 上面的例子也可以這樣寫。
            {src: ['foo/a*.js', 'foo/b*.js'], dest: …}

            // foo 目錄下所有的 .js ,但不包含子目錄下的
            {src: ['foo/*.js'], dest: …}

            {src: ['foo/bar.js', 'foo/*.js'], dest: …}

            // 除了 bar.js 以外所有的 .js ,會按照字母排列。
            {src: ['foo/*.js', '!foo/bar.js'], dest: …}
            // 所有的 .js 檔案, bar.js 先被排除後,又被加入了
            {src: ['foo/*.js', '!foo/bar.js', 'foo/bar.js'], dest: …}

            // 模版也可以用在檔案路徑或模式匹配中。
            {src: ['src/<%= basename %>.js'], dest: 'build/<%= basename %>.min.js'}
            // 也可以使用其他設定檔中配置的屬性值。
            {src: ['foo/*.js', '<%= jshint.all.src %>'], dest: …}


動態建立檔案物件



當你需要處理很多個別的檔案的時候,有些額外的屬性可以協助你動態的建立檔案清單。這些屬性都可以用在簡潔格式和檔案陣列格式中。

  • expand 設成 true 用來啓用後面的屬性設定。
  • cwd (Current Working Directory) 設定一個目標路徑,注意這個不是檔案路徑或者 Pattern。
  • src 在根據上面的 cwd 做 Pattern 或檔案的選取。
  • dest 目的地目錄
  • extdest 目錄設定新的附檔名取代原始附檔名。
  • flatten 移除 dest 設定的路徑,設定值為 true/false
  • rename 對每個符合規則的 src 檔案調用這個 function (在執行完 ext 和 flatten 之後)。接著傳遞 destsrc 的值給 function 最後回傳一個新的 dest 路徑。

对每个匹配的src文件调用这个函数(在执行ext和flatten之后)。传递dest和匹配的src路径给它,这个函数应该返回一个新的dest值。 如果相同的dest返回不止一次,每个使用它的src来源都将被添加到一个数组中。如果傳回的 dest 重複則會被加入 sources 的陣列。

下面是關於 minify 的例子,我們會看到 static_mappingsdynamic_mappings 目標雖然設定不同,卻處理了相同的檔案列表, static_mappings 是一條一條設定,而 dynamic_mappings 則是動態,先切換到一個路徑底下,再根據同樣的規則去執行。

            grunt.initConfig({
                minify: {
                    static_mappings: {
                        // 由於 src-dest 檔案路徑是寫死的, 每次新增或刪除,Gruntfile 都要更新。
                        files: [
                            {src: 'lib/a.js', dest: 'build/a.min.js'},
                            {src: 'lib/b.js', dest: 'build/b.min.js'},
                            {src: 'lib/subdir/c.js', dest: 'build/subdir/c.min.js'},
                            {src: 'lib/subdir/d.js', dest: 'build/subdir/d.min.js'}
                        ]
                    },
                    dynamic_mappings: {
                        // 執行任務時 Grunt 會自動在當下的工作目錄 "lib/" 下搜尋 Pettern "**/*.js", 接著建立 src-dest 檔案對應,因此你就不需要在新增或刪除檔案時更新 Gruntfile。
                        files: [
                            {
                                expand: true,   // 啓用動態對應
                                cwd: 'lib/',    // 匹配切換成當前的工作目錄 lib
                                src: '**/*.js', // 根據上面的目錄限制,套用 Pettern
                                dest: 'build/', // 檔案產生目的地目錄
                                ext: '.min.js'  // 取代原本的附檔名
                            }
                        ]
                    }
                }
            });


樣板(Templates)



直接翻譯為樣板可能不太恰當,主要的用法是可以透過 <% %> 嵌入變數,而這些變數可以是來自讀取設定檔的資料。模板本身會不斷遞回讀取變數直到裡面不再存在任何模板。
整個設定物件中由外到內的屬性都會被解析,此外 grunt 的方法或屬性都可以被嵌入樣板中。例如 <%= grunt.template.today('yyyy-mm-dd') %>

  • <%= prop.subprop %> 取得 prop.subprop 設定屬性的值。只要正規的型別他都能使用,不僅是字串,陣列貨,物件也可以。這樣說明你可能會有點模糊,讓我們看下列的範例。

            module.exports = function(grunt){
                grunt.initConfig({
                    prop: {subprop:"tmp/lib/"},
                    clean: {
                        src: "<%= prop.subprop %>*.js"
                    }
                });
            }
    
  • <% %> 在分隔符號中您可以使用 Javascript。對於做一些基本的邏輯判斷是非常有用的。

讓我們來一些範例觀察,下面使用了 concat 任務來整合檔案,執行 grunt concat:sample ,透過 banner 會在檔案加入 /* abcde */ ,而這隻檔案是由 foo/*.js, bar/*.js, baz/*.js 這三個規則搜尋來的檔案組合而成,最後結合的檔案則生成 build/abcde.js

            grunt.initConfig({
                concat: {
                    sample: {
                        options: {
                            banner: '/* <%= baz %> */\n' // '/* abcde */\n'
                        },
                        src: ['<%= qux %>', 'baz/*.js'], // [['foo/*js', 'bar/*.js'], 'baz/*.js']
                        dest: 'build/<%= baz %>.js'
                    }
                },
                // 同樣在 initConfig 中可以直接存取屬性。
                foo: 'c',
                bar: 'b<%= foo %>d', //'bcd'
                baz: 'a<%= bar %>e', //'abcde'
                qux: ['foo/*.js', 'bar/*.js']
            });


匯入外部資料



在下面的 Gruntfile 中,專案本身的 metadata 可以透過 package.json 匯入到設定物件中。接著 uglify 任務就可以透過 pkg.name 取得該專案名稱的js。 src/pkg.name.js ,然後產生到 dest 中並加上 .min.js

            grunt.initConfig({
                pkg: grunt.file.readJSON('package.json'),
                uglify: {
                    options: {
                        banner: '/* <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
                    },
                    dist: {
                        src: 'src/<%= pkg.name %>.js',
                        dest: 'dist/<%= pkg.name %>.min.js'
                    }
                }
            });


實做練習



最後讓我們在透過一個簡單的清除檔案練習,加深對整個 Grunt 運作的觀念吧!

            $ mkdir grunt-test 
            $ cd grunt-test
            $ npm init
            $ npm install grunt grunt-contrib-clean --save
            $ vi Gruntfile.js

Gruntfile 設定

            module.exports = function(grunt){  
                // 設定關於 clean 的設定,清除 tmp/ 底下的 js 檔案
            grunt.initConfig({
                clean: {
                files:[
                    {src: 'tmp/*.js'}       
                        ],
                        dynamic:{
                            files:[
                                { 
                                    expand: true,
                                    cwd: '<%= prop.andy %>',
                                    src: ['**/*.js']
                                } 
                            ] 
                        } 
                    } 
                }); 
                // 載入套件
                grunt.loadNpmTasks('grunt-contrib-clean');
                // 註冊預設的任務
                grunt.registerTask('default', ['clean']);
            }

接著執行

            $ grunt --help // 查詢可用的指令
            $ grunt clean  // 執行指令清除
            $ grunt clean:dynamic // 執行任務中的 dynamic 目標


參考資料



官方文件
官方手冊簡體翻譯
Wiki
其他教學