How to Configure Sourcemaps for Less Using Grunt

How to configure sourceMaps for LESS using Grunt?

I found the LESS site documentation to be more clear regarding params used by grunt-contrib-less.

LESS: Command Line Usage
http://lesscss.org/usage/#command-line-usage-installing-lessc

NPM: grunt-contrib-less
https://www.npmjs.org/package/grunt-contrib-less

File structure:

laravel/gruntfile.js
laravel/public/less/main.less
laravel/public/css/main.css
laravel/public/css/main.css.map

File 'main.css.map' note:

  • If you wish, you can rename to: main.css.source-map.json
  • I guess you have some server rule setup that doesn't server *.map files properly from the 'css' folder

Compression notes:

  • cleancss: true = will remove sourceMappingURL comment from main.css
  • yuicompress: true = will NOT remove sourceMappingURL comment from main.css

Gruntfile.js

less: {
dev: {
options: {
compress: true,
yuicompress: true,
optimization: 2,
sourceMap: true,
sourceMapFilename: 'public/css/main.css.map', // where file is generated and located
sourceMapURL: '/css/main.css.map', // the complete url and filename put in the compiled css file
sourceMapBasepath: 'public', // Sets sourcemap base path, defaults to current working directory.
sourceMapRootpath: '/', // adds this path onto the sourcemap filename and less file paths
},
files: {
'public/css/main.css': 'public/less/main.less',
}
}
},

watch: {
styles: {
files: ["public/less/*"],
tasks: ['less'],
options: {
livereload: true,
nospaces: true
}
}
},

laravel/public/css/main.css

.class{ compiled css here } /*# sourceMappingURL=/css/main.css.map */

laravel/public/css/main.css.map

{"version":3,"sources":["/less/main.less"], etc...

How do I generate sourcemaps for Uglified files using Grunt?

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

How to set CSS Source Map output destination when grunt-contrib-less is used

The sourceMapFilename option may include a path part as well.
I.e. just change it to:

sourceMapFilename: "qa1/avinash/html5/phase1/css/style.css.map"

Output multiple LESS source maps with Grunt?

The crummy brute-force method is to simply define a second build process and make sure that your build processes call both less.development1 and less.development2:

less: {
"development1": {
options: {
compress: true,
yuicompress: true,
optimization: 2,
sourceMap: true,
sourceMapFilename: "<%= yeoman.app %>/live_preview/b/css/theme.css.map"

},
files: {
// target.css file: source.less file
"<%= yeoman.app %>/live_preview/b/css/theme.css": "<%= yeoman.app %>/less/theme.less"
}
},
"development2": {
options: {
compress: true,
yuicompress: true,
optimization: 2,
sourceMap: true,
sourceMapFilename: "<%= yeoman.app %>/live_preview/b/css/main.css.map"

},
files: {
// target.css file: source.less file
"<%= yeoman.app %>/live_preview/b/css/main.css": "<%= yeoman.app %>/less/main.less"
}
}
}

How do I create a sourcemap for each individual js file with grunt-contrib-concat and grunt-contrib-uglify?

This can be achieved by configuring your tasks as follows:

Note: Both solutions below will require the concat task to be run before the uglify task.


1. Using default generated names for source maps:

concat: {
options: {
sourceMap: true // <-- 1. Enable source maps
},
angular: {
src: ['public/**/*.js', '!public/**/*.min.js', '!public/lib/**/*.js'],
dest: 'build/_tsbc.js',
}
},

uglify: {
options: {
mangle: false,
sourceMap: true,
sourceMapIn: './build/_tsbc.js.map' // <-- 2. Define .map file to use.
},
app: {
files: {
'public/js/app.min.js': ['build/_tsbc.js']
}
},
// ...
}

Explanation

  1. In your concat task you need to firstly set the sourceMap option to true. Given your current configuration this will create a source map file at the following path:

    build/_tsbc.js.map

    Note: the location of the generated map file defaults to the same dest path as defined in the angular target of the concat task - with the additional .map suffix appended.

  2. Then in your uglify task add the sourceMapIn option and set its value (String) to the path of the .map file generated at stage one above. I.e.

    sourceMapIn: './build/_tsbc.js.map'

2. Explicitly defined names for source maps:

It's also possible to define the specific name and path for the source map file generated by the concat task. For example:

concat: {
options: {
sourceMap: true, // <-- 1. Enable source maps
sourceMapName: 'build/my_map.map' // <-- 2. Specify output path
},
angular: {
src: ['public/**/*.js', '!public/**/*.min.js', '!public/lib/**/*.js'],
dest: 'build/_tsbc.js',
}
},

uglify: {
options: {
mangle: false,
sourceMap: true,
sourceMapIn: './build/my_map.map' // <-- 3. Define .map file to use.
},
app: {
files: {
'public/js/app.min.js': ['build/_tsbc.js']
}
},
// ...
}

Explanation

  1. Same as before, set the sourceMap option to true

  2. This time, add the sourceMapName option and specify the path of the generated source map.

    Note: the location of the generated map file is now set to:

    'build/my_map.map'

  3. Again, in your uglify task add the sourceMapIn option and set its value to the same path specified in the previous point. I.e.

    sourceMapIn: './build/my_map.map'

EDIT

I've just noticed that your bower target in the uglify task does not depend on output from the previous concat task. For this scenario you'll need to nest the options object in each target. For example:

uglify: {
app: {
options: { // <-- Specific options for `app` target
mangle: false,
sourceMap: true,
sourceMapIn: './build/my_map.map' // <-- 3. Define .map file to use.
},
files: {
'public/js/app.min.js': ['build/_tsbc.js']
}
},
bower: {
options: { // <-- Specific options for `bower` target
mangle: false,
sourceMap: true
// <-- There's no `sourceMapIn` needed here.
},
files: {
'public/js/lib.min.js': ['build/_bower.js']
}
}
},


Related Topics



Leave a reply



Submit