Do not compile until you fix sass-lint errors grunt - gruntjs

I have a problem with sass-lint, in theory I should not compile grunt if there are errors in SASS LINT
I'm setting my entry wrong?
module.exports = function (grunt) {
var path = require('path'),
argv = require('minimist')(process.argv.slice(2));
// load all grunt tasks
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-browser-sync');
grunt.loadNpmTasks('grunt-contrib-sass');
grunt.loadNpmTasks('grunt-postcss');
grunt.loadNpmTasks('grunt-sass-lint');
/******************************************************
* PATTERN LAB CONFIGURATION
******************************************************/
//read all paths from our namespaced config file
var config = require('./patternlab-config.json'),
pl = require('patternlab-node')(config);
function paths() {
return config.paths;
}
function getConfiguredCleanOption() {
return config.cleanPublic;
}
grunt.registerTask('patternlab', 'create design systems with atomic design', function (arg) {
if (arguments.length === 0) {
pl.build(function(){}, getConfiguredCleanOption());
}
if (arg && arg === 'version') {
pl.version();
}
if (arg && arg === "patternsonly") {
pl.patternsonly(function(){},getConfiguredCleanOption());
}
if (arg && arg === "help") {
pl.help();
}
if (arg && arg === "liststarterkits") {
pl.liststarterkits();
}
if (arg && arg === "loadstarterkit") {
pl.loadstarterkit(argv.kit, argv.clean);
}
if (arg && (arg !== "version" && arg !== "patternsonly" && arg !== "help" && arg !== "liststarterkits" && arg !== "loadstarterkit")) {
pl.help();
}
});
grunt.initConfig({
/******************************************************
* COPY TASKS
******************************************************/
copy: {
main: {
files: [
{ expand: true, cwd: path.resolve(paths().source.js), src: '**/*.js', dest: path.resolve(paths().public.js) },
{ expand: true, cwd: path.resolve(paths().source.js), src: '**/*.js.map', dest: path.resolve(paths().public.js) },
{ expand: true, cwd: path.resolve(paths().source.css), src: '**/*.css', dest: path.resolve(paths().public.css) },
{ expand: true, cwd: path.resolve(paths().source.css), src: '**/*.css.map', dest: path.resolve(paths().public.css) },
{ expand: true, cwd: path.resolve(paths().source.images), src: '**/*', dest: path.resolve(paths().public.images) },
{ expand: true, cwd: path.resolve(paths().source.fonts), src: '**/*', dest: path.resolve(paths().public.fonts) },
{ expand: true, cwd: path.resolve(paths().source.root), src: 'favicon.ico', dest: path.resolve(paths().public.root) },
{ expand: true, cwd: path.resolve(paths().source.styleguide), src: ['*', '**'], dest: path.resolve(paths().public.root) },
// slightly inefficient to do this again - I am not a grunt glob master. someone fix
{ expand: true, flatten: true, cwd: path.resolve(paths().source.styleguide, 'styleguide', 'css', 'custom'), src: '*.css)', dest: path.resolve(paths().public.styleguide, 'css') }
]
}
},
/******************************************************
* SERVER AND WATCH TASKS
******************************************************/
browserSync: {
dev: {
options: {
server: path.resolve(paths().public.root),
watchTask: true,
watchOptions: {
ignoreInitial: true,
ignored: '*.html'
},
snippetOptions: {
// Ignore all HTML files within the templates folder
blacklist: ['/index.html', '/', '/?*']
},
plugins: [
{
module: 'bs-html-injector',
options: {
files: [path.resolve(paths().public.root + '/index.html'), path.resolve(paths().public.styleguide + '/styleguide.html')]
}
}
],
notify: {
styles: [
'display: none',
'padding: 15px',
'font-family: sans-serif',
'position: fixed',
'font-size: 1em',
'z-index: 9999',
'bottom: 0px',
'right: 0px',
'border-top-left-radius: 5px',
'background-color: #1B2032',
'opacity: 0.4',
'margin: 0',
'color: white',
'text-align: center'
]
}
}
}
},
bsReload: {
css: path.resolve(paths().public.root + '**/*.css')
},
sass: {
dist: {
options: {
style: 'expanded'
},
files: {
'source/css/style.css': 'source/scss/main.scss'
}
}
},
postcss: {
options: {
processors: [
require('autoprefixer')({browsers: ['last 2 version']})
]
},
dist: {
src: 'source/css/style.css'
}
},
sasslint: {
options: {
bundleExec: true,
configFile: 'sass-lint.yml',
failOnWarning: true,
failOnError: true,
colorizeOutput: true
},
target: ['source/scss/\*.scss']
},
watch: {
css: {
files: [
path.resolve(paths().source.scss + '**/*.scss'),
path.resolve(paths().source.css + '**/*.css'),
path.resolve(paths().source.styleguide + 'css/*.css'),
],
tasks: ['sass', 'sasslint', 'postcss', 'default']
},
all: {
files: [
path.resolve(paths().source.patterns + '**/*'),
path.resolve(paths().source.fonts + '/*'),
path.resolve(paths().source.images + '/*'),
path.resolve(paths().source.data + '*.json'),
path.resolve(paths().source.js + '/*.js'),
path.resolve(paths().source.root + '/*.ico'),
path.resolve(paths().source.root + '/*.mustache')
],
tasks: ['default', 'bsReload:css']
}
},
});
/******************************************************
* COMPOUND TASKS
******************************************************/
grunt.registerTask('default', ['patternlab', 'copy:main']);
grunt.registerTask('patternlab:build', ['patternlab', 'sass', 'sasslint', 'postcss', 'copy:main']);
grunt.registerTask('patternlab:watch', ['patternlab', 'sass', 'sasslint', 'postcss', 'copy:main', 'watch']);
grunt.registerTask('patternlab:serve', ['patternlab', 'sass', 'sasslint', 'postcss', 'copy:main', 'browserSync', 'watch']);
};
The idea is that it does not compile until the errors and warnings are solved
Currently executes the following statement:
grunt patternlab:serve
And this generates the work environment despite having errors and warnings
Any possible solution?

