How to execute multiple targets with different options in grunt-contrib-jshint - gruntjs

Goal: to flex the execution of jshint to output results to the console (if in development) or to a file (if being released/in Jenkins).
Is it possible? I can add individual targets to my jshint settings, but all of my options are the same except for the reporter details. So it'd be nice to not duplicate those. I need to do the concatenation from "all" and use all of the global options and the specific options from the target that is called. How?
jshint.js:
module.exports = {
options: {
node: true,
browser: true,
blah: blah...
},
all: [
'Gruntfile.js',
'<%= yeoman.app %>/modules/**/*.js',
'<%= yeoman.app %>/*.js'
],
dev: {
options: {
reporter: 'checkstyle'
}
}
release: {
options: {
reporter: 'checkstyle',
reporterOutput: 'myfile.xml'
}
}
};

I hope this can help you, its just a sample you can improve the code.
install : grunt-contrib-jshint
Gruntfile.js
//Jshint ===============================
var jshint;
config.jshint = jshint ={};
jshint.dist = {
options: {jshintrc: ".jshintrc"},
files: {all: ["lib/main.js","lib/test.js"]}
};
jshint.dev = {
options: {jshintrc: ".jshintrc.dev"},
files: {all: ["lib/main.js","lib/test.js"]}
};
.jshintrc
{
"strict":true,
"laxcomma":true,
"sub":true
}
.jshintrc.dev
{
"strict":true,
"laxcomma":true,
"sub":true,
"debug":true
}

If you have a subset of options that will be the same between two targets, you can create a variable for the object and assign its value to both target's option key:
var reporterOptions = {
foo: 'red',
bar: 'green',
baz: 'blue'
};
module.exports = {
options: {
node: true,
browser: true,
blah: blah...
},
all: [
'Gruntfile.js',
'<%= yeoman.app %>/modules/**/*.js',
'<%= yeoman.app %>/*.js'
],
dev: {
options: reporterOptions
}
release: {
options: reporterOptions
}
};
If you want to maintain your option values in one place, but want to selectively decide which options are applied to which targets, you can reference the values stored in the variable using dot notation.
var reporterOptions = {
foo: 'red',
bar: 'green',
baz: 'blue'
};
module.exports = {
options: {
node: true,
browser: true,
blah: blah...
},
all: [
'Gruntfile.js',
'<%= yeoman.app %>/modules/**/*.js',
'<%= yeoman.app %>/*.js'
],
dev: {
options: {
foo: reporterOptions.foo
bar: reporterOptions.bar
}
}
release: {
options: {
foo: reporterOptions.foo
baz: reporterOptions.baz
}
}
};

I ended up going the variable route but using that for the files source and then overriding the necessary options in the targets.
var files = [
'Gruntfile.js',
'<%= yeoman.app %>/modules/**/*.js',
'<%= yeoman.app %>/*.js'
];
module.exports = {
options: {
reporter: require('jshint-stylish'),
node: true, ...
},
dev: {
src: files
},
release: {
src: files,
options: {
reporter: 'checkstyle',
reporterOutput: 'myfile.xml',
reporterOutputRelative: false
}
}
}
Based on other things I had read, I didn't think the default options would work in the targets and allow overriding, and I also had encountered problems with the files, but this solved everything.

Related

How to use array variable properly in gruntfile.js

