Open leyyinad opened 11 years ago
+1
+1
You could try using https://github.com/gruntjs/grunt/wiki/grunt.task#grunttaskrun
grunt.registerTask('default', function() {
var done = this.async();
//do your async config...
setTimeout(function() {
//equivalent to running grunt foo bar on the cli
grunt.task.run(['foo','bar']);
done();
});
});
Untested, though should work
Note, you could use grunt.initConfig
within this default
task, however keep in mind, it will wipe all existing config. You can also do grunt.config('concat', {my:'concat-config'})
I'll give another example where this would be useful. I'm working on oasis.js, MessageChannel.js and conductor.js. They use components I install with bower. In fact, MessageChannel.js is a dependency of oasis.js which is a dependency of conductor.js.
I want to do things with the main bower files, like concatenate them and copy them. In particular I want to "concatenate all vendor files". I don't want to have to manage a list of manual paths that will change when upstream dependencies change. Fortunately, bower provides an api to deal with this. But it's asynchronous, so I can't easily add the output from bower's API to the conductor concat
configuration.
Should I be organising these grunt builds differently? Should this be a bower concern (ie to provide a synchronous API)?
It occurs to me that something i could do is use ShellJS and call bower list --json --paths
. Not sure if this would be considered the idiomatic way to do this in grunt.
Maybe the simplest solution for you would be to create an async run-bower
task, and always prepend that task whenever you run grunt or prepend it to all of your task aliases
If you do want to get fancy, you could modify the task queue, so run-bower
is always run first
Though these are just workarounds, as I think this is a legitimate issue, asynchronous config would come in handy, so the real solution is for grunt to implement a this.async()
API for configuration
+1
I have a similar issue where I'm using data from an API that provides environment data for specific instances of a project that is then used to configure speicific tasks. I haven't found a way around it other than storing a copy of the data locally.
+1
I'd like to be able to read data from files to init the grunting, see http://stackoverflow.com/questions/19831266/grunt-initconfig-in-a-callback-does-not-work/19836024
:+1: It would be awesome if grunt.initConfig
accepted a promise for a config object.
@NickHeiner
How would that work? The resolve
would trigger the task?
Because there is no grunt.start() drive the grunt, so must be one task registered at init time.
I think all should be as a grunt task inlcude the Gruntfile, then we could force Gruntfile into async mod using this.async()
:
function findSomeFilesAndPaths(callback) {
// async tasks that detect and parse
// and execute callback(results) when done
}
module.exports = function (grunt) {
// Force Gruntfile into async mode.
var done = this.async();
var config = {
pkg: grunt.file.readJSON('package.json'),
}
findSomeFilesAndPaths(function (results) {
config.watch = {
coffee: {
files: results.coffeeDir + "**/*.coffee",
tasks: ["coffee"]
// ...
}
done();
};
grunt.initConfig(config);
grunt.loadNpmTasks "grunt-contrib-coffee"
// grunt.loadNpmTasks(...);
});
};
@dnutels It could look something like this:
module.exports = function(grunt) {
var configPromise = getConfigBasedOnFileSystemContents();
// either this
configPromise.then(grunt.initConfig);
// or this
grunt.initConfig(configPromise);
// in this case, no grunt tasks would be run until configPromise is resolved.
};
Does that address what you're looking for?
@NickHeiner That's what I thought you had in mind. You'd need a finer granulation though - per task, probably, rather than per entire config. And without having to envelope all tasks in custom task and muck around with async
and what not.
I would be a little cautious about the finer granulation - if you see something like
grunt.initConfig({
copy: somePromise,
clean: someSettings
});
How do you know that grunt.config('copy')
is a promise for config, as opposed to just having the promise itself be the config value?
I think it would be clearer to just do it all at once:
q.all([getTaskAConfig, getTaskBConfig]).spread(function(taskAConfig, taskBConfig) {
grunt.initConfig({
'task-a': taskAConfig,
'task-b': taskBConfig,
'task-c': {
foo: 'bar'
}
});
});
Now there is no ambiguity.
+1.
:+1:
+1
More than two years later and still not addressed. Huh.
@NickHeiner @dnutels - an alternate way that I would expect for asynchronous config is adjust the handling for gruntfiles and task files. Gruntfiles export an function export that takes in a grunt object parameter, and when invocation of that export is complete, that file's processing is currently deemed 'complete'. However, we could also check to see if the exported function returned a promise, and condition completion on that.
For example, here is how it is today:
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig(...);
// Load tasks from a plugin.
grunt.loadNpmTasks(...);
// register some tasks
grunt.registerTask(...);
// when this function returns, processing is considered complete for this file
};
An async version could look like:
module.exports = function(grunt) {
return doSomethingAsync()
.then(function() {
// Project configuration.
grunt.initConfig(...);
})
.then(function() {
// Load tasks from a plugin.
grunt.loadNpmTasks(...);
})
.then(function() {
// register some tasks
grunt.registerTask(...);
});
// when the returned promise completes, processing is considered complete for this file
};
IMHO, this format is very recognisable to any programmer who is familiar with Promise-based asynchrony.
By the way, I was annoyed by this limitation because I wanted to dynamically generate targets to a multitask, and the steps needed to generate it were only available via async APIs. So instead, the tasks are not a multi-task, which is kind of sad that it can't be done via the natural grunt
idioms because of the limitation about asynchronous gruntfiles.
From an ecosystem approach, learning to respect returned promises is technically a breaking change - though probably small. I don't expect many gruntfiles intentionally return a Promise that was intended to be ignored/dropped the the floor by grunt.
The docs say that I can programatically setup my configuration when (or before) calling grunt.initConfig(). Now I do very complicated stuff to dynamically initialize grunt (figuring out paths by parsing PHP-sources, downloading and decompressing archives, ...).
The problem is that my setup routine works asynchronously and must be completed before I call grunt.initConfig(), which obviously doesn't support asynchronous execution. Is there a way to defer the call until my callback has been fired?
More on this here: http://stackoverflow.com/q/16547528/382597
Thank you!