Set the sass lint to level 2 (importance level) so that it throws an error in the grunt, for example:
rules:
   # Extends
   extends-before-mixins: 2
   extends-before-declarations: 2
   placeholder-in-extend: 2

Related

PostCSS not compiling but executes successfully

I am a grunt newbie...
Please read through my grunt file bellow. Everything executes successfully, however the PostCSS function doesn't do it's job. If I remove the expanded and compressed calls within it and just use the options and dist then it works, but when I try to double up on the calls it doesn't work. What do I need to do?
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
scripts_jquery: {
options: {
beautify: false,
mangle: false
},
files: {
'js/vendor/jquery-1.12.4.min.js': ['js/vendor/jquery-1.12.4.js']
}
},
scripts_functions: {
options: {
beautify: false,
mangle: false
},
files: {
'js/functions.min.js': ['js/functions.js']
}
},
scripts_expanded: {
options: {
beautify: true,
mangle: false,
sourceMap: true,
sourceMapName: 'js/scripts.js.map'
},
files: {
'js/scripts.js': ['js/plugins/**/*.js']
}
},
scripts_compressed: {
options: {
beautify: false,
mangle: false,
sourceMap: true,
sourceMapName: 'js/scripts.min.js.map'
},
files: {
'js/scripts.min.js': ['js/plugins/**/*.js']
}
}
},
sass: {
compile: {
options: {
indentType: 'tab',
indentWidth: 1,
linefeed: 'crlf',
sourceMap: false
},
files: {
'css/styles.css': 'css/styles.scss',
'css/styles.min.css': 'css/styles.scss'
}
}
},
postcss: {
css_expanded: {
options: {
map: {
inline: false,
annotation: 'css/'
},
processors: [
require('autoprefixer')({
browsers: 'last 2 versions'
})
]
},
dist: {
src: 'css/styles.css'
}
},
css_compressed: {
options: {
map: {
inline: false,
annotation: 'css/'
},
processors: [
require('autoprefixer')({
browsers: 'last 2 versions'
}),
require('cssnano')()
]
},
dist: {
src: 'css/styles.min.css'
}
}
},
imagemin: {
dynamic: {
files: [{
expand: true,
cwd: 'img/',
src: ['img/**/*.{png,jpg,gif}'],
dest: 'img/'
}]
}
},
svgmin: {
options: {
plugins: [{
removeViewBox: false
}, // don't remove the viewbox atribute from the SVG
{
removeEmptyAttrs: false
} // don't remove Empty Attributes from the SVG
]
},
dist: {
files: [{
expand: true,
cwd: 'img/',
src: ['img/**/*.svg'],
dest: 'img/'
}]
}
},
svgstore: {
options: {
prefix: 'icon-',
cleanup: true,
includedemo: true,
svg: {
viewBox: '0 0 100 100',
xmlns: 'http://www.w3.org/2000/svg'
}
},
dist: {
files: {
'svg/svg-sprite.svg': ['img/**/*.svg']
},
}
},
watch: {
scripts: {
files: ['js/plugins/**/*.js', 'js/vendor/jquery-1.12.4.js', 'js/functions.js'],
tasks: ['uglify'],
options: {
livereload: true,
},
},
css: {
files: ['css/**/*.scss'],
tasks: ['sass', 'postcss'],
options: {
livereload: true,
},
},
images: {
files: ['img/**/*.{png,jpg,gif}'],
tasks: ['imagemin'],
options: {
livereload: true,
},
},
svgs: {
files: ['img/**/*.svg'],
tasks: ['svgmin', 'svgstore'],
options: {
livereload: true,
},
}
},
});
require('load-grunt-tasks')(grunt);
grunt.loadNpmTasks('grunt-contrib-imagemin');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-postcss');
grunt.loadNpmTasks('grunt-sass');
grunt.loadNpmTasks('grunt-svgmin');
grunt.loadNpmTasks('grunt-svgstore');
// Default task(s).
grunt.registerTask('default', ['watch']);
};
You don't need the dist property inside each target. dist is a name for the default target. Remove it and the task should work:
postcss: {
css_expanded: {
options: {
map: {
inline: false,
annotation: 'css/'
},
processors: [
require('autoprefixer')({
browsers: 'last 2 versions'
})
]
},
src: 'css/styles.css'
},
css_compressed: {
options: {
map: {
inline: false,
annotation: 'css/'
},
processors: [
require('autoprefixer')({
browsers: 'last 2 versions'
}),
require('cssnano')()
]
},
src: 'css/styles.min.css'
}
},
There is actually a thread on this here: https://github.com/nDmitry/grunt-postcss/issues/67

