1.快速入門
1)Grunt Cli
要在指令行中使用grunt,必須先将grunt-cli先安裝到全局環境中:
npm install -g grunt-cli
(要使用npm,請先安裝node,如未安裝,請自行安裝)
grunt-cli工作原理
每次運作grunt 時,他就利用node提供的require()系統查找本地安裝的 Grunt。正是由于這一機制,你可以在項目的任意子目錄中運作grunt 。
如果找到一份本地安裝的 Grunt,CLI就将其加載,并傳遞Gruntfile中的配置資訊,然後執行你所指定的任務。為了更好的了解 Grunt CLI的執行原理,請閱讀源碼。*
2)Gruntfile
Gruntfile有以下幾部分構成:
- “wrapper” 函數
- 項目與任務配置
- 加載grunt插件和任務
- 自定義任務
wrapper函數
module.exports = function(grunt) {
// Do grunt-related things in here
};
項目和任務配置
大部分的Grunt任務都依賴某些配置資料,這些資料被定義在一個object内,并傳遞給grunt.initConfig 方法。一般是擷取通過package.json擷取依賴
// 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 插件和任務
// 加載能夠提供"uglify"任務的插件。
grunt.loadNpmTasks('grunt-contrib-uglify');
自定義任務
通過定義 default 任務,可以讓Grunt預設執行一個或多個任務。在下面的這個案例中,執行 grunt 指令時如果不指定一個任務的話,将會執行uglify任務。這和執行grunt uglify 或者 grunt default的效果一樣。default任務清單數組中可以指定任意數目的任務(可以帶參數)。
// Default task(s).
grunt.registerTask('default', ['uglify']);
2.配置任務
1.Grunt配置
Grunt的task配置都是在 Gruntfile 中的grunt.initConfig方法中指定的。此配置主要是以任務名稱命名的屬性,也可以包含其他任意資料。
grunt.initConfig({
concat: {
// 這裡是concat任務的配置資訊。
},
uglify: {
// 這裡是uglify任務的配置資訊
},
// 任意資料。
my_property: 'whatever',
my_src_files: ['foo/*.js', 'bar/*.js'],
});
2.任務配置和目标
當運作一個任務時,Grunt會自動查找配置對象中的同名屬性。多任務(multi-task)可以通過任意命名的“目标(target)”來定義多個配置。在下面的案例中,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.
},
},
});
options屬性
在一個任務配置中,options屬性可以用來指定覆寫内置屬性的預設值。此外,每一個目标(target)中還可以擁有一個專門針對此目标(target)的options屬性。目标(target)級的平options将會覆寫任務級的options。
grunt.initConfig({
concat: {
options: {
// 這裡是任務級的Options,覆寫預設值
},
foo: {
options: {
// "foo" target options may go here, overriding task-level options.
},
},
bar: {
// No options specified; this target will use task-level options.
},
},
});
檔案
Grunt有一個強大的抽象層用于聲明任務應該操作哪些檔案,這個抽象層有好幾種定義src-dest(源檔案-目标檔案)檔案映射的方式,均提供了不同程度的描述和控制操作方式。 檔案映射方式有:
- 簡潔格式;
- 檔案對象格式;
- 檔案數組格式;
-
較老的格式。
所有的檔案格式都支援src和dest屬性,此外,簡潔格式和檔案數組格式還支援一下屬性:
fileter,nonull,dot,matchBase,expand
其他的屬性将作為比對項傳遞給底層的庫。
簡潔格式
這種形式允許每個目标對應一個src-dest檔案映射。支援給每個src-dest檔案映射指定額外的屬性。
bar: {
src: ['src/bb.js', 'src/bbb.js'],
dest: 'dest/b.js',
},
檔案對象格式
這種形式支援每個目标對應多個src-dest形式的檔案映射,屬性名就是目标檔案,源檔案就是它的值(源檔案清單則使用數組格式聲明)。不能夠給每個映射指定附加的屬性。
foo: {
files: {
'dest/a.js': ['src/aa.js', 'src/aaa.js'],
'dest/a1.js': ['src/aa1.js', 'src/aaa1.js'],
},
}
檔案數組格式
這種形式支援每個目标對應多個src-dest檔案映射,同時也允許每個映射擁有額外屬性
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'},
],
},
過濾器filter
隻需要使用一個有效的fs.Stats 方法名,或者建立自己的filter函數。
foo: {
src: ['tmp/**/*'],
filter: function(filepath) {
return (grunt.file.isDir(filepath) && require('fs').readdirSync(filepath).length === );
},
},
通配符模式
Grunt通過内置支援node-glob 和 minimatch庫來比對檔案名(又叫作globbing)。
常用通配符:
- *比對任意數量的字元,但不比對 /;
- ? 比對單個字元,但不比對 /;
- ** 比對任意數量的字元,包括 /,隻要它是路徑中唯一的一部分;
- {} 允許使用一個逗号分割的“或”表達式清單;
-
! 在模式的開頭用于排除一個比對模式所比對的任何檔案 。
為了簡化原本複雜的通配符模式,Grunt允許指定一個數組形式的檔案路徑或者一個通配符模式。所有模式按順序處理,模式處理的過程中,帶有!字首的模式所比對的檔案将不包含在結果集中。 而且其結果集中的每一項也是唯一的。
動态建構檔案對象
當你希望處理大量的單個檔案時,這裡有一些附加的屬性可以用來動态的建構一個檔案清單。這些屬性都可以用于Compact和Files Array檔案映射格式。
grunt.initConfig({
uglify: {
static_mappings: {
// Because these src-dest file mappings are manually specified, every
// time a new file is added or removed, the Gruntfile has to be updated.
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 will search for "**/*.js" under "lib/" when the "uglify" task
// runs and build the appropriate src-dest file mappings then, so you
// don't need to update the Gruntfile when files are added or removed.
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.
extDot: 'first' // Extensions in filenames begin after the first dot
},
],
},
},
});
模闆
使用<% %>分隔符指定的模闆會在任務從它們的配置中讀取相應的資料時将自動擴充掃描。模闆會被遞歸的展開,直到配置中不再存在遺留的模闆相關的資訊(與模闆比對的)。
整個配置對象決定了屬性上下文(模闆中的屬性)。此外,在模闆中使用grunt以及它的方法都是有效的,例如: <%= grunt.template.today(‘yyyy-mm-dd’) %>。
下面以concat任務配置為例,運作grunt concat:sample時将通過banner中的/* abcde /連同foo/.js+bar/.js+bar/.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', // 'build/abcde.js'
},
},
//用于任務配置模闆的任意屬性
foo: 'c',
bar: 'b<%= foo %>d', // 'bcd'
baz: 'a<%= bar %>e', // 'abcde'
qux: ['foo/*.js', 'bar/*.js'],
});
導入外部資料
Grunt有grunt.file.readJSON和grunt.file.readYAML兩個方法分别用于引入JSON和YAML資料。
3. 建立任務
對于grunt而言,任務(task)就像面包和黃油,grunt的工作是有任務驅動的
任務别名
當一個任務清單被指定為一個新任務,當這個新任務運作時,任務清單裡的任務會按照順序依次執行。
grunt.registerTask(taskName, [description, ] taskList)
多任務(Multi Tasks)
當一個多任務運作時,grunt會尋找配置中的同名屬性,多任務有較多配置和用任意名稱定義目标。
grunt.initConfig({
log: {
foo: [, , ],
bar: 'hello world',
baz: false
}
});
grunt.registerMultiTask('log', 'Log stuff.', function() {
grunt.log.writeln(this.target + ': ' + this.data);
});
多任務運作時如果不指定目标,将依次執行所有目标,如果指定目标,将隻執行該目标中的工作。如上面的代碼:
運作grunt log 将輸出foo: [1,2,3] ,bar : hello world,baz:false。
運作grunt log:bar 将輸出 bar:helloworld
基礎任務
基礎任務隻運作指定的任務函數,函數的參數用冒号分隔。
grunt.registerTask('foo', 'A sample task that logs stuff.', function(arg1, arg2) {
if (arguments.length === ) {
grunt.log.writeln(this.name + ", no args");
} else {
grunt.log.writeln(this.name + ", " + arg1 + " " + arg2);
}
});
例如可以運作:grunt foo:testing:123
自定義任務
當你的任務不遵循多任務的結構時,就使用自定義任務。
最簡單的一個自定義任務,如基礎任務。
在任務内部,還可以運作其他任務 ex:
grunt.registerTask('foo', 'My "foo" task.', function() {
// Enqueue "bar" and "baz" tasks, to run after "foo" finishes, in-order.
grunt.task.run(['bar', 'baz']);
});
任務可以是異步的。
grunt.registerTask('asyncme', 'My asynchronous task.', function() {
var done = this.async();
doSomethingAsync(done);
});
處理錯誤
grunt.registerTask('foo', 'My "foo" task.', function() {
if (failureOfSomeKind) {
grunt.log.error('This is an error message.');
}
// Fail by returning false if this task had errors
if (ifErrors) { return false; }
grunt.log.writeln('This is the success message');
});
一個任務失敗,所有的後續任務都将停止執行
有些任務的執行需要依賴另外一些任務,在任務中使用grunt.task.requires,可以引入需要依賴的任務,然而并不實際執行,隻是檢測其是否能運作良好。
grunt.registerTask('foo', 'My "foo" task.', function() {
return false;
});
grunt.registerTask('bar', 'My "bar" task.', function() {
// Fail task if "foo" task failed or never ran.
grunt.task.requires('foo');
// This code executes if the "foo" task ran successfully.
grunt.log.writeln('Hello, world.');
})
指定的必須設定的配置資訊不存在,任務也會失敗。ex:
grunt.registerTask('foo', 'My "foo" task.', function() {
// Fail task if "meta.name" config prop is missing
// Format 1: String
grunt.config.requires('meta.name');
// or Format 2: Array
grunt.config.requires(['meta', 'name']);
// Log... conditionally.
grunt.log.writeln('This will only log if meta.name is defined in the config.');
});
通路配置屬性
grunt.registerTask('foo', 'My "foo" task.', function() {
// Log the property value. Returns null if the property is undefined.
grunt.log.writeln('The meta.name property is: ' + grunt.config('meta.name'));
// Also logs the property value. Returns null if the property is undefined.
grunt.log.writeln('The meta.name property is: ' + grunt.config(['meta', 'name']));
});