Trying to use a predefined array inside of a grunt file, thought using this.js_paths would work, but doesn't seem to work as I'm getting the error, "Cannot read property IndexOf of undefined" when it comes to trying to uglify the scripts. How can I link the js_paths variable to the files src property properly instead of copying the array into the files. Would like to define it separately at the top. Is this possible?
module.exports = function(grunt) {
// loadNpmTasks from package.json file for all devDependencies that start with grunt-
require("matchdep").filterDev("grunt-*", './package.json').forEach(grunt.loadNpmTasks);
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
js_paths: [
'inc/header1/js/*.js',
'!inc/header1/js/*.min.js',
'inc/header2/js/*.js',
'inc/header2/js/*.js',
'!inc/header2/js/*.min.js',
'js/*.js',
'!js/*.min.js'
],
uglify: {
options: {
mangle: true
},
build: {
files: [{
expand: true,
src: this.js_paths,
rename: function(dst, src) {
return src.replace('.js', '.min.js');
}
}]
}
},
watch: {
scripts: {
files: ['inc/header1/js/*.js', 'inc/header2/js/*.js', 'js/*.js'],
tasks: ['uglify'],
options: {
spawn: false,
}
}
}
});
grunt.registerTask('default', ['uglify', 'watch']);
};
Preferrably would like to use the same array js_paths in the watch files (since it's required there), if that makes sense? Still kinda new to using gruntfile.js
Utilize the Templates syntax. It's described in the docs as following:
Templates
Templates specified using <% %> delimiters will be automatically expanded when tasks read them from the config. Templates are expanded recursively until no more remain.
Essentially, change this.js_paths to '<%= js_paths %>' in your uglify task.
For instance:
// ...
uglify: {
options: {
mangle: true
},
build: {
files: [{
expand: true,
src: '<%= js_paths %>', // <-----
rename: function(dst, src) {
return src.replace('.js', '.min.js');
}
}]
}
},
// ...
Likewise for your watch task too.
For instance:
watch: {
scripts: {
files: '<%= js_paths %>', // <-----
tasks: ['uglify'],
options: {
spawn: false,
}
}
}

How to Create Sourcemaps with GruntJs Terser JS Minify

I'm using Terser for Gruntjs to minify js files.
I want to be able to create a sourcemap but I don't know how to translate the samples I see in the options section (https://www.npmjs.com/package/terser#source-map-options) into my gruntfile.js.
Here is the section where I am minifying and I added where I think the sourceMap options go:
grunt.initConfig({
terser: {
pages: {
options: {
mangle: {
properties: false
},
sourceMap: {
// source map options goes here I think but not certain what
}
},
files: [
{ expand: true,
src: '**/*.js',
dest: 'wwwroot/js',
cwd: 'wwwroot/js',
ext: '.min.js'
}
]
}
}
});
I cannot find a gruntjs example for this anywhere so any input or help would be great
I found the answer and have tested it. Note the sourceMap entry in the options below:
grunt.initConfig({
terser: {
pages: {
options: {
mangle: {
properties: false
},
sourceMap: true
},
files: [
{ expand: true,
src: '**/*.js',
dest: 'wwwroot/js',
cwd: 'wwwroot/js',
ext: '.min.js'
}
]
}
}
});

Grunt - how can I specify files to multiple targets in the same task?

I have 2 sass targets.
one creates debug info, the other does not.
If I don't copy/paste the files section between the targets, it doesn't seem to work.
sass: {
server: {
options: {
debugInfo: true
},
files: {
'.tmp/styles/main.css': '<%= yeoman.app %>/styles/main.scss'
}
},
dist: {
files: {
'.tmp/styles/main.css': '<%= yeoman.app %>/styles/main.scss'
}
}
},
How can I reuse the files decleration?
From a grunt-sass point of view, you can't share the file declaration between multiple targets.
But to avoid duplication in your Gruntfile, you can always use plain old Javascript variables:
var cssFiles = {
'.tmp/styles/main.css': '<%= yeoman.app %>/styles/main.scss'
}
// ...
grunt.initConfig({
// ...
sass: {
server: {
options: {debugInfo: true},
files: cssFiles
},
dist: {
files: cssFiles
}
},
// ...
});

LESS/Grunt is not writing the sourcemap reference to the end of the compiled CSS

I am using grunt-contrib-less to compile my .less files in to a single CSS stylesheet. Everything is working, except the source map, which I cannot get to work under any circumstances!
Here is my Gruntfile:
'use strict';
module.exports = function(grunt) {
// Force use of Unix newlines
grunt.util.linefeed = '\n';
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
project: {
// Add entries to this array to create variables for use by Grunt
app: ['app'],
server: 'http://mysite.app',
// Components
bower: ['<%= project.app %>/bower_components'],
bootstrap: ['<%= project.bower %>/bootstrap'],
// Custom Assets
assets: ['<%= project.app %>/assets'],
css: ['<%= project.assets %>/css'],
less: ['<%= project.assets %>/less'],
js: ['<%= project.assets %>/js']
},
less: {
production: {
options: {
ieCompat: true,
sourceMap: true,
sourceMapFilename: '<%= project.css %>/style.css.map',
sourceMapURL: '<%= project.server %>/assets/css/style.css.map',
sourceMapBasepath: 'app',
sourceMapRootpath: '<%= project.server %>'
},
files: {
'<%= project.css %>/style.css': '<%= project.less %>/style.less'
}
}
},
autoprefixer: {
dist: {
files: {
'<%= project.assets %>/css/style.css': '<%= project.assets %>/css/style.css'
}
}
},
concat: {
options: {
separator: ';\n',
sourceMap: true
},
plugins_head: {
// Add further Javascript plugins to this array and they will be
// concatenated in to the plugins-built-head.js file
src: [
'<%= project.bower %>/modernizr/modernizr.js'
],
dest: '<%= project.js %>/built/plugins-built-head.js'
},
plugins: {
// Add further Javascript plugins to this array and they will be
// concatenated in to the plugins-built.js file
src: [
'<%= project.bootstrap %>/js/dropdown.js'
],
dest: '<%= project.js %>/built/plugins-built.js'
},
custom: {
// Add further custom-written javascript files to this array and
// they will be concatenated in to the scripts-built.js file
src: [
'<%= project.js %>/scripts.js'
],
dest: '<%= project.js %>/built/scripts-built.js'
}
},
watch: {
css: {
files: [
'<%= project.bootstrap %>/less/*.less',
'<%= project.less %>/*.less'
],
tasks: [
'less',
'autoprefixer'
],
options: {
livereload: true
}
},
js: {
files: [
'<%= project.js %>/scripts.js'
],
tasks: ['concat']
},
html: {
files: [
'<%= project.app %>/*.html'
],
options: {
livereload: true
}
}
}
});
grunt.loadNpmTasks('grunt-run-grunt');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-autoprefixer');
grunt.registerTask('default', [
'watch'
]);
};
What's happening is that Grunt is writing a proper and correct style.css.map file, but it's not writing the following line to the end of the compiled style.css file:
/*# sourceMappingURL=http://mysite.app/assets/css/style.css.map */
That's the one line that's missing. Everything else is getting compiled and written correctly. If I manually add that line to the end of the compiled CSS, Chrome picks up on the source map properly, but it's not being written in.
Additionally, trying options like sourceMapFileInline seems to make no difference - the file is never written inline.
Any ideas?
Hopefully you have found a solution by now. This is for other people with the same issue:
Make sure the soourcemap will be placed in the same folder as css.
Set sourceMapUrl to only the name of the map file.
This will add the following line to your .css file: /*# sourceMappingURL=default.css.map */
Here are the sourcemap settings in my grunt file:
sourceMap: true,
sourceMapFilename: "src/assets/css/default.css.map",
sourceMapURL: "default.css.map"

