Break out task running functionality into a lib. Start on nospawn option.

This commit is contained in:
Kyle Robinson Young 2013-02-13 09:47:48 -08:00
parent 09586abc0c
commit 8572348cea
8 changed files with 183 additions and 63 deletions

View File

@ -43,3 +43,6 @@ Yes. Although `grunt-contrib-watch` is a replacement watch task for Grunt v0.4,
## Why is the watch devouring all my memory?
Likely because of an enthusiastic pattern trying to watch thousands of files. Such as `'**/*.js'` but forgetting to exclude the `node_modules` folder with `'!node_modules/**/*.js'`. Try grouping your files within a subfolder or be more explicit with your file matching pattern.
## Why spawn as child processes as a default?
TODO: Write this.

View File

@ -12,6 +12,12 @@ Type: `String|Array`
This defines which tasks to run when a watched file event occurs.
## options.nospawn
Type: `boolean`
Default: false
TODO: Write this.
## options.interrupt
Type: `boolean`
Default: false

77
tasks/lib/taskrun.js Normal file
View File

@ -0,0 +1,77 @@
var grunt = require('grunt');
var taskrun = module.exports = {
waiting: 'Waiting...',
cliArgs: null,
nameArgs: null,
changedFiles: Object.create(null)
};
// Do this when watch has completed
function completed(start) {
grunt.log.writeln('').write(String(
'Completed in ' +
Number((Date.now() - start) / 1000).toFixed(2) +
's at ' +
(new Date()).toString()
).cyan + ' - ' + taskrun.waiting);
}
// Do this when watch has been triggered
function triggered() {
grunt.log.ok();
Object.keys(taskrun.changedFiles).forEach(function(filepath) {
// Log which file has changed, and how.
grunt.log.ok('File "' + filepath + '" ' + taskrun.changedFiles[filepath] + '.');
});
// Reset changedFiles
taskrun.changedFiles = Object.create(null);
}
// Keep track of spawned processes
var spawned = Object.create(null);
taskrun.spawn = grunt.util._.debounce(function(id, tasks, options, done) {
// If interrupted, reset the spawned for a target
if (options.interrupt && typeof spawned[id] === 'object') {
grunt.log.writeln('').write('Previously spawned task has been interrupted...'.yellow);
spawned[id].kill('SIGINT');
delete spawned[id];
}
// Only spawn one at a time unless interrupt is specified
if (!spawned[id]) {
triggered();
// Spawn the tasks as a child process
var start = Date.now();
spawned[id] = grunt.util.spawn({
// Spawn with the grunt bin
grunt: true,
// Run from current working dir and inherit stdio from process
opts: {cwd: process.cwd(), stdio: 'inherit'},
// Run grunt this process uses, append the task to be run and any cli options
args: grunt.util._.union(tasks, taskrun.cliArgs)
}, function(err, res, code) {
// Spawn is done
delete spawned[id];
completed(start);
});
}
}, 250);
taskrun.nospawn = grunt.util._.debounce(function(id, tasks, options, done) {
// todo: add interrupt
triggered();
// Mark tasks to run and enqueue this task afterward
var start = Date.now();
grunt.task.run(tasks).mark().run(taskrun.nameArgs);
// todo: check if subsequent runs and run completed with start at watch beginning
completed(start);
// Finish the task
done();
}, 250);

View File