Grunt Watch not creating files on save

The tasks in my grunt file are running without errors but the files i'm asking it to create aren't being compiled. They are created if I simple run 'grunt' but if I use 'grunt watch' and save a file it doesn't update.
Particularly I am working on file 'script/src/latestNews.js' so on save this should concat with others to create 'script/dist/main.js', which it does. But it does not then go on to create 'dist/build.min.js' like it should.
module.exports = function (grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
mangle: false
},
target: {
files: {
'script/dist/main.min.js':'script/dist/main.js'
}
},
build: {
files: {
'script/dist/build.min.js':'script/dist/build.min.js'
}
}
},
concat: {
options: {
stripBanners: true,
banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %> */',
},
srcJS: {
src: ['script/src/menu.js',
'script/src/custom_plugins.js',
'script/src/banner.js',
'script/src/latestNews.js',
'script/src/officers.js',
'script/src/eventsCalendar.js',
'script/src/combinedSocialMedia.js',
'script/src/haveYourSay.js',
'script/src/photoGallery.js',
'script/src/countdown.js'
],
dest: 'script/dist/main.js'
},
css: {
src: ['style/libs/bootstrap.min.css',
'style/libs/bootstrap-theme.min.css',
'style/src/css/*'],
dest: 'style/dist/build.min.css'
},
build: {
src: ['script/libs/jquery.easing.min.js',
'script/dist/main.js',
'script/libs/bootstrap.min.js',
'script/libs/velocity.min.js',
'script/libs/date.js',
'script/libs/jquery.timeago.js',
'script/libs/owl.carousel.min.js'
],
dest: 'script/dist/build.min.js'
}
},
jshint: {
main: 'script/dist/main.js'
},
watch: {
js: {
files: 'script/src/*',
tasks: ['concat:srcJS', 'uglify:target', 'jshint:main', 'copy:js']
},
css: {
files: 'style/src/css/*',
tasks: ['copy:css']
},
less: {
files: 'style/src/less/*',
tasks: ['less', 'copy:css']
},
html: {
files: '*.html',
tasks: ['validation', 'bootlint']
}
},
clean: {
js: [
'script/dist/main.min.js',
'dist/build.min.js',
'dist/build.min.css'
]
},
copy: {
css: {
files: [
{ expand: true, 'src' : 'style/src/css/main.css',
'dest' : 'style/dist/', flatten: true,
rename: function(dest, src) {
return dest + src.replace('main','build.min');
}
},
{ expand: true, 'src' : 'style/dist/build.min.css',
'dest' : 'dist/', flatten: true },
]
},
js: {
files: [
{ expand: true, 'src' : 'script/dist/build.min.js',
'dest' : 'dist/', flatten: true }
]
}
},
validation: {
options: {
reset: grunt.option('reset') || false,
stoponerror: true,
relaxerror: ['Bad value X-UA-Compatible for attribute http-equiv on element meta.'] //ignores these errors
},
files: {
src: ['homepage.html']
}
},
bootlint: {
options: {
stoponerror: false,
relaxerror: ['E001', 'E003', 'E031', 'W001', 'W002', 'W003', 'W005', 'W007', 'W009', 'E013']
},
files: ['homepage.html'],
},
less: {
build: {
options: {
paths: ["style/src/less"],
cleancss: true,
compress: true
},
files: {
"style/src/css/main.css": "style/src/less/main.less"
}
}
}
});
// Load the plugin that provides the "uglify" task.
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-html-validation');
grunt.loadNpmTasks('grunt-bootlint');
// Default task(s).
//grunt.registerTask('default', ['concat:srcJS','concat:css','uglify','jshint:main']);
grunt.registerTask('default', [
'validation',
'bootlint',
'concat:srcJS',
'jshint:main',
'uglify:target',
'clean',
'concat:build',
'uglify:build',
'less',
'copy'
]);
};
It looks like the array of tasks that are run by your watch:js task is simply missing a couple of critical tasks. Specifically you are not running the concat:build and uglify:build tasks.
Your current array of tasks:
['concat:srcJS', 'uglify:target', 'jshint:main', 'copy:js']
Complete/fixed array of tasks:
['concat:srcJS', 'uglify:target', 'jshint:main', 'concat:build', 'uglify:build', 'copy:js']
Replacing your current array with the fix that I supplied should solve your issue.

