running a dynamically named grunt task with grunt-contrib-watch - gruntjs

Is there a way to dynamically specify the task to run based on the file changed?
In other words:
watch: {
exec: {
files: [html/*.html],
tasks: ['exec:my_exec_task:THE_FILE_THAT_CHANGED']
}
}
I can catch the watch events, but I can't run tasks from within the callback as it's "doing it wrong".
grunt.event.on('watch', function(action, filepath, target) {
if (target === 'exec') {
grunt.task.run('exec:my_exec_task:' + filepath); /* this doesn't work */
grunt.config('filepath', filepath); /* and neither does this, it's undefined in my exec task */
}
});
At least that's what the documentation says: https://github.com/gruntjs/grunt-contrib-watch#using-the-watch-event
Any ideas?

According to docs
The watch event is not intended for replacing the standard Grunt API for configuring and running tasks. If you're trying to run tasks from within the watch event you're more than likely doing it wrong. Please read configuring tasks.
You can't run task from event, but you can change config before task starts. Add to watch config options section spawn: false
grunt.initConfig({
watch: {
scripts: {
files: ['app/*.js'],
tasks: ['jshint:one'],
options: {
spawn: false,
},
},
},
jshint: {
one: {src: ""},
},
});
and on watch event change config "on fly"
grunt.event.on('watch', function(action, filepath) {
grunt.config('jshint.one.src', filepath);
});
after applying config that watch section will run task jahint:one.

Related

How to combine multiple watch tasks for different compass tasks in Grunt

We are having some problems finetuning our Grunt setup. Our current project setup is like this. We have a Themes folder, in that themes folder there are different themes that all hold their own SCSS files and other bits related to that theme.
Our grunt file is setup like this with around 15 themes (leaving out default Grunt setup and JSHint because in the end Grunt is working):
compass: {
options: {
...
}
theme1: {
src: ['App/Themes/theme1/scss/**/*.scss'],
tasks: ['compass'],
options: {
sassDir: 'App/Themes/theme1/scss',
cssDir: 'App/Themes/theme1'
}
},
theme2: {
src: ['App/Themes/theme2/scss/**/*.scss'],
tasks: ['compass'],
options: {
sassDir: 'App/Themes/theme2/scss',
cssDir: 'App/Themes/theme2'
}
},
...
}
concurrent: {
watch: {
tasks: ['compass:theme1', 'compass:theme2', ..., 'compass:themeXX'],
options: {
logConcurrentOutput: true,
spawn: false
}
}
}
grunt.loadNpmTasks('grunt-concurrent');
grunt.loadNpmTasks('grunt-contrib-compass');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['concurrent']);
The actual issue then is that when we start the default task also x watch threads are started. Which have a lot of overhead for the small watch task they have to do.
The solution I'm looking for is a way to setup a single watch task that can trigger the specific theme compass compile. Is there a way to do that? Or is the current setup the only way to do it? So no other option than to have x watch tasks?
Thanks.
First, scaffold a watch task in your config object that watches files but doesn't execute any tasks. Using the glob pattern, tell the watcher to spy on all .scss files within the themes directory:
grunt.initConfig({
compress: {}, //your existing compress object goes here
watch: {
themes: {
files: ['App/Themes/**/*.scss'],
tasks: []
},
},
});
Next, you're going to add a grunt.event listener to your gruntfile. The listener event will expose the file changed (example: App/Themes/theme1/scss/foobar.scss). With that, you can now determine which compress target (theme1) to run:
grunt.event.on('watch', function(action, filepath, target) {
if (target === 'themes') {
var theme = filepath.split("/");
grunt.task.run('compress.' + theme[2]); //tells grunt to run "compress.theme1" based on this example
}
});

Grunt run shell after 'watch' eventListener changes filename

I'm trying to run shell command when file changes. Getting last changed file to use as argument for shell command. Here is the code:
grunt.initConfig({
changedFile: 'test',
watch: {
all: {
files: ['js/*.js'],
tasks: ['shell']
}
},
shell: {
run: {
// it outputs 'test', not changed file
command: 'touch <%= changedFile %>'
}
}
});
grunt.event.on('watch', function(action, filepath) {
grunt.config('changedFile', filepath);
});
'watch' eventListener actually works, but it does after shell command runs. How can I run task before event has been triggered?
options: { nospawn: true } for the watch task helped me on this.
Thanks guys from this thread: How to modify grunt watch tasks based on the file changed?

Are there global variables in grunt?

