grunt 學習

我想先花點時間回憶一下做爲一個前端須要作的工做(Loading...)html

  • JS合併
  • JS壓縮
  • CSS壓縮
  • CSS Sprite
  • 圖片優化
  • 測試
  • 靜態資源緩存(版本更新)
  • ...

對應的,一個全副武裝的前端可能會是這樣的:前端

  • JSHint
  • CSSLint
  • Jade
  • CoffeeScript
  • RequireJS/SeaJS
  • Compass/Stylus/Less
  • QUnit/Mocha/Jasmine
  • ...

就是這麼苦逼的設定,但其實也正是有了這些天才的工具和解決方案才讓咱們的工做變得更美好,WTFnode

隨身裝備這麼多武器,拔刀的姿式很重要,手忙腳亂焦頭爛額的狼狽樣確定是不行的。若是每一個工具(任務)對應一個招數的話,Grunt就是用來組合各類華麗連續技的輔助裝備。這比喻略顯白爛,高端大氣國際化的說法叫「工做流」(Workflow)。因此開始進入正題: Grunt鍛造指南,參見!git

Note:這些都是基於Grunt 0.4.x版本,須要Nodejs版本>=0.8.0github

命令行下安裝:npm

# 若是以前有裝過grunt,卸載之 npm uninstall -g grunt # 安裝grunt運行工具 npm install -g grunt-cli

一個grunt項目須要兩個文件:package.jsonGruntfile.js,前者用於nodejs包管理,好比grunt插件安裝,後者是grunt配置文件,配置任務或者自定義任務。json

先生成一個package.json文件,在grunt項目的目錄下運行npm install就能夠生成一個空的package.jsonapi

安裝grunt到當前目錄:npm install grunt --save-dev緩存

再生成一個Gruntfile.js的模板文件,這時候能夠用grunt-init,或者直接手寫一個:app

module.exports = function(grunt) { grunt.initConfig({ // task configuration }); // Load the plugin grunt.loadNpmTasks('grunt-contrib-uglify'); // Default task(s) grunt.registerTask('default', ['uglify']); });

關於插件

grunt利用不一樣的插件完成不一樣的任務,好比用uglifyJS壓縮js對應的插件就是grunt-contrib-uglify

使用插件(以grunt-contrib-uglify爲例):

  • 在grunt項目目錄下安裝對應的插件 npm install grunt-contrib-uglify --save-dev

  • 在 Gruntfile.js 中加載插件 grunt.loadNpmTasks('grunt-contrib-uglify')

  • 在 Gruntfile.js 中配置對應的插件任務,指定要壓縮的js文件

關於配置和怎麼運行任務往下細說。

這裏能夠看到可用的插件,基本上大部分你能想到或沒想到的任務都能找到對應的插件,須要作什麼就裝什麼。

之後若是要重用一個grunt項目的配置,只須要有package.jsonGruntfile.js這兩個文件,而後npm install便可安裝全部依賴的插件。

一個插件就是對應一個任務,通常來講,全部插件都會遵循下面將要說到的任務配置規則,不少插件的文檔都不會很詳細,因此你只能根據插件提供的示例套用這些規則看有沒有更多配置的可能性。

關於任務

任務分爲兩種:"Basic" Tasks和"Multi" Tasks

Multi-tasks有所謂的target,好比下面的concat任務有foobar兩個targets,而uglify任務有一個叫bar的target

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. } } });

target的名字能夠任意指定,由於target只是爲了用特定配置運行指定的任務,好比grunt concat:foo或者grunt concat:bar會分別運行foo或者bar指定的concat任務。若是隻運行grunt concat將會遍歷全部concat下的targets按順序運行。

可是任務的名稱好比concatuglify是固定的,由對應的插件指定,在插件的使用文檔裏面都會有說明。

每一個multi task都必須有至少一個target.

不須要配置的任務就是Basic Task,你能夠這樣來定義一個Basic Task,grunt.registerTask(taskName, [description, ] taskFunction)

// foo task grunt.register('foo', function(arg1, arg2) { // do something });

這樣運行:grunt foo,或者grunt foo:a:bab就是傳遞給foo的參數

模板變量

grunt能夠經過相似<%= k.sub.k %>這種格式插入配置的其餘屬性值

Options

在一個任務配置裏面,option屬性能夠用來覆蓋默認的配置,另外,每一個target均可以有本身的option屬性。target的option優先級高於task的。options是可選的。