@ -10,12 +10,13 @@ module.exports = function(grunt) {
'use strict';
var path = require('path');
var fs = require('fs');
var Gaze = require('gaze').Gaze;
var taskrun = require('./lib/taskrun');
// Default options for the watch task
var defaults = {
interrupt: false
interrupt: false,
nospawn: false
};
grunt.registerTask('watch', 'Run predefined tasks whenever watched files change.', function(target) {
@ -40,69 +41,15 @@ module.exports = function(grunt) {
targets.push({files: watch.files, tasks: watch.tasks});
}
// Message to display when waiting for changes
var waiting = 'Waiting...';
// File changes to be logged.
var changedFiles = Object.create(null);
// Keep track of spawns per tasks
var spawned = Object.create(null);
// List of changed / deleted file paths.
grunt.file.watchFiles = {changed: [], deleted: [], added: []};
// This task's name + optional args, in string format.
taskrun.nameArgs = this.nameArgs;
// Get process.argv options without grunt.cli.tasks to pass to child processes
var cliArgs = grunt.util._.without.apply(null, [[].slice.call(process.argv, 2)].concat(grunt.cli.tasks));
taskrun.cliArgs = grunt.util._.without.apply(null, [[].slice.call(process.argv, 2)].concat(grunt.cli.tasks));
// Call to close this task
var done = this.async();
grunt.log.write(waiting);
// Run the tasks for the changed files
var runTasks = grunt.util._.debounce(function runTasks(i, tasks, options) {
// If interrupted, reset the spawned for a target
if (options.interrupt && typeof spawned[i] === 'object') {
grunt.log.writeln('').write('Previously spawned task has been interrupted...'.yellow);
spawned[i].kill('SIGINT');
delete spawned[i];
}
// Only spawn one at a time unless interrupt is specified
if (!spawned[i]) {
grunt.log.ok();
var fileArray = Object.keys(changedFiles);
fileArray.forEach(function(filepath) {
// Log which file has changed, and how.
grunt.log.ok('File "' + filepath + '" ' + changedFiles[filepath] + '.');
});
// Reset changedFiles
changedFiles = Object.create(null);
// Spawn the tasks as a child process
var start = Date.now();
spawned[i] = grunt.util.spawn({
// Spawn with the grunt bin
grunt: true,
// Run from current working dir and inherit stdio from process
opts: {cwd: process.cwd(), stdio: 'inherit'},
// Run grunt this process uses, append the task to be run and any cli options
args: grunt.util._.union(tasks, cliArgs)
}, function(err, res, code) {
// Spawn is done
delete spawned[i];
var msg = String(
'Completed in ' +
Number((Date.now() - start) / 1000).toFixed(2) +
's at ' +
(new Date()).toString()
).cyan;
grunt.log.writeln('').write(msg + ' - ' + waiting);
});
}
}, 250);
grunt.log.write(taskrun.waiting);
targets.forEach(function(target, i) {
if (typeof target.files === 'string') {
@ -118,7 +65,7 @@ module.exports = function(grunt) {
var options = grunt.util._.defaults(target.options || {}, defaults);
// Create watcher per target
var gaze = new Gaze(patterns, options, function(err) {
new Gaze(patterns, options, function(err) {
if (err) {
grunt.log.error(err.message);
return done();
@ -127,8 +74,8 @@ module.exports = function(grunt) {
// On changed/added/deleted
this.on('all', function(status, filepath) {
filepath = path.relative(process.cwd(), filepath);
changedFiles[filepath] = status;
runTasks(i, target.tasks, options);
taskrun.changedFiles[filepath] = status;
taskrun[options.nospawn ? 'nospawn' : 'spawn'](i, target.tasks, options, done);
});
// On watcher error

45
test/fixtures/nospawn/Gruntfile.js vendored Normal file
View File

@ -0,0 +1,45 @@
module.exports = function(grunt) {
'use strict';
var http = require('http');
var port = 8080;
grunt.initConfig({
watch: {
nospawn: {
files: ['lib/nospawn.js'],
tasks: ['server'],
options: {
nospawn: true
}
},
spawn: {
files: ['lib/spawn.js'],
tasks: ['server']
}
}
});
// Load this watch task
grunt.loadTasks('../../../tasks');
// Our test server task
var server;
grunt.registerTask('server', function() {
if (!server) {
server = http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('It works!');
}).listen(port);
grunt.log.writeln('Server is listening...');
} else {
http.request({port: port}, function(res) {
res.on('data', function(buf) {
grunt.log.writeln(buf);
});
}).end();
}
});
grunt.registerTask('default', ['server', 'watch']);
};

1
test/fixtures/nospawn/lib/nospawn.js vendored Normal file
View File

@ -0,0 +1 @@
var nospawn = true;

1
test/fixtures/nospawn/lib/spawn.js vendored Normal file
View File

@ -0,0 +1 @@
var spawn = true;

View File

@ -0,0 +1,40 @@
'use strict';
var grunt = require('grunt');
var path = require('path');
var fs = require('fs');
var helper = require('./helper');
var fixtures = helper.fixtures;
function cleanUp() {
helper.cleanUp([
'nospawn/node_modules'
]);
}
exports.nospawn = {
setUp: function(done) {
cleanUp();
fs.symlinkSync(path.join(__dirname, '../../node_modules'), path.join(fixtures, 'nospawn', 'node_modules'));
done();
},
tearDown: function(done) {
cleanUp();
done();
},
nospawn: function(test) {
//test.expect(2);
var cwd = path.resolve(fixtures, 'nospawn');
var assertWatch = helper.assertTask('watch', {cwd:cwd});
assertWatch(function() {
var write = 'var one = true;';
grunt.file.write(path.join(cwd, 'lib', 'one.js'), write);
}, function(result) {
helper.verboseLog(result);
//test.ok(result.indexOf('File "lib' + path.sep + 'one.js" changed') !== -1, 'Watch should have fired when oneTarget/lib/one.js has changed.');
//test.ok(result.indexOf('I do absolutely nothing.') !== -1, 'echo task should have fired.');
test.done();
});
}
};