Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asynchronous configuration #783

Open
leyyinad opened this issue May 14, 2013 · 20 comments
Open

Asynchronous configuration #783

leyyinad opened this issue May 14, 2013 · 20 comments

Comments

@leyyinad
Copy link

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!

@mariocasciaro
Copy link

+1

1 similar comment
@liuyicheng
Copy link

+1

@jpillora
Copy link

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'})

@hjdivad
Copy link

hjdivad commented Sep 19, 2013

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)?

@hjdivad
Copy link

hjdivad commented Sep 19, 2013

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.

@jpillora
Copy link

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

@prettyboymp
Copy link

+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.

@asperling
Copy link

+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

@NickHeiner
Copy link

👍 It would be awesome if grunt.initConfig accepted a promise for a config object.

@dnutels
Copy link

dnutels commented Dec 11, 2013

@NickHeiner

How would that work? The resolve would trigger the task?

@yuanyan
Copy link

yuanyan commented Dec 11, 2013

Because there is no grunt.start() drive the grunt, so must be one task registered at init time.

@yuanyan
Copy link

yuanyan commented Dec 11, 2013

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(...);
  });
};

@NickHeiner
Copy link

@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?

@dnutels
Copy link

dnutels commented Dec 16, 2013

@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.

@NickHeiner
Copy link

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.

@bardiharborow
Copy link

+1.

@masterspambot
Copy link

👍

1 similar comment
@JemarJones
Copy link

+1

@Qix-
Copy link

Qix- commented Oct 4, 2015

More than two years later and still not addressed. Huh.

@theoy
Copy link

theoy commented Jan 22, 2016

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests