yeoman/grunt-usemin

Creating sourcemap for built file

Closed this issue · 6 comments

When building out the uglify block; usemin sets the destination and source filenames to the same value: uglify[block.dest] = block.dest;.

This conflicts with building a source map via grunt-contrib-uglify, as the source and destination files are the same file. Since we populate the uglify config, do we want to specify a way to populate the config with a list of files (like what is pushed into concat)?

I came across this issue, too, and thought it would be helpful to outline it in a bit more detail.

I have my build:js block in _scripts.html:

<!-- build:js /scripts/main.js -->
<script src="scripts/foo.js"></script>
<script src="scripts/bar.js"></script>
<!-- endbuild -->

When I build with grunt, useminPrepare finds the block and updates the uglify configuration to:

uglify:
  { 'dist/scripts/main.js': 'dist/scripts/main.js' }

I want to create a source map for this uglified file so I add the options in Gruntfile.coffee:

uglify:
  options:
    sourceMap: 'dist/scripts/main-source.js'

Like @jgrund said, the source and destination files are the same. In my example, that's dist/scripts/main.js. It has already been uglified when it's being referenced as a source.

To work around this, I specified my own files object in a target of the uglify configuration, call that target specifically (uglify:dist), and hard-code that destination file in my template instead of including the build block.

Gruntfile.coffee:

useminPrepare:
  html: '<%= yeoman.app %>/_scripts.html'
...
uglify:
  dist:
    options:
      sourceMap: 'dist/scripts/main-source.js'
    files:
      'dist/scripts/m.js': 'dist/scripts/main.js'

...

grunt.registerTask 'build', [
  ...
  'uglify:dist'
  ...
]

layout.jade:

script(src="/scripts/m.js")

In this workaround, _scripts.html is only used for build purposes.

Thanks @maxbeatty for the workaround.

You can also override the URL that is generated by providing a value for uglify/options/sourceMappingURL. This can be useful to modify or remove the path prefix.

If you use uglify's sourceMap together with grunt-rev, you'll want to add '!dist/**/*-source.js' to the rev task's list of src files so that your -source.js file isn't renamed because main.js's sourceMappingURL comment will still be pointing at the original filename. This is necessary until usemin adds support for revving the filename in sourceMappingURL comments.

A simple workaround for getting uglify to do the concat and thus support source maps:

All you need to do is add this to your grunt file:

  grunt.registerTask('useminPatch', function () {
    var concat = grunt.config('concat');
    var uglify = grunt.config('uglify');
    for (var dest in concat) {
      if (uglify[dest] === dest) {
        uglify[dest] = concat[dest];
        delete concat[dest];
      }
    }
    grunt.config('concat', concat);
    grunt.config('uglify', uglify);
  });

Now make sure 'useminPatch' is run right after 'useminPrepare' and your are good to go...

Should be ok on v2.0 as you can change the workflow to have only concat/usemin (or a user defined tool) taken into consideration.

From what I can gather, v2.0 has resolved some of the issues with sourcemaps, but not all of them. A few remaining problems:

  1. It seems that it's not possible to have usemin generate more than one source map
  2. If you only need one map, all of your blocks reference that same map. You can't make just one block reference your map
  3. If you do want to generate a source map, it necessarily must be included everywhere in your project (relevant in the context of #206)

Let me explain these issues further.

Because of the fact that usemin places all of your blocks in the files array of the generated target, it's impossible to specify unique options for each object in the file array. Uglify happens to use the options to specify the source map generated, so, consequently, there can only be one source map for your entire application.

One instance I can see this being particularly important is when you have two sections of javascript: one that must load before the DOM (think Modernizr), and then everything else that you'd place at the end of the <body>. You would only be able to map one of these.

The second point follows naturally from this. If there's one set of options being applied to every block, and that configuration says to reference a source map, then all of the blocks will point to the same sourcemap (relative to themselves).

Number three refers to the inability for a user to configure uglify on a per-useminPrepare basis. This is a feature that #206 suggests will be added soon. To see what I mean, let's say I have three versions of my site: a src folder, a test folder that includes the sourcemap, and the prod folder that has no sourcemap. There is no way to specify to just use the sourcemap for test. Because useminPrepare always sets the target as generated, I can only apply the sourcemap options to that target or globally. This causes my sourcemap to appear in both the test and prod locations.

A possible solution would be allowing the user to configure the task at the target level. For example, instead of returning:

var cfg = {files:[]};

you might return:

var cfg = {
  someTargetName: {
    files: [],
    options: {
      sourceMap: 'build/js/someTargetName.map'
    }
  }
};

where responsibility is placed on the developer to ensure that the target names don't overlap.

Closing in favor of #220