9

Now I have my Gruntfile setup to perform some automatic detection magic like parsing sourcefiles to parse some PHP sources in roder to dynamically figure out filenames and paths I need to know before running grunt.initConfig().

Unfortunately grunt.initConfig() doesn't seem to be meant to be run asynchronously, so I see no way to have my asynchronous code executed before I can call it. Is there a trick to accomplish this or do I have to rewrite my detection routines synchronously? Is there any easy way to block execution before my callback has arrived?

Inside grunt tasks there is of course this.async(), but for initConfig() that doesn't work.

Here's a stripped down example:

function findSomeFilesAndPaths(callback) {
  // async tasks that detect and parse
  // and execute callback(results) when done
}

module.exports = function (grunt) {
  var config = {
    pkg: grunt.file.readJSON('package.json'),
  }

  findSomeFilesAndPaths(function (results) {
    config.watch = {
      coffee: {
        files: results.coffeeDir + "**/*.coffee",
        tasks: ["coffee"]
         // ...
      }
    };

    grunt.initConfig(config);

    grunt.loadNpmTasks "grunt-contrib-coffee"
    // grunt.loadNpmTasks(...);
  });
};

Any good ideas how to get this done?

Thanks a lot!

leyyinad
  • 380
  • 3
  • 19
  • What happens if you just put grunt.initconfig and grunt.loadnpmtasks etc in the callback from the async function? – Andreas Hultgren May 14 '13 at 15:55
  • Isn't that what I did above? What happens is that grunt does not wait for my callback, so `grunt.initConfig()` etc. will not be called before the grunt client returns. – leyyinad May 14 '13 at 15:58

3 Answers3

6

I would do it as a task since Grunt is sync or if you can make findSomeFilesAndPaths sync.

grunt.initConfig({
  initData: {},
  watch: {
    coffee: {
      files: ['<%= initData.coffeeDir %>/**/*.coffee'],
      tasks: ['coffee'],
    },
  },
});

grunt.registerTask('init', function() {
  var done = this.async();
  findSomeFilesAndPaths(function(results) {
    // Set our initData in our config
    grunt.config(['initData'], results);
    done();
  });
});

// This is optional but if you want it to
// always run the init task first do this
grunt.renameTask('watch', 'actualWatch');
grunt.registerTask('watch', ['init', 'actualWatch']);
Kyle Robinson Young
  • 13,732
  • 1
  • 47
  • 38
  • Thank you very much Kyle, your solution is very helpful for that minimal example. In fact I might have multiple directories for scripts and stylesheets and lots of other tasks which will only be known at runtime, like automatic downloads and decompression (all of this did work fine with rake, but for various reasons I want to switch to grunt). I might get this to work this way, but it would be much easier and cleaner to have the whole config object ready before calling `grunt.initConfig()`. Can this be accomplished? – leyyinad May 14 '13 at 18:21
  • Since Grunt is sync you unfortunately can't without writing your own grunt-cli; which would be more cumbersome than the above solution, imo. – Kyle Robinson Young May 14 '13 at 18:37
  • I guess you're right. Just opened an [issue](https://github.com/gruntjs/grunt/issues/783) on GitHub, anyway. – leyyinad May 14 '13 at 18:43
  • Rather than replacing existing tasks that rely on the config data, you can just add `grunt.task.run('init')` to the script, to ensure it runs before Grunt starts running any tasks. – bdukes Aug 29 '13 at 18:03
2

Solved by rewriting, synchronous style. ShellJS came in handy, especially for synchronously executing shell commands.

leyyinad
  • 380
  • 3
  • 19
1

Example of how you could use ShellJS in Grunt:

grunt.initConfig({
    paths: {
        bootstrap: exec('bundle show bootstrap-sass').output.replace(/(\r\n|\n|\r)/gm, '')
    },
    uglify: {
        vendor: {
            files: { 'vendor.js': ['<%= paths.bootstrap %>/vendor/assets/javascripts/bootstrap/alert.js']
        }
    }
});
jgillich
  • 71,459
  • 6
  • 57
  • 85