本文主要讲配置任务中的filter,包括使用默认fs.Stats方法名和自定义过滤函数,以及filter的实现原理。
通过设置filter属性可以实现一些特殊处理逻辑。例如:要清理某个文件夹下的所有空文件夹,这时使用clean任务时,需要判断该文件夹下的哪些是文件,哪些是文件夹,只对空文件执行clean任务。
具体使用方法分为如下两种:
一 使用fs.Stats方法作为过滤函数
fs.stats是NodeJS的一个类,上边提供了很多对文件判断的方法,具体可以参考这里。
如下将filter设置为'isFile'时,如果发现当前文件不是文件时,就不进行clean处理。
1 grunt.initConfig({ 2 clean: { 3 foo: { 4 src: ['tmp/**/*'], 5 filter: 'isFile', 6 }, 7 }, 8 });
二 自定义过滤函数
自定义过滤函数,如果过滤函数返回true,就对当前文件进行处理;返回值是false时,就不处理当前文件。
下边示例是清理tmp下边所有的空文件夹:
1 grunt.initConfig({ 2 clean: { 3 foo: { 4 src: ['tmp/**/*'], 5 filter: function(filepath) { 6 return (grunt.file.isDir(filepath) && require('fs').readdirSync(filepath).length === 0); 7 }, 8 }, 9 }, 10 });
三 filter实现原理
在分析filter的实现原理之前,首先给出一个使用filter时的注意事项:
filter配置属性只能用于通过grunt.task.registerMultiTask()方法注册的复合任务。
如果使用的某个Grunt插件注册任务时,没有使用grunt.task.registerMultiTask 注册任务,则配置filter是不生效的。
而大多数的contrib任务,包括 jshint task、concat task 和 uglify task 都是复合任务。
下边说明filter在Grunt的实现原理:
通过grunt.task.registerMultiTask()方法注册复合任务时,会拿到配置信息中对应任务的配置对象,对配置对象中的filter属性进行判断,如果设置了filter,就对要处理的文件进行filter过滤,根据过滤结果将新的文件列表存到files属性中,各个任务函数中拿到的files属性实际上是已经过滤后的复合filter要求的文件。
下边以上边的clean任务为例,分析几个关键点的源码:
3.1 首先分析clean的源码:
在npm安装包node_module目录下grunt-contrib-clean中找到clean.js文件,该文件实际就是一个标准的nodeJS模块文件,找到其中通过grunt.registerMultiTask()方法注册clean任务代码,在第三个参数的处理函数中,我们可以看到这里实际是对this.filesSrc进行的处理,下边是部分代码:
1 module.exports = function(grunt) { 2 3 function clean(filepath, options, done) { 4 // 省略的代码... 5 } 6 7 grunt.registerMultiTask('clean', 'Clean files and folders.', function() { 8 // 省略的代码... 9 10 // 要处理的文件 11 var files = this.filesSrc; 12 13 async.eachSeries(files, function (filepath, cb) { 14 clean(filepath, options, cb); 15 }, function (err) { 16 grunt.log.ok(files.length + ' ' + grunt.util.pluralize(files.length, 'path/paths') + ' cleaned.'); 17 done(err); 18 }); 19 }); 20 21 };
3.2 接下来分析task.normalizeMultiTaskFiles()函数的源码:
在该函数源码中,我们要找到传给上边clean函数的this.filesSrc来自哪里。
在grunt的npm源码包中,找到task.js,其中定义了task.registerMultiTask函数,从该函数中我们可以发现this.filesSrc实际是对this.files.src进行处理后得到的。
而this.files又是通过task.normalizeMultiTaskFiles(this.data, target)得到的,其中this.data和target就是源自于Grunt的配置信息。
在最后fn.apply(this, this.args)可以知道,这个fn实际就是注册任务时的回调函数。
下边是部分关键代码:
task.registerMultiTask = function(name, info, fn) { // 省略的代码... task.registerTask(name, info, function(target) { // 省略的代码... this.data = grunt.config([name, target]); // 获取到files文件 this.files = task.normalizeMultiTaskFiles(this.data, target); // 定义this.filesSrc文件 Object.defineProperty(this, 'filesSrc', { enumerable: true, get: function() { return grunt.util._(this.files).chain().pluck('src').flatten().uniq().value(); }.bind(this) }); // 调用任务的回调函数,并将指定this return fn.apply(this, this.args); }); // 省略的代码... };
3.3 分析task.normalizeMultiTaskFiles()源码:
在这个源码中找到this.files.src是如何来的。从源码中可以发现来源于grunt.file.expand(expandOptions, src)函数。
下边是其中部分代码:
1 task.normalizeMultiTaskFiles = function(data, target) { 2 // 省略的代码... 3 4 if ('src' in result) { 5 Object.defineProperty(result, 'src', { 6 enumerable: true, 7 get: function fn() { 8 var src; 9 if (!('result' in fn)) { 10 src = obj.src; 11 // If src is an array, flatten it. Otherwise, make it into an array. 12 src = Array.isArray(src) ? grunt.util._.flatten(src) : [src]; 13 // Expand src files, memoizing result. 14 fn.result = grunt.file.expand(expandOptions, src); 15 } 16 return fn.result; 17 } 18 }); 19 } 20 21 // 省略的代码... 22 23 return result; 24 };
3.4 分析grunt.file.expand源码:
在grunt的npm包中,找到file.js文件,在里边可以找到file.expand函数的定义,在该函数的定义中我们终于可以看到对filter的判断和处理,可以看到对filter是函数和字符时不同的处理。
下边是部分关键代码:
1 file.expand = function() { 2 // 省略的代码... 3 4 if (options.filter) { 5 matches = matches.filter(function(filepath) { 6 filepath = path.join(options.cwd || '', filepath); 7 try { 8 if (typeof options.filter === 'function') { 9 return options.filter(filepath); 10 } else { 11 // If the file is of the right type and exists, this should work. 12 return fs.statSync(filepath)[options.filter](); 13 } 14 } catch(e) { 15 // Otherwise, it's probably not the right type. 16 return false; 17 } 18 }); 19 } 20 return matches; 21 };
说明:本人实际分析源码过程是倒着来的,首先在grunt包中搜到filter判断的地方,然后一步一步查找方法的调用链。
参考资料&内容来源:
Grunt官网:https://www.gruntjs.net/configuring-tasks