Grunt学习笔记【3】---- filter使用方式和实现原理详解

本文主要讲配置任务中的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 taskconcat 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

原文地址:https://www.cnblogs.com/zhaoweikai/p/9713528.html