4
votes

Hello,

I just enable sourceMap option of the grunt-contrib-uglify module to generate a map file for my concat-jshint-minified-filerev javascript files.

In the dist directory I can see :

  • dist/
    • vendor.6ff9fbf4.js
    • vendor.6ff9fbf4.js.map

But if I edit vendor.6ff9fbf4.js, I can see at the end of file that the referenced sourceMap is wrong

/* much, much, much minified code here */
//# sourceMappingURL=vendor.js.map

And if I open vendor.6ff9fbf4.js.map I can see at the first line that the reference source file is wrong too.

{"version":3,
 "file":"vendor.js",
 "sources":["../../.tmp/concat/scripts/vendor.js"]
 /* much, much, much more things here */
}

Obviously, the files are not renamed after the grunt-filerev task.

Any suggestions on how to do that?


Config: Gruntfile.js

// Generated on 2014-06-19 using generator-angular 0.9.0-1
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  grunt.loadNpmTasks('grunt-ng-annotate');

  // Load grunt tasks automatically
  require('load-grunt-tasks')(grunt);

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: appConfig,

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: ['<%= yeoman.app %>/scripts/{,*/**/}*.js'],
        tasks: ['newer:jshint:all'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      jsTest: {
        files: ['test/spec/{,*/**/}*.js'],
        tasks: ['newer:jshint:test', 'karma']
      },
      styles: {
        files: ['<%= yeoman.app %>/styles/{,*/**/}*.css'],
        tasks: ['newer:copy:styles', 'autoprefixer']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/**/}*.html',
          '.tmp/styles/{,*/**/}*.css',
          '<%= yeoman.app %>/images/{,*/**/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: '0.0.0.0',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect.static('test'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>'
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/**/}*.js'
        ]
      },
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/spec/{,*/**/}*.js']
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/**/}*',
            '!<%= yeoman.dist %>/.git*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/**/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath: new RegExp('^<%= yeoman.app %>/|../')
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/**/}*.js',
          '<%= yeoman.dist %>/styles/{,*/**/}*.css',
          '<%= yeoman.dist %>/images/{,*/**/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
      //html: ['<%= yeoman.dist %>/{,*/**/}*.html'],
    usemin: {
      html: ['<%= yeoman.dist %>/**/*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/**/}*.css'],
      options: {
        assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images']
      }
    },

    // The following *-min tasks will produce minified files in the dist folder
    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/styles/main.css': [
    //         '.tmp/styles/{,*/}*.css'
    //       ]
    //     }
    //   }
    // },
    // uglify: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/scripts/scripts.js': [
    //         '<%= yeoman.dist %>/scripts/scripts.js'
    //       ]
    //     }
    //   }
    // },
    // concat: {
    //   dist: {}
    // },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true,
          removeOptionalTags: true
        },
        //  src: ['*.html', 'views/{,*/**/}*.html'],
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html', 'views/**/*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    // ngAnnotate tries to make the code safe for minification automatically by
    // using the Angular long form for dependency injection. It doesn't work on
    // things like resolve or inject so those have to be done manually.
    ngAnnotate: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: '*.js',
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
            //'views/{,*/}*.html',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            '*.html',
            'views/**/*.html',
            'images/{,*/}*.{webp}',
            'fonts/*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'copy:styles'
      ],
      test: [
        'copy:styles'
      ],
      dist: [
        'copy:styles',
        'imagemin',
        'svgmin'
      ]
    },

    // Test settings
    karma: {
      unit: {
        configFile: 'test/karma.conf.js',
        singleRun: true
      }
    } ,

    uglify: {
      options : {
        sourceMap : true,
        compress : true,
        mangle : true
      }
    }
  });


  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'concurrent:test',
    'autoprefixer',
    'connect:test',
    'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);
};
2

2 Answers

2
votes

Recently faced this problem (it's actually a set of problems). Here is how I solved it.

First of all had to update the grunt plugin dependencies for:

"grunt-filerev": "^2.3.*"
"grunt-contrib-uglify": "^0.9.*",

The first one because the latest filerev also changes the source map names to the reved ones (matching the .js), the second because the version I was using didn't even had the sourceMap boolean option.

But this was not enough. You have to make the "uglification" on the dist folder. With your setup

useminPrepare: {
  html: '<%= yeoman.app %>/index.html',
  options: {
    dest: '<%= yeoman.dist %>',
    flow: {
      html: {
        steps: {
          js: ['concat', 'uglifyjs'],
          css: ['cssmin']
        },
        post: {}
      }
    }
  }
},

you are making it inside the concat folder and the uglify task is resolving the relative path to "sources":["../../.tmp/concat/scripts/vendor.js"].

So remove the 'uglifyjs' from the js steps array.

        steps: {
          js: ['concat'],
          css: ['cssmin']
        },

We now need to indicate where the uglification will be made so:

uglify: {
  options : {
    sourceMap : true,
    compress : true,
    mangle : true
  },
  all: {
      files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>/scripts',
          src: [
                '*.js',
                '!*<%= yeoman.beforeUglifyMarker %>.js'
               ],
          dest: '<%= yeoman.dist %>/scripts',
          ext: '.js'
      }]
  }

}

