我想先花點時間回憶一下做爲一個前端須要作的工做(Loading...)html
對應的,一個全副武裝的前端可能會是這樣的:前端
就是這麼苦逼的設定,但其實也正是有了這些天才的工具和解決方案才讓咱們的工做變得更美好,WTFnode
隨身裝備這麼多武器,拔刀的姿式很重要,手忙腳亂焦頭爛額的狼狽樣確定是不行的。若是每一個工具(任務)對應一個招數的話,Grunt就是用來組合各類華麗連續技的輔助裝備。這比喻略顯白爛,高端大氣國際化的說法叫「工做流」(Workflow)。因此開始進入正題: Grunt鍛造指南,參見!git
Note:這些都是基於Grunt 0.4.x
版本,須要Nodejs版本>=0.8.0
github
命令行下安裝:npm
# 若是以前有裝過grunt,卸載之 npm uninstall -g grunt # 安裝grunt運行工具 npm install -g grunt-cli
一個grunt項目須要兩個文件:package.json
和Gruntfile.js
,前者用於nodejs包管理,好比grunt插件安裝,後者是grunt配置文件,配置任務或者自定義任務。json
先生成一個package.json
文件,在grunt項目的目錄下運行npm install
就能夠生成一個空的package.json
。api
安裝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.json
和Gruntfile.js
這兩個文件,而後npm install
便可安裝全部依賴的插件。
一個插件就是對應一個任務,通常來講,全部插件都會遵循下面將要說到的任務配置規則,不少插件的文檔都不會很詳細,因此你只能根據插件提供的示例套用這些規則看有沒有更多配置的可能性。
任務分爲兩種:"Basic" Tasks和"Multi" Tasks
Multi-tasks有所謂的target,好比下面的concat
任務有foo
和bar
兩個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按順序運行。
可是任務的名稱好比concat
和uglify
是固定的,由對應的插件指定,在插件的使用文檔裏面都會有說明。
每一個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:b
,a
和b
就是傳遞給foo
的參數
grunt能夠經過相似<%= k.sub.k %>
這種格式插入配置的其餘屬性值
在一個任務配置裏面,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方法定義的名字,好比isFile
, isDirectory
,或者自定義函數接受一個源文件名作爲參數,返回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.
......(剩下幾個看文檔吧)
如下示例中的屬性名src
和dest
, files
都是固定的key名,一開始就沒必要糾結了。
這種形式只容許單個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', }, }, });
這種形式支持指定多個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'], }, }, }, });
同上,只是支持額外的屬性
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.option
,則option
在運行時就已經肯定了,不能再更改,假設這樣配置:
t: { target: 'some ' + grunt.option('param') }
運行grunt t --param=0
,則target對應就是'some 0'
,不能再經過grunt.option(param, 1)
這樣來更改配置
grunt.option
和grunt.config
均可以用來在任務之間共享一些信息,但option
更多用來接受額外的任務參數。
直接在字符串後面點一個顏色:grunt.log('test color'.green)
假如你已經熟悉了Grunt,能夠去看看Yeoman,也許能爲你提供更多靈感。
UPDATE: 2013-6-29
關於這個主題,最近在w3ctech廣州站作了一次分享,濃縮了一個slides:利用Grunt打造前端工做流