While trying to do some kind of generic grunt task i got stuck while accessing variables/options.
I need some kind of global variable or something similar.
It seems I have overlooked something.
If I run grunt sassCompile:myprojectFolder, everything works fine
while grunt sassWatch:myprojectFolder does not.
I run it in verbose mode and it seems projectPath is empty while compass is being called by watch.
compass options (from verbose output):
sassCompile: config="projectRoot/myprojectFolder/config.rb" ...
sassWatch: config="config.rb" ...
This is the Gruntfile.js is used for testing:
What I am doing wrong?
(function() {
'use strict';
module.exports = function (grunt) {
grunt.initConfig({
compass: {
dev: {
options: {
config: "<%= projectPath %>config.rb",
basePath: "<%= projectPath %>",
specify: ["<%= projectPath %>src/sass/style*.scss","!**/*ie*.scss"],
bundleExec: true
}
}
},
watch: {
css: {
files: ['<%= projectPath %>../**/*.scss'],
tasks: ['compass']
}
}
});
grunt.registerTask('sassCompile', 'compass', function (project) {
grunt.config('projectPath', 'projectRoot/' + project + '/');
grunt.task.run('compass');
});
grunt.registerTask('sassWatch', 'watch', function (project) {
grunt.config('projectPath', 'projectRoot/' + project + '/');
grunt.task.run('watch');
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-compass');
};
}());
This is definitely an interesting approach you have there – it took me a while to understand what is going on.
What happens when run grunt sassWatch:myprojectFolder is that the watch task gets started with the projectPath you supplied after the colon, but, when watch detects a change it runs the compass task without the configuration.
That is because grunt-contrib-watch runs it's tasks by starting a new grunt process. You could run the tasks inside the same process by using options: { spawn: false }, but that seems to be discouraged.
I would suggest you try this:
watch: {
css: {
files: ['<%= projectPath %>../**/*.scss'],
tasks: ['sassCompile:<%= projectPath %>']
}
}
That way watch will run sassCompile:myprojectFolder in the spawned grunt process, making sure the configuration gets transferred to the compass task.
At the grunt page there is a section about global variables, but they won't work across multiple processes.
It would be possible to start a new async process by hand and handover the needed variables.
You could take a look at here and possible extend it to accept parameters for the new processes.

Grunt - lint only modified files using grunt-newer

I'm running a Grunt task that uses Concurrent to run both Nodemon and Watch/Livereload. On default load, I lint and launch Concurrent. I would also like to set up a Watch to lint individual files on change. Currently, all files are linted when any one file is changed.
I have examined a similar question on StackOverflow and decided to go with grunt-newer as a potential solution. In my implementation below, however, the 'newer' prefix doesn't seem to do anything. How can I fix this so that only changed files are linted?
module.exports = function(grunt) {
//load all dependencies
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
concurrent: {
dev: {
options: {
logConcurrentOutput: true
},
tasks: ['watch', 'nodemon']
}
},
jshint: {
files: ['Gruntfile.js', 'client/src/*.js', 'server/**/*.js'],
options: {
'-W030': true,
'-W083': true,
globals: {
console: true,
module: true,
document: true
}
}
},
watch: {
all: {
files: ['<%= jshint.files %>'],
tasks: ['newer:jshint']
},
frontend: {
files: ['client/**/*.{css,js,html}'],
options: {
livereload: true
}
}
},
nodemon: {
dev: {
options: {
file: 'server/server.js',
watchedFolders: ['server']
}
}
}
});
grunt.registerTask('test', ['jshint']);
grunt.registerTask('default', ['jshint', 'concurrent']);
};
I was having the same problem and finally figured it out. The solution is hidden deep in the documentation and very misleading with the spawn option in the code sample: https://github.com/gruntjs/grunt-contrib-watch#compiling-files-as-needed
Your config file should remain the same as you have it in your question, but you need to add a listener to the watch event. I recommend the 'robust' option they provide (modified for you specific task config). Place this code just above the call to grunt.initConfig and after you require calls.
var changedFiles = Object.create(null);
var onChange = grunt.util._.debounce(function() {
// Modified to point to jshint.files as per the task example in the question.
grunt.config('jshint.files', Object.keys(changedFiles));
changedFiles = Object.create(null);
}, 200);
grunt.event.on('watch', function(action, filepath) {
changedFiles[filepath] = action;
onChange();
});
Add the nospawn option to the all watch task. This is what is misleading in in the documentation. It mentions it should be disabled if you want dynamically modify your config but basically prevents it from working with newer unless it's set to true:
watch: {
all: {
files: ['<%= jshint.files %>'],
tasks: ['newer:jshint'],
options: {
nospawn: true,
}
},
...
NOTE: If you modify your grunt file while it's running then it will lint all files, not sure why it does this but then it gets stuck and will just keep linting everything for all changes you make. I just took out the 'gruntfile.js' from the list of files that should be linted to avoid it.

grunt: watch command never runs when including other tasks in registerTask method

I'm having some unexpected behavior with my Gruntfile. I've registered a task that looks like this: grunt.registerTask('dev', ['jekyll:server', 'watch:jekyll']) with the hopes that it will sequentially start a jekyll server, and then watch my project for specific file changes (using the grunt-contrib-watch plugin). Once it detects those changes, it would re-run jekyll:server automatically.
The problem I'm having is that when I run grunt dev, it will start the Jekyll server, but it will not run the watch commands. However, if I remove the server task from grunt dev, it will run the watch command as expected.
Below is the contents of my Gruntfile. Can anyone help me understand what is happening?
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
jekyll: {
server : {
server: true,
server_port: 4000,
exclude: ['node_modules']
},
prod: {
dest: './_site-release'
}
},
watch: {
jekyll: {
files: ['_posts/**/*.md', '_layout/*.html', '_includes/*.html', 'index.html'],
tasks: ['jekyll:server']
}
}
});
grunt.loadNpmTasks('grunt-jekyll');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', 'jekyll:server');
grunt.registerTask('dev', ['jekyll:server', 'watch:jekyll']);
grunt.registerTask('release', 'jekyll:prod');
};
The server option makes the task block since it's persistent. You can either use the tasks watch option or something like grunt-concurrent to run jekyll and watch concurrently:
grunt.initConfig({
concurrent: {
target: {
tasks: ['jekyll:server', 'watch'],
options: {
logConcurrentOutput: true
}
}
}
});
grunt.loadNpmTasks('grunt-concurrent');
grunt.registerTask('default', ['concurrent:target']);

Resources