grunt.initConfig({ concat: { options: { // Task-level options may go here, overriding task defaults. }, foo: { options: { // 'foo' target options may go here, overriding task-level options. }, }, bar: { // No options specified; this target will use task-level options. }, }, });

不必定全部的任務都會有option的。

指定文件

這應該是剛接觸grunt時最讓人不知所措的地方了,想一想這麼多插件,每一個插件都須要指定對應要應用到的文件,可是咋一看好像每一個插件都有一套本身配置文件的方式,配置方式看上去很隨意,因此老是會以爲有一絲不靠譜。

就像以前提到的,實際上是有一套通用的規則的:

Grunt提供了幾種不一樣的格式定義src-dest形式的文件映射。任何multi-task都支持這幾種格式。

文件映射能夠有3種格式:Compact Format, Files Object Format和File Array Format, 其中"Compact"和"File Array"這兩種形式提供了一些額外的屬性可用:

  • filter 過濾,接受fs.Stats方法定義的名字,好比isFileisDirectory,或者自定義函數接受一個源文件名作爲參數,返回true or false

  • nonull Retain src patterns even if they fail to match files. Combined with grunt's --verbose flag, this option can help debug file path issues.

  • matchBase Patterns without slashes will match just the basename part.

  • ......(剩下幾個看文檔吧)

  • 另外還有一個動態文件列表生成(批量匹配文件)

如下示例中的屬性名srcdestfiles都是固定的key名,一開始就沒必要糾結了。

Compact Format

這種形式只容許單個src-dest映射在一個target裏面,只有src屬性是必須的,能夠沒有dest,這種形式通常用在只讀的task,好比jshint

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

Files Object Format

這種形式支持指定多個src-dest對應多個target,屬性名(key)是要輸出的目標文件名,value值是源文件列表。不支持額外的屬性

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'], }, }, }, });

Files Array Format

同上,只是支持額外的屬性

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'}, ], }, }, });

通配符支持

由nodejs內置的node-glob庫支持,這些均可以用在上面所說的各類文件配置中

  • *匹配任何字符,除了/

  • ?匹配單個字符,除了/

  • **匹配任何字符,包括/,因此用在目錄路徑裏面

  • {}逗號分割的「或」操做(逗號後面不要有空格)

  • ! 排除某個匹配

    // You can specify single files: {src: 'foo/this.js', dest: ...} // Or you can generalize with a glob pattern: {src: 'foo/th*.js', dest: ...} // This single node-glob pattern: {src: 'foo/{a,b}*.js', dest: ...} // Could also be written like this: {src: ['foo/a*.js', 'foo/b*.js'], dest: ...} // All files in alpha order, but with bar.js at the end. {src: ['foo/*.js', '!foo/bar.js', 'foo/bar.js'], dest: ...} // Templates may be used in filepaths or glob patterns: {src: ['src/<%= basename %>.js'], dest: 'build/<%= basename %>.min.js'}

動態生成文件名

  • expand 設置爲true打開如下選項

  • cwd 全部src指定的文件相對於這個屬性指定的路徑

  • src 要匹配的路徑,相對與cwd

  • dest 生成的目標路徑前綴

  • ext 替換全部生成的目標文件後綴爲這個屬性

  • flatten 刪除全部生成的dest的路徑部分

  • rename 一個函數,接受匹配到的文件名,和匹配的目標位置,返回一個新的目標路徑

    grunt.initConfig({ minify: { dynamic_mappings: { // Grunt will search for "**/?.js" under "lib/" when the "minify" task runs  files: [ { expand: true, // Enable dynamic expansion. cwd: 'lib/' // Src matches are relative to this path. src: ['**/?.js'], // Actual pattern(s) to match. dest: 'build/', // Destination path prefix. ext: '.min.js', // Dest filepaths will have this extension. } ] } } });

自定義任務

這裏總結一些遇到的問題吧

獲取/設置配置(模板變量)

  • 能夠讀取json配置文件:config: grunt.file.readJSON('config.json')

  • 獲取json對象的屬性:grunt.config('config.key.subkey')

  • 對應的模板變量:'<%= config.key.subkey %>'

  • 設置配置段:grunt.config('config', 'value')

動態更改任務配置,循環執行某個任務

grunt的任務都會放入一個隊列順序執行,可是隊列自己是異步執行的,因此下面的這種作法是不會如預期輸出:

grunt.registerTask('demo', function() { for (var i = 0; i < 5; i++) { grunt.task.run('t'); } // 指望執行完5次`t`任務以後打印輸出 // 實際上會當即輸出,在`t`任務開始以前 console.log('run after t'); // 執行5次`t`任務以後纔會執行這個`final`任務 grunt.task.run('final'); });

動態更改任務配置能夠利用模板變量來作,因爲如上所說的異步,因此不能直接在循環中給模板變量賦值,而是要額外作一個任務來接受配置:

// 假若有這樣的一個配置 t: { target: 'some <%= param %>' } // 在這個demo任務中須要屢次調用t任務,每次都要設置param grunt.registerTask('demo', function() { for (var i = 0; i < 5; i++) { // 要一個額外任務去更改配置 grunt.task.run('t_wrapper:' + i); } }); // 更改`t`配置並運行 grunt.register('t_wrapper', function(i) { grunt.config('param', i); grunt.task.run('t'); });

還有一種方法能夠克隆一個新的target,而後直接更改這個cloned target的配置

grunt.config和grunt.option的區別

grunt.config如上所述能夠用來動態更改模板變量,可是grunt.option不能這樣,若是在配置中直接使用grunt.option,則option在運行時就已經肯定了,不能再更改,假設這樣配置:

t: { target: 'some ' + grunt.option('param') }

運行grunt t --param=0,則target對應就是'some 0',不能再經過grunt.option(param, 1)這樣來更改配置

grunt.optiongrunt.config均可以用來在任務之間共享一些信息,但option更多用來接受額外的任務參數。

設置輸出文字顏色

直接在字符串後面點一個顏色:grunt.log('test color'.green)

References

假如你已經熟悉了Grunt,能夠去看看Yeoman,也許能爲你提供更多靈感。

UPDATE: 2013-6-29

關於這個主題,最近在w3ctech廣州站作了一次分享,濃縮了一個slides:利用Grunt打造前端工做流

相關文章
相關標籤/搜索