gruntjssource-mapsgrunt-contrib-uglifygrunt-browserify

How do I generate sourcemaps for Uglified files using Grunt?


I have a Grunt project that uses both Browserify and Uglify. Here are the core bits of it:

browserify: {
  myapp: {
    options: {
      transform: ['babelify'],
      browserifyOptions: {
        debug: true
      },
    },
    src: 'src/index.js',
    dest: 'build/myapp.js'
  }
},

uglify: {
  options: {
    sourceMap: true,
    banner: bannerContent
  },
  target: {
    src: 'build/myapp.js',
    dest: 'build/myapp.min.js'
  }
},

It seems to generate a myapp.min.js.map file but it no longer has the raw sources in the source-map that existed prior to the Browserification.

Here's what the resultant source-map file contains:

{
  "version":3,
  "sources":[
    "myapp.js"
  ],
  "names":[
    ...
    ...
    ...
  ],
  "mappings":".........",
  "file":"myapp.min.js"
}

I've tried using the uglifyify transform for Browserify but that does not seem to generate as small files as the Uglify task.

I've also bumped all my dependencies to the latest but I haven't been able to resolve this issue.


Solution

  • grunt-contrib-uglify has a sourceMapIn option that allows you to specify the location of an input source map file from an earlier compilation - which in your scenario is the browserify task.

    However, whilst setting browserifyOptions: { debug: true } in your browserify task does generate an inline source map in the resultant .js file (i.e. in build/myapp.js), the crux of the problem is twofold:

    1. We don't have an external source map file that we can configure the sourceMapIn option of the subsequent grunt-contrib-uglify task to utilize.

    2. grunt-browserify doesn't provide a feature to create an external .map file, it only creates them inline (see here)

    To address the aforementioned issue consider utilizing grunt-extract-sourcemap to extract the inline source map from build/myapp.js (i.e. from the output file generated by your browserify task) after it has been produced.

    Gruntfile.js

    The following gist shows how your Gruntfile.js should be configured:

    module.exports = function (grunt) {
    
      grunt.initConfig({
    
          browserify: {
            myapp: {
              options: {
                transform: ['babelify'],
                browserifyOptions: {
                  debug: true
                },
              },
              src: 'src/index.js',
              dest: 'build/myapp.js'
            }
          },
    
          extract_sourcemap: {
            myapp: {
              files: {
                'build': ['build/myapp.js']
              }
            }
          },
    
          uglify: {
            options: {
              sourceMap: true,
              sourceMapIn: 'build/myapp.js.map'
            },
            target: {
              src: 'build/myapp.js',
              dest: 'build/myapp.min.js'
            }
          }
    
      });
    
      grunt.loadNpmTasks('grunt-browserify');
      grunt.loadNpmTasks('grunt-extract-sourcemap');
      grunt.loadNpmTasks('grunt-contrib-uglify');
    
      // Note the order of the tasks in your task list is important.
      grunt.registerTask('default', ['browserify', 'extract_sourcemap', 'uglify']);
    };
    

    Explanation

    1. First the browserify task is invoked which outputs a new file (i.e. build/myapp.js) containing your bundled JavaScript and an "inlined" source map info. If you were to inspect the content of build/myapp.js at this stage it includes something like the following at the end:

      //# sourceMappingURL=data:application/json;charset=utf-8;base64, ...
      
    2. Next the extract_sourcemap task is invoked. This essentially extracts the "inlined" source map info from build/myapp.js and writes it to a new file named myapp.js.map which is saved in your build directory.

      The original "inlined" source map info in build/myapp.js is replaced with a link to the newly generated source map file, i.e. myapp.js.map. If you inspect the content of build/myapp.js you'll now notice the following at the end of the file instead:

      //# sourceMappingURL=myapp.js.map
      
    3. Lastly the uglify task is invoked. Notice how its sourceMapIn option is configured to read build/myapp.js.map, i.e the source map file we generated at step 2.

      This task creates your desired build/myapp.min.js file containing; your minified JS, and a link to a newly generated source map file build/myapp.min.js.map.

    Note The final resultant file (i.e. build/myapp.min.js) now correctly maps back to the original src/index.js file and any file(s) that index.js itself may have import'ed or require()'d