標籤:prefix bar ... asc doc 順序 mic rri options
前端工作流程,Grunt上手指南
Posted@2013-04-20 7:15 a.m.
CategoriesGrunt , javascript
我想先花點時間回憶一下作為一個前端需要做的工作(Loading...)
- JS合并
- JS壓縮
- CSS壓縮
- CSS Sprite
- 圖片最佳化
- 測試
- 靜態資源緩衝(版本更新)
- ...
對應的,一個全副武裝的前端可能會是這樣的:
- JSHint
- CSSLint
- Jade
- CoffeeScript
- RequireJS/SeaJS
- Compass/Stylus/Less
- QUnit/Mocha/Jasmine
- ...
就是這麼苦逼的設定,但其實也正是有了這些天才的工具和解決方案才讓我們的工作變得更美好,WTF
隨身裝備這麼多武器,拔刀的姿勢很重要,手忙腳亂焦頭爛額的狼狽樣肯定是不行的。如果每個工具(任務)對應一個招數的話,Grunt就是用來組合各種華麗連續技的輔助裝備。這比喻略顯白爛,高端大氣國際化的說法叫“工作流程”(Workflow)。所以開始進入正題: Grunt鍛造指南,參見!
- 安裝
- 關於外掛程式
- 配置任務
- 模板變數
- 任務選項
- 指定檔案
- Compact Format
- Files Object Format
- Files Array Format
- 萬用字元
- 動態匹配
- 自訂任務
Note:這些都是基於Grunt 0.4.x版本,需要Nodejs版本>=0.8.0
命令列下安裝:
# 如果之前有裝過grunt,卸載之npm uninstall -g grunt# 安裝grunt運行工具npm install -g grunt-cli
一個grunt項目需要兩個檔案:package.json和Gruntfile.js,前者用於nodejs包管理,比如grunt外掛程式安裝,後者是grunt設定檔,配置任務或者自訂任務。
先產生一個package.json檔案,在grunt項目的目錄下運行npm install就可以產生一個空的package.json。
安裝grunt到目前的目錄:npm install grunt --save-dev
再產生一個Gruntfile.js的模板檔案,這時候可以用grunt-init,或者直接手寫一個:
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 taskgrunt.register(‘foo‘, function(arg1, arg2) { // do something});
這樣運行:grunt foo,或者grunt foo:a:b,a和b就是傳遞給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方法定義的名字,比如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名,一開始就不必糾結了。
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任務,每次都要設定paramgrunt.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.option和grunt.config都可以用來在任務之間共用一些資訊,但option更多用來接受額外的任務參數。
設定輸出文字顏色
直接在字串後面點一個顏色:grunt.log(‘test color‘.green)
References
- Grunt API
- gruntjs
- workflow-vips
假如你已經熟悉了Grunt,可以去看看Yeoman,也許能為你提供更多靈感。
grunt 入門學習