grunt的插件机制
task.loadNpmTasks = function(name) { var root = path.resolve('node_modules'); var tasksdir = path.join(root, packagename, 'tasks'); //加载grunt 插件的tasks if (grunt.file.exists(tasksdir)) { loadTasks(tasksdir); } else { grunt.log.error('Local Npm module "' + name + '" not found. Is it installed?'); } }
1、插件中使用插件
有时我们需要在插件中使用插件来充分利用package资源, 但是由于插件通常会再被其它package使用,tasksdir通常会定位到使用插件的package下的tasks; 所以就会报错找不到相关packageName的tasks。
那么我们可以通过在插件中重新定义 loadNpmTasks 方法来实现在当前package下加载插件, 如:
//grunt查找插件的规则是通过path.join(G) var loadNpmTasks = function(name) { var cwd = process.cwd(); var baseDir = path.resolve(__dirname, '..'); grunt.file.setBase(baseDir); //packdir必须使用相对路径 grunt.loadNpmTasks(name); grunt.file.setBase(cwd); };
2、插件中文件的处理
可以通过 grunt.file.setBase(cwd) 来设计,文件操作的cwd
3、任务名称覆盖问题
如果在插件中使用到一些非常常用的第三方插件, 如grunt-contrib-clean、grunt-contrib-copy等插件,有可能在使用插件的package中也会用到, 所以一定不要覆盖使用插件的package的grunt配置,也不能merge配置, merge会导致执行顺序的修改。
举个例子:
grunt task build 的gruntfile //package build grunt.initConfig({ "build-xxx": { //build-xxx是一个插件提供的task的配置 }, "clean": { //使用插件 grunt-contrib-clean } }); //package build-xxx 中的tasks/build-xxx.js grunt.registerTask('build-xxx', function() { //如下会覆盖掉package build中的grunt config clean, 导致task clean不可用。 grunt.initConfig({ "clean": {}, "copy": {} }); //merge会导致执行顺序被修改 grunt.config.merge({clean: {}}); grunt.task.run(["clean", "copy"]) });
以上两种方式都会导致用户在使用grunt插件时,得到和预期不一致的结果,但是一个好的插件是不应该让使用者关心下层的实现的, so,可以改进如下:
在build-xxx的tasks/build-xxx.js中增加一个cleanConfig的任务 var originGruntConfig = grunt.config.data; grunt.initConfig({ "clean": {}, "copy": {}, "cleanConfig": {} }); grunt.registerTask('cleanConfig', function() { grunt.initConfig(originGruntConfig); });
为什么要使用新增任务cleanConfig,而不是直接在 grunt.task.run(["clean", "copy"]) 执行后将配置修改过来呢, 是因为grunt的run是基于任务队列运行, 不是及时同步完成。 我们需要在run结束后在将原有的config给同步过来。
4、task中的异步处理回调问题
如果在task的function body中存在异步调用, 这个问题和插件开发没什么关心,不过在用grunt写工具时需要注意一下
setTimeout(function() { console.log('enter'); }, 2000);
那么在grunt中是默认不会执行的, 原因grunt的tasks 队列运行机制, 队列中的tasks运行完之后会执行exit, 退出当前进程;
do { thing = this._queue.shift(); } while (thing === this._placeholder || thing === this._marker); // If queue was empty, we're all done. if (!thing) { this._running = false; if (this._options.done) { process.exit(0); } return; } }
可以使用grunt提供的 this.async()方法, 在异步回调结束后调用生成的done行数,已确保该异步调用能在下一个task之前执行, 如:
var done = this.async(); setTimeout(function() { //do something; done(true); }, 2000);