tschaub / grunt-newer

Configure Grunt tasks to run with newer files only.
https://npmjs.org/package/grunt-newer
MIT License
945 stars 53 forks source link

Using newer to compile parent Stylus files #18

Closed stinoga closed 9 years ago

stinoga commented 10 years ago

Trying to use grunt-newer on a large enterprise site using Stylus, so that we only compile the files necessary, rather than all stylus files every time one is changed.

This is working great, however if a .styl file is updated that is imported into parent files, we need to update those as well. Any ideas on how to accomplish that without touching every file that has an @import call?

tschaub commented 10 years ago

That's a tough one. So you've got some imports that come from your stylus options.paths, and you want to selectively compile your dest css based on src files that have modified imports?

The grunt-newer task only works with a multi-task's files config (which may be generated by separate src and dest properties if files is not present). It doesn't reach out into other tasks' options (like paths). So I don't think this is going to be possible.

The only general solution I can think of right now (related to grunt-newer) would be to add an isNewer option that would be called for every source file considered. Then if the return from isNewer was true or the source file had been modified since the corresponding dest file (or last successful run if no dest file), it would be included. But this would require that you supply an isNewer function that parsed your @imports and decided if the resolved imports had been modified. Sounds like too much work.

tschaub commented 10 years ago

To be clear, here's what the isNewer option might look like:

grunt.initConfig({
  newer: {
    options: {
      isNewer: function(taskName, targetName, srcPath, time, callback) {
        if (taskName === 'stylus') {
          resolveImports(srcPath, function(imports) {
            anyNewer(imports, time, callback);
          });
        }
      }
    }
  } // ... other task config
});

And you would have to provide the resolveImports (call callback with paths to @imported files) and anyNewer (call callback with true if any of the provided files have mtimes more recent than time) functions. They wouldn't have to be async, but it still seems like more pain than gain perhaps.

stinoga commented 10 years ago

Hmmmm... that could work though, and may still be a good bit faster that compiling every stylus file. I'll give it a look. Thanks Tim!

tschaub commented 10 years ago

Note that the code above won't work currently. I was just thinking about what it might look like if the isNewer option were implemented (it is not yet). Just trying to gage if this sounded like a potential solution.

tschaub commented 10 years ago

@stinoga if you want to experiment, you can replace your grunt-newer install with this branch: https://github.com/tschaub/grunt-newer/tree/is-newer (maybe easiest if you manually remove node_modules/grunt-newer, and edit your package.json to have grunt-newer point to https://github.com/tschaub/grunt-newer#is-newer).

Note that the signature for the isNewer function is (taskName, targetName, srcPath, time, callback). That's a bit awkward, but it should give you what you need.

ghost commented 10 years ago

:+1: Having the same issue with LESS, and this type of generic solution could work quite well.

samtsai commented 10 years ago

@tschaub I'm looking into an implementation of what you suggested above. Apart from writing the @import parsing function, how do I get access to the anyNewer function:

resolveImports(srcPath, function(imports) {
   anyNewer(imports, time, callback);
});

I'm not understanding the relationship of how to access the util library within the grunt-newer module inside of my own project's Gruntfile.js.

tschaub commented 10 years ago

@samtsai with the isNewer option, you'd have to provide something like resolveImports and anyNewer. Here's an quick (untested) implementation of an anyNewer function that takes an array of paths and calls the callback with true if any of the files has an mtime greater than the provided time.

var fs = require('fs');
var async = require('async');

function newer(pathname, time, callback) {
  fs.stat(pathname, function(err, stats) {
    callback(stats && stats.mtime > timestamp);
  });
}

function anyNewer(paths, time, callback) {
  async.some(paths, newer, callback);
}

Since this would be a common pattern, I'll see about supporting a different option - called something like additionalSrc. That would take a function like the resolveImports above. Then the task itself could do the timestamp comparison.

yanivtal commented 10 years ago

This would be huge. Just switched to grunt and newer falls down with stylus without this addition. Recompiling all the files is a massive hit on the workflow so it would be great to have this.

tschaub commented 10 years ago

See #35 for a new proposed solution.