grunt will not run on my project, which default Task?

I am trying to run grunt in order to get SASS and a few other things to compile my project (that someone else created), however I keep getting the following error:
Warning: Task "default" not found. Use --force to continue.
Aborted due to warnings.
I do not understand which "default" task it is is referring to. The grunt file I have inherited is as follows:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
concat: {
options: {
separator: ';'
},
dev: {
src: ['www/_assets/js/vendor-concat/*.js', 'www/_assets/js/*.js', '!www/_assets/js/prototype.js', '!www/_assets/js/scripts.js', '!www/_assets/js/scripts.min.js'],
dest: 'www/_assets/js/scripts.js'
}
},
uglify: {
dist: {
files: {
'www/_assets/js/scripts.min.js': ['www/_assets/js/scripts.js']
}
}
},
jshint: {
files: ['Gruntfile.js'],
options: {
// options here to override JSHint defaults
globals: {
jQuery: true,
console: true,
module: true,
document: true
}
}
},
modernizr: {
dist: {
// [REQUIRED] Path to the build you're using for development.
"devFile" : "www/_assets/js/vendor/modernizr-2.7.1.js",
// [REQUIRED] Path to save out the built file.
"outputFile" : "www/_assets/js/vendor/modernizr-custom.js"
}
},
imageoptim: {
dev: {
src: ['www/_assets/img']
}
},
sass: {
dev: {
options: {
includePaths: ['www/_assets/scss'],
outputStyle: 'compressed',
sourceMap: true
},
files: {
'www/_assets/css/main.css' : 'www/_assets/scss/main.scss',
'www/_assets/css/main-ie8.css' : 'www/_assets/scss/main-ie8.scss'
}
}
},
assemble: {
options: {
flatten: false,
expand: true,
assets: 'www/_assets',
layout: 'default.hbs',
layoutdir: 'www/_templates/layouts',
partials: ['www/_templates/partials/*.hbs'],
data: ['www/_templates/data/*.json']
},
dev: {
files: [
{expand: true, cwd: 'www/_templates/pages/', src: '**/*.hbs', dest: 'www/', ext: '.html'}
]
}
},
watch: {
options: {
livereload: true,
spawn: false
},
css: {
files: ['www/_assets/scss/**/*.scss'],
tasks: ['sass']
},
js: {
files: ['www/_assets/js/**/*.js', '!www/_assets/js/scripts.min.js', '!www/_assets/js/scripts.js'],
tasks: ['jshint', 'concat:dev']
},
hbs: {
files: ['www/_templates/{,*/}*.hbs', 'www/_templates/{,*/*/}*.hbs', 'www/_templates/{,*/*/*/}*.hbs'],
tasks: ['assemble']
},
html: {
files: ['www/email.html']
}
},
copyto: {
prototype: {
files: [
{cwd: 'www/', src: ['**/*'], dest: 'prototype/'}
],
options: {
ignore: [
'www/_assets/scss{,/**/*}',
'www/_assets/js{,/**/*}',
'!www/_assets/js/vendor{,/**/*}',
'!www/_assets/js/scripts.min.js',
'!www/_assets/js/prototype.js',
'www/_templates{,/**/*}',
'www/z-backups{,/**/*}',
'www/_assets/css/*.map'
]
}
},
beta: {
files: [
{cwd: 'prototype/_assets/', src: ['**/*'], dest: '_beta-sln/Beta/src/SFA.as.Web.Candidate/Content/_assets/'}
],
options: {
ignore: [
'prototype/_assets/video{,/**/*}',
'prototype/_assets/js/prototype.js'
]
}
},
sprint: {
files: [
{cwd: 'prototype/', src: ['**/*'], dest: 'sprint/'}
]
},
dist: {
files: [
{cwd: 'www/', src: ['**/*'], dest: 'dist/'}
],
options: {
ignore: [
'www/_assets/scss{,/**/*}',
'www/_assets/css/*.map',
'www/_assets/video{,/**/*}',
'www/_assets/js/plugins{,/**/*}',
'www/_assets/js/interactions.js',
'www/_assets/js/scripts.js',
'www/_templates{,/**/*}',
'www/*.html',
'!www/index.html',
'www/z-backups{,/**/*}'
]
}
}
},
clean: {
prototype: {
src: [ 'prototype/' ]
},
sprint: {
src: [ 'sprint/' ]
},
dist: {
src: [ 'dist/' ]
}
},
replace: {
map: {
src: ['www/_assets/css/*.css'],
overwrite: true,
replacements: [{
from: 'sourceMappingURL=main.css.map',
to: 'Map removed'
},{
from: 'sourceMappingURL=main-ie8.css.map',
to: 'Map removed'
}]
},
scripts: {
src: ['www/apprentice/*.html', 'www/test/apprentice/*.html', 'www/trainee/*.html', 'www/employer/*.html', 'www/*.html'],
overwrite: true,
replacements: [{
from: 'scripts.js',
to: 'scripts.min.js'
}]
}
},
prettify: {
options: {
indent: 2,
wrap_line_length: 78,
brace_style: 'expand',
},
// Specify a number to padcomments
dist: {
files: [
{expand: true, cwd: 'dist/', src: ['apprentice/*.html', 'trainee/*.html', 'employer/*.html', '*.html'], dest: 'dist/', ext: '.html'}
]
},
prototype: {
files: [
{expand: true, cwd: 'prototype/', src: ['apprentice/*.html', 'trainee/*.html', 'employer/*.html', '*.html', '!pattern-library.html'], dest: 'prototype/', ext: '.html'}
]
}
},
devUpdate: {
main: {
options: {
updateType: 'report', //just report outdated packages
reportUpdated: false, //don't report already updated packages
semver: true, //use package.json semver rules when updating
packages: { //what packages to check
devDependencies: true, //only devDependencies
dependencies: false
},
packageJson: null //find package.json automatically
}
}
},
browserSync: {
dev: {
bsFiles: {
src : [
'www/_assets/css/*.css',
'www/_assets/js/scripts.js',
'www/**/*.html'
]
},
options: {
ghostMode: {
clicks: true,
scroll: true,
links: true,
forms: true
},
watchTask: true,
server: {
baseDir: "www"
}
}
}
},
connect: {
server: {
options: {
port: 7000,
base: 'www',
livereload: true,
open: true
}
}
},
uncss: {
offline: {
options: {
// csspath : 'www/_assets/css/',
// urls : ['http://localhost:/mypage', '...'], // Deprecated
timeout : 1000,
},
files: {
'www/offline/offline.css': ['www/offline/maintenance.html']
}
}
}
});
[
'assemble',
'grunt-modernizr',
'grunt-contrib-imagemin',
'grunt-imageoptim',
'grunt-contrib-uglify',
'grunt-contrib-jshint',
'grunt-sass',
'grunt-uncss',
'grunt-criticalcss',
'grunt-contrib-concat',
'grunt-text-replace',
'grunt-contrib-watch',
'grunt-copy-to',
'grunt-contrib-clean',
'grunt-contrib-compress',
'grunt-pngmin',
'grunt-browser-sync',
'grunt-dev-update',
'grunt-contrib-connect',
'grunt-prettify'
].forEach(function (task) {
grunt.loadNpmTasks(task);
});
grunt.registerTask('images', ['imageoptim']);
grunt.registerTask('modern', ['modernizr']);
grunt.registerTask('offline', ['uncss:offline']);
grunt.registerTask('dev', ['jshint', 'concat:dev', 'sass', 'assemble', 'connect', 'watch']);
grunt.registerTask('sync', ['jshint', 'concat:dev', 'sass', 'assemble', 'browserSync', 'watch']);
grunt.registerTask('proto', ['uglify:dist', 'replace:map', 'clean:prototype', 'replace:scripts', 'copyto:prototype', 'prettify:prototype']);
grunt.registerTask('sprint', ['clean:sprint', 'copyto:sprint']);
grunt.registerTask('dist', ['uglify:dist', 'replace:map', 'clean:dist', 'replace:scripts', 'copyto:dist', 'prettify:dist']);
grunt.registerTask('beta', ['copyto:beta']);
};
As I have not created this myself, I am reluctant to go ahead and break it but is there anything there which stands out? I have seen similar questions but as the grunt files need to vary it is difficult to find the correct answer for this.
Many thanks!
Since file doesn't have default task you have to either create it or run grunt with one of defined tasks:
> grunt dev
or add default task in your Gruntfile.js
grunt.registerTask('default',['dev']);
please refer to http://gruntjs.com/creating-tasks for more information