You will notice the <%= yeoman.beforeUglifyMarker %>. You should create a property beforeUglifyMarker in the appConfig object, that we will use to exclude the non uglified original files from the uglification, keeping them available in the same path as the source map.

So before the uglify task runs we will duplicate the files that will be uglified, renaming them with the value of yeoman.beforeUglifyMarker (so we can keep the originals to match with the source map).

In your copy task create 2 targets:

        savePrettyVersion: {
            expand: true,
            cwd: '<%= yeoman.dist %>/scripts',
            dest: '<%= yeoman.dist %>/scripts',
            src: '*.js',
            rename: function(dest, src){
                var name = src.split('.')[0];
                return dest + '/' + name + appConfig.beforeUglifyMarker + '.js';
            }
        },
        restorePrettyVersion: {
            expand: true,
            cwd: '<%= yeoman.dist %>/scripts',
            dest: '<%= yeoman.dist %>/scripts',
            src: '*<%= yeoman.beforeUglifyMarker %>.js',
            rename: function(dest, src){
                var originalParts = src.split(appConfig.beforeUglifyMarker);
                return dest + '/' + originalParts.join('');
            }
        },

Use them in your build task like this:

        'copy:savePrettyVersion',
        'uglify',
        'filerev',
        'usemin',
        'htmlmin',
        'copy:restorePrettyVersion',
        'clean:nonUglyTempFiles',

The last on is optional, just cleans the temp files.

Then make sure you exclude the temp files (marked with beforeUglifyMarker) from the filerev uglify and usemin tasks with a negated pattern or a regex.

usemin: {
  html: ['<%= yeoman.dist %>/**/*.html'],
  css: ['<%= yeoman.dist %>/styles/{,*/**/}*.css'],
  js: [
        '<%= yeoman.dist %>/scripts/{,*/}*.js',
        '!<%= yeoman.dist %>/scripts/*<%= yeoman.beforeUglifyMarker %>.js',
  ],

  options: {
    assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images']
  }
},
filerev: {
  dist: {
    src: [
      '<%= yeoman.dist %>/scripts/{,*/**/}*.js',
      '!<%= yeoman.dist %>/scripts/*<%= yeoman.beforeUglifyMarker %>.js',

      '<%= yeoman.dist %>/styles/{,*/**/}*.css',
      '<%= yeoman.dist %>/images/{,*/**/}*.{png,jpg,jpeg,gif,webp,svg}',
      '<%= yeoman.dist %>/styles/fonts/*'
    ]
  }
},

This is a hacky workaround, that works but should be obviated in the future, look/watch this issue on usemin. Meanwhile I hope its helpfull.

0
votes

Finally, I updated all my packages and all goes smoothly.

Here after my uglify task :

uglify: {
  options : {
    sourceMap : true,
    compress : {},
    mangle : true
  }
}

Here after an extract of my package.json

{
  "grunt": "^0.4.5",
  "grunt-autoprefixer": "^2.2.0",
  "grunt-concurrent": "^1.0.0",
  "grunt-contrib-clean": "^0.6.0",
  "grunt-contrib-concat": "^0.5.0",
  "grunt-contrib-connect": "^0.9.0",
  "grunt-contrib-copy": "^0.7.0",
  "grunt-contrib-cssmin": "^0.11.0",
  "grunt-contrib-htmlmin": "^0.3.0",
  "grunt-contrib-imagemin": "^0.9.4",
  "grunt-contrib-jshint": "^0.11.0",
  "grunt-contrib-uglify": "^0.7.0",
  "grunt-contrib-watch": "^0.6.1",
  "grunt-filerev": "^2.1.2",
  "grunt-google-cdn": "^0.4.3",
  "grunt-karma": "~0.10.1",
  "grunt-newer": "^1.1.0",
  "grunt-ng-annotate": "~0.9.2",
  "grunt-svgmin": "^2.0.0",
  "grunt-usemin": "^3.0.0",
  "grunt-wiredep": "^2.0.0",
  "jshint-stylish": "^1.0.0",
  "karma": "~0.12.31",
  "karma-chrome-launcher": "~0.1.7",
  "karma-jasmine": "~0.3.5",
  "karma-ng-html2js-preprocessor": "~0.1.2",
  "karma-phantomjs-launcher": "~0.1.4",
  "load-grunt-tasks": "^3.0.0",
  "time-grunt": "^1.0.0"
}