Grunt watch less on changed file only

I want to have a gruntfile with 2 tasks: less (compiles all less files) and watch (listens to changes and re-compiles the changed file).
I have the following Gruntfile.js:
module.exports = function(grunt) {
var files = [
{
expand: true,
cwd: 'media/less',
src: ['*.less'],
dest: 'media/css/',
ext: '.css'
},
{
expand: true,
cwd: 'media/less/vendor',
src: ['*.less'],
dest: 'media/css/vendor/',
ext: '.css'
},
{
expand: true,
cwd: 'media/admin/less',
src: ['*.less'],
dest: 'media/admin/css/',
ext: '.css'
}
];
grunt.initConfig({
less: {
development: {
options: {
compress: false,
yuicompress: true,
optimization: 2
},
files: files
},
production: {
options: {
compress: true,
yuicompress: true,
optimization: 2
},
files: files
}
},
watch: {
styles: {
files: ['media/**/*.less'],
tasks: ['less:development'],
options: {
nospawn: true
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['less:development']);
};
The less task runs correctly without any problems. The watch task however listens to changes, but re-compiles all files when one is changed. I suspect it's something to do with how I set up my less task, because I want my less file list to be dynamic and not to add each file manually.
As per this answer grunt should already support this, but I'm unsure how.
Ended up using the watch event and overriding the files property of the less task. Here's my final code:
module.exports = function(grunt) {
var files = [
{
expand: true,
cwd: 'media/less',
src: ['*.less'],
dest: 'media/css/',
ext: '.css',
extDot: 'last'
},
{
expand: true,
cwd: 'media/less/vendor',
src: ['*.less'],
dest: 'media/css/vendor/',
ext: '.css',
extDot: 'last'
},
{
expand: true,
cwd: 'media/admin/less',
src: ['*.less'],
dest: 'media/admin/css/',
ext: '.css',
extDot: 'last'
}
];
grunt.initConfig({
less: {
development: {
options: {
compress: false,
yuicompress: true,
optimization: 2
},
files: files
},
production: {
options: {
compress: true,
yuicompress: true,
optimization: 2
},
files: files
}
},
watch: {
styles: {
files: ['media/**/*.less'],
tasks: ['less:development'],
options: {
nospawn: true
}
}
}
});
grunt.event.on('watch', function(action, filepath){
// ignore include files, TODO: have naming convention
// if an include file has been changed, all files will be re-compiled
if(filepath.indexOf('.inc.') > -1)
return true;
// might not be the most efficient way to do this
var srcDir = filepath.split('/');
var filename = srcDir[srcDir.length - 1];
delete srcDir[srcDir.length - 1];
srcDir = srcDir.join('/');
var destDir = srcDir.replace(/less/g, 'css');
grunt.config('less.development.files', [{
src: filename,
dest: destDir,
expand: true,
cwd: srcDir,
ext: '.css',
extDot: 'last'
}]);
});
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['less:development']);
};
I'm unsure what you mean with the current title of your question Grunt watch less on changed file only. Do you mean that is a problem? That's the expected behavior of a watch task, it will watch the files specified for changes and run the tasks you specified - in this case the LESS compilation.
I did some changes to your file. Some of it was simplified, some was changed keeping flexibility and expandability of the script in mind.
First install underscore as a dependency, by running:
npm install underscore --save-dev
Then do the following changes to your Gruntfile.js:
module.exports = function(grunt) {
var _ = require('underscore');
var files = {
app : {
'<%= path.styles.css %>/styles.css' : '<%= path.styles.less %>/*.less'
},
vendor : {
'<%= path.styles.css %>/styles-vendor.css' : '<%= path.styles.vendor %>/*.less'
},
admin : {
'<%= path.styles.css %>/styles-admin.css' : '<%= path.styles.admin %>/*.less'
}
}
function all() {
'use strict';
var allfiles = {},
i = {};
for (i in files) {
_.extend(allfiles, files[i]);
}
return allfiles;
}
grunt.initConfig({
path : {
media : 'media',
styles : {
css: 'media/css',
less: 'media/less',
admin: 'media/admin/less',
vendor: '<%= path.styles.less %>/vendor'
}
},
less: {
development: {
options: {
compress: false,
yuicompress: true,
optimization: 2
},
files: (all())
},
production: {
options: {
compress: true,
yuicompress: true,
optimization: 2
},
files: (all())
}
},
watch: {
styles: {
files: ['<%= path.media %>/**/*.less'],
tasks: ['less:development'],
options: {
nospawn: true
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-watch');
// run several tasks as default (handy for complex projects)
grunt.registerTask('dist', [ // run with 'grunt dist'
'less:production'
]);
grunt.registerTask('dev', [ // default, will run with 'grunt' only
'less:development'
]);
grunt.registerTask('default', 'dev');
};
If what you want is to actually compile the sets of files separately (files.app, files.vendor & files.admin), you might need to split the task some more, like so:
less: {
app: {
options: {
compress: false,
yuicompress: true,
optimization: 2
},
files: files.app
},
vendor: {
options: {
compress: true,
yuicompress: true,
optimization: 2
},
files: files.vendor
},
admin: {
options: {
compress: true,
yuicompress: true,
optimization: 2
},
files: files.admin
},
development: {
options: {
compress: false,
yuicompress: true,
optimization: 2
},
files: (all())
},
production: {
options: {
compress: true,
yuicompress: true,
optimization: 2
},
files: (all())
}
},
watch: {
all: {
files: ['<%= path.media %>/**/*.less'],
tasks: ['less:development'],
options: {
nospawn: true
}
},
app : {
files: ['<%= path.styles.less %>/*.less'],
tasks: ['less:app'],
options: {
nospawn: true
}
},
vendor : {
files: ['<%= path.styles.vendor %>/*.less'],
tasks: ['less:vendor'],
options: {
nospawn: true
}
},
admin : {
files: ['<%= path.styles.admin %>/*.less'],
tasks: ['less:admin'],
options: {
nospawn: true
}
}
}
Then you could then run either of these:
grunt watch:app
grunt watch:vendor
grunt watch:admin
You can always to choose to run the tasks directly, once:
grunt less:app
grunt less:vendor
grunt less:admin
Hope this helps! Please note that I haven't tested this.

Resources