10

I'd like to run coffee lint and coffee compile on only the single file that I'm saving. There are hundreds of CoffeeScript files in my project, and compiling all of them takes too much time.

Here's my Gruntfile:

module.exports = (grunt) ->

  grunt.initConfig

    pkg: grunt.file.readJSON 'package.json'

    coffee:
      all:
        expand: true
        bare: true
        cwd: 'src/coffeescript/'
        src: '**/*.coffee'
        dest: 'public/js/compiled'
        ext: '.js'

    coffeelint:
      all: ['src/coffeescript/**/*.coffee']

    watch:
      coffeescript:
        files: ['src/**/*.coffee']
        tasks: ['coffeelint', 'coffee']
        options:
          spawn: false

  grunt.event.on 'watch', (action, filepath) ->
    grunt.config(['coffeelint', 'all'], filepath)
    grunt.config(['coffee', 'all'], filepath)

  grunt.loadNpmTasks 'grunt-coffeelint'
  grunt.loadNpmTasks 'grunt-contrib-coffee'
  grunt.loadNpmTasks 'grunt-contrib-watch'

  grunt.registerTask 'default', ['coffeelint', 'coffee', 'watch']

The coffeelint task runs successfully only on the changed file.

The coffee compilation doesn't produce any JS files, even though grunt says it runs.

Here's the output after saving a single coffee file:

OK
>> File "src/coffeescript/app.coffee" changed.


Running "coffeelint:all" (coffeelint) task
>> 1 file lint free.

Running "coffee:all" (coffee) task

Running "watch" task
Completed in 0.009s at Sat Feb 01 2014 13:10:07 GMT-0600 (CST) - Waiting...

What's wrong here? Any help would be greatly appreciated!

Update:

Here's a working example:

module.exports = (grunt) ->

  fs = require 'fs'
  isModified = (filepath) ->
    now = new Date()
    modified =  fs.statSync(filepath).mtime
    return (now - modified) < 10000

  grunt.initConfig

    coffee:
      options:
        sourceMap: true
        bare: true
        force: true # needs to be added to the plugin
      all:
        expand: true
        cwd: 'src/coffeescript/'
        src: '**/*.coffee'
        dest: 'public/js/compiled'
        ext: '.js'
      modified:
        expand: true
        cwd: 'src/coffeescript/'
        src: '**/*.coffee'
        dest: 'public/js/compiled'
        ext: '.js'
        filter: isModified

    coffeelint:
      options:
        force: true
      all:
        expand: true
        cwd: 'src/coffeescript/'
        src: '**/*.coffee'
      modified:
        expand: true
        cwd: 'src/coffeescript/'
        src: '**/*.coffee'
        filter: isModified

    watch:
      coffeescript:
        files: ['src/**/*.coffee']
        tasks: ['coffeelint:modified', 'coffee:modified']

  grunt.loadNpmTasks 'grunt-coffeelint'
  grunt.loadNpmTasks 'grunt-contrib-coffee'
  grunt.loadNpmTasks 'grunt-contrib-watch'

  grunt.registerTask 'default', ['coffeelint:all', 'coffee:all', 'watch']
Eric the Red
  • 5,364
  • 11
  • 49
  • 63

4 Answers4

10

You can use grunt-newer to only compile files when the source is newer than its compiled output.

Install it with:

npm install grunt-newer --save-dev

Then change your coffeescript task a bit. If you go to section on "Building the files object dynamically" in the Grunt.js docs you'll see that you need to hold the information related to file location and location of compiled output in a files array for the task to execute correctly.

Additional options, like bare: true, can then be specified with an options object.

So for your coffee task, do this instead:

   coffee:
      all:
        files: [{
          expand: true
          cwd: 'src/coffeescript/'
          src: '**/*.coffee'
          dest: 'public/js/compiled'
          ext: '.js'
        }]
        options:
          bare: true
          spawn: false

then use newer in your watch task like so:

    watch:
      coffeescript:
        files: ['src/**/*.coffee']
        tasks: ['newer:coffeelint:all', 'newer:coffee:all']
        options:
          spawn: false

Newer will then only compile files that are more recent than their compiled versions.

agconti
  • 17,780
  • 15
  • 80
  • 114
4

Try to add something like this to your c task

 coffee:
  all:
    filter: (filepath) ->
        fs = require('fs')
        now = new Date()
        modified =  fs.statSync(filepath).mtime
        return (now - modified) < 10000 # or another difference in millyseconds

Read more in documentation

kharandziuk
  • 12,020
  • 17
  • 63
  • 121
0

There are two problems here.

First:

Grunt produces changed file path as src/coffeescript/some_file.coffee, but in your coffee task you have defined cwd as src/coffeescript.

So the coffee task is actually expecting only some_file.coffee but it is getting src/coffeescript/some_file.coffee, and then expanding it to src/coffeescript/src/coffeescript/some_file.coffee O_o

Your coffeelint task doesn't have any cwd so it runs successfully with the full file path.

Adjust the file path accordingly, or remove the cwd from the coffee configuration.

grunt.config(['coffee', 'all'], filepath.replace("src/coffeescript", ""))

Second:

This one is minor, and may not be required, but watch out for this as well.

grunt.event.on 'watch', (action, filepath) ->
  grunt.config(['coffeelint', 'all'], filepath)
  grunt.config(['coffee', 'all'], filepath)
  ^^^^^^^ You might have to change above line to:
  grunt.config(['coffee', 'all', 'src'], filepath)

In your coffeelint task, sources are directly set under all. But in your coffee task, sources are set under all -> src. Reflecting the same might also be useful.

EDIT: Minor addendum

Subhas
  • 14,290
  • 1
  • 29
  • 37
  • First: >> File "src/coffeescript/app.coffee" changed. Running "coffeelint:files" (coffeelint) task >> 1 file lint free. Running "coffee:files" (coffee) task Warning: Unable to read "undefined" file (Error code: ENOENT). – Eric the Red Feb 10 '14 at 20:23
  • Second: no JavaScript files are created. – Eric the Red Feb 10 '14 at 20:27
0

Instead of using an arbitrary duration threshold, you could store each modification time in a map:

fs = require 'fs'
mtimes = {}
isModified = (filepath) ->
    mtime = fs.statSync(filepath).mtime
    if mtimes[filepath]?
        return mtimes[filepath] < mtime
    else
        mtimes[filepath] = mtime
        return yes

It's too bad though that grunt does not provide the destination file path as a second argument to the filter function. This would enable a simple check to see if the generated file exists and is older than the source… Make does that out of the box…

David Bonnet
  • 1,168
  • 11
  • 11