Incorrect path for generated coffee sourcemaps when serving with nodejs

I'm using coffeescript to do some work. The coffeescript is compiled to js with grunt and served with a simple nodejs express app.

My folder structure does follow the common one with a assets folder for things to be compiled (coffeescript, stylus) and public folder with the compiled stuff (js, css):

/
-- assets /
          -- coffee /
                    -- lib /
                           -- util.coffee
                    -- main.coffee
          -- styl
-- public /
          -- css
          -- js /
                -- lib /
                       -- util.js
                -- main.js

My coffee setup in grunt is:

coffee:
  client:
    options:
      sourceMap: true
      #sourceRoot: '??'
    files: [
      expand:   true
      cwd:      'assets/coffee/'
      src:      ['**/*.coffee']
      dest:     'public/js/'
      ext:      '.js'
    ]

To serve files from the assets dir I added this to be a static directory in my express app:

app.use express.static(process.cwd() + '/assets')

Chrome correctly recognizes that the are source maps but the location to the coffee files are wrong. For example a url looks like http://localhost:3000/assets/coffee/main.coffee. Of course this results in a 404 because assets is the root for all coffee files and is served by my express app.

So I need to adjust the sourceRoot variable.

  • If I set sourceRoot to sourceRoot: '/assets/', Chrome generates links to http://localhost:3000/assets/main.coffee.
  • If I set it to sourceRoot: '/coffee/' the link is http://localhost:3000/coffee/main.coffee. This works for files in assets/coffee/. Files in a subdirectory of assets/coffee/ like assets/coffee/lib/ aren't found (the generated link is http://localhost:3000/coffee/util.coffee)

Setting the sourceRoot option seems to remove the folder structure?!

Long question short: What's the correct setting for sourceRoot? How can I preserve the folder structure?


I filed this issue as a possible bug report: https://github.com/jashkenas/coffee-script/issues/3075

This appears to actually be a bug in the CoffeeScript Grunt task.

See: https://github.com/gruntjs/grunt-contrib-coffee/blob/master/tasks/coffee.js#L87

options = _.extend({
    generatedFile: path.basename(paths.dest),
    sourceRoot: mapOptions.sourceRoot,
    sourceFiles: mapOptions.sourceFiles
  }, options);

Here, if the options object has a "sourceRoot" element, it will override the generated sourceRoot which is created by this function:

https://github.com/gruntjs/grunt-contrib-coffee/blob/master/tasks/coffee.js#L130

var createOptionsForFile = function (file, paths) {
  return {
    code: grunt.file.read(file),
    sourceFiles: [path.basename(file)],
    sourceRoot: appendTrailingSlash(path.relative(paths.destDir, path.dirname(file)))
  };
};

Which uses the relative path from the destination directory to where the source file is (which would probably work if you mapped things like /js to /public/js instead, but for you there will be an extra ../ in the path).

For your case, it might work if you modified the code so that you replaced the options = _.extend({... code with something like:

var newRoot = undefined
if (options.sourceRoot) newRoot = appendTrailingSlash(path.join(options.sourceRoot, path.dirname(file)));
options = _.extend({
    generatedFile: path.basename(paths.dest),
    sourceRoot: mapOptions.sourceRoot,
    sourceFiles: mapOptions.sourceFiles
  }, options);
if (newRoot) options.sourceRoot = newRoot;

I think that will work because file should be relative to your cwd setting.

If that change to the Grunt task works, it would be worth making a cleaner version and submitting a pull request, because I think if your .coffee files are in a directory tree, that should be reflected in your sourcemaps' sourceRoot property.