Grunt isn't making changes when watching

Grunt says waiting... but doesn't make changes to sass while watching. I have to run grunt on the command line every time I want it to run and change things. The thing I need it to update the most is the sass to css. But it's not working. Please help.
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
// package options
express: {
server: {
options: {
port: 3000,
hostname: 'localhost',
bases: 'public' // the 'public' folder for your project
}
}
},
jshint: {
options: {
jshintrc: '.jshintrc' // jshint config file
},
all: [
'Gruntfile.js',
'js/*.js'
]
},
concat: {
basic: {
src: [
'bower_components/jquery/dist/jquery.js',
'bower_components/foundation/js/foundation/foundation.js',
'dev/js/jquery.royalslider.custom.min.js',
'dev/js/royalslider.js',
'dev/js/megamenu_plugins.js',
'dev/js/megamenu.min.js',
'dev/js/megamenu.js',
'dev/js/app.js'
],
dest: 'dev/tmp/app.js'
},
extras: {
src: [
'bower_components/modernizr/modernizr.js'
],
dest: 'dev/tmp/modernizr.js'
}
},
sass: {
options: {
includePaths: ['bower_components/foundation/scss']
},
dist: {
options: {
outputStyle: 'compressed'
},
files: {
'public/build/css/app.min.css': 'dev/scss/app.scss'
}
}
},
imagemin: {
dynamic: {
files: [{
expand: true,
cwd: 'dev/img/',
src: ['**/*.{png,jpg,gif}'],
dest: 'public/build/img/'
}]
}
},
uglify: {
build: {
files: {
'public/build/js/modernizr.min.js': 'dev/tmp/modernizr.js',
'public/build/js/app.min.js': 'dev/tmp/app.js'
}
}
},
clean: {
dist: [
'tmp/**',
'public/build/img/**'
]
},
watch: {
grunt: {
files: ['Gruntfile.js']
},
css: {
files: ['scss/*.scss'],
tasks: ['newer:sass'],
options: {
spawn: false
}
},
js: {
files: [
'js/*.js'
],
tasks: ['newer:concat', 'newer:uglify'],
options: {
livereload: true,
atBegin: true
}
},
imagemin: {
files: [
'img/**'
],
tasks: ['newer:imagemin'],
options: {
livereload: true,
atBegin: true
}
}
}
});
// Load tasks
grunt.loadNpmTasks('grunt-sass');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-notify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-imagemin');
grunt.loadNpmTasks('grunt-express');
grunt.loadNpmTasks('grunt-newer');
// Register default tasks
grunt.registerTask('build', ['sass']);
grunt.registerTask('default', ['build','watch']);
}

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