How to avoid unnecessary uglifying in GruntJS? - gruntjs

I have a grunt file with the following definition:
uglify: {
build: {
src: 'www/temp/application.js', // a concatenation of files via grunt-contrib-concat
dest: 'www/temp/application.min.js'
}
},
what I would really like to do is to recompute the final application.min.js only in case that application.js file was changed. More precisely, I want to add the condition:
# pseudocode
if (getFileContents(application.js) == getFileContents(previously.uglified.application.js)) {
// do nothing
} else {
// run uglifying on application.js
}
Reason:
I deploy my project by git and uglifying is relatively slow (3+ seconds) and moreover, it is unnecessary since I don't change JS files often.

There are several possible solutions:
You can create your own grunt task that will check files for last modify time using for example fs.stat then run uglify task through grunt.task.run with prepared options as argument.
Or you can build files object dynamically passing it through filter function:
var fs = require('fs');
module.exports = function (grunt) {
function filterChanged(files) {
var mtime = '',
stats;
for (var dest in files) {
stats = fs.statSync(files[dest]);
try {
mtime = fs.readFileSync(files[dest] + '.mtime', 'utf8');
}
catch (ex) {
fs.writeFileSync(files[dest] + '.mtime', stats.mtime, 'utf8');
return files;
}
if (stats.mtime == mtime || !mtime) {
delete files[dest];
}
else {
fs.writeFileSync(files[dest] + '.mtime', stats.mtime, 'utf8');
}
}
return files;
}
grunt.initConfig({
uglify: {
build: {
files: filterChanged({
'www/temp/application.min.js': 'www/temp/application.js'
})
}
}
});
};
This causes invoke of filterChanged function every time uglify task runs.

Related

Enabling live reload with Grunt on a Vagrant-hosted project

My project uses Vagrant; it's served as https://domain.dev:8443.
The goal is to reload the CSS into the page whenever the CSS changes, without requiring a full page reload.
I am trying to achieve this without any manual action, this means I do not want to use a browser-plugin. Everything should be able to get up and running using the task grunt develop.
grunt-contrib-watch is used to watch 3 different targets, namely css, scss and js. Because these tasks are concurrent, but sadly blocking, I also use grunt-focus to run these 3 tasks at the same time, which correctly works. Whenever changing any file that should be watched, I get a notification in my console that it changed. Awesome.
grunt-contrib-watch recommends connect-livereload, which is middleware for grunt-contrib-connect. This would enable me to get the desired behaviour, but I do not know how to set it up properly.
This is my Gruntfile, note that I did leave out a lot of tasks that are not related to this issue, so it will not run.
module.exports = function(grunt) {
'use strict';
//Load all dependencies that we need
var ConnectLiveReload = require('connect-livereload')();
//Load all packages that we need into Grunt
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-focus');
//The watcher needs a clean object to store data to
var files = Object.create(null);
//Initialize Grunt with the options for all tasks
grunt.initConfig({
connect: {
options: {
port: 0
},
develop: {
options: {
middleware: function(connect) {
return [ConnectLiveReload];
}
}
}
},
focus: {
develop: {}
},
watch: {
scss: {
tasks: ['build'],
files: ['app/webroot/**/*.scss'],
options: {
spawn: false
}
},
css: {
files: ['app/webroot/**/*.css'],
options: {
spawn: false,
livereload: true
}
},
js: {
tasks: ['jasmine:test', 'eslint'],
files: dictionary.javascript,
options: {
spawn: false
}
}
}
});
//Sets the target config for specific tasks 200ms after the last change
var onChange = grunt.util._.debounce(function() {
var target = getTarget();
grunt.config('eslint.target', target);
}, 200);
//Listen to changed files using the watcher
grunt.event.on('watch', function(action, filepath, target) {
files[filepath] = action;
onChange();
});
/**
* Gets the path from the dictionary and adds a JavaScript file selector to it.
*
* #method getPath
* #param [pathkey=webroot]
* #return {String}
*/
function getPath(pathkey) {
pathkey = pathkey || 'webroot';
return (dictionary[pathkey] || pathkey) + '/*.js';
}
/**
* Gets an array of the last-changed files as detected by the watcher.
*
* #method getTarget
* #return {Array}
*/
function getTarget() {
var target = Object.keys(files);
files = Object.create(null);
return target;
}
/**
* #task develop
* #runs watch:css
* #runs watch:scss
* #runs watch:js
*/
grunt.registerTask('develop', 'Start the automated development tasks', function() {
grunt.task.run(['connect:develop', 'focus:develop']);
});
};
http://localhost:35729/livereload.js is a valid file and I can visit in my browser, but the script is not being added to the page. How would I do this?

Access variables from other tasks when using load-grunt-config?

I'm using load-grunt-config to split my gruntfile and I have a requirement to access the variables of one task in another. The tasks are being exported using module.exports. Is it possible to access job variables in one task from another task that runs after it?
module.exports= function(grunt) {
combine: {
files: {
'dist/lib.min.css' : ['file1', 'file2']
}
}
}
module.exports = function (grunt) {
for (var file in //grunt.combine.files???
//do stuff with files
}
It looks like you are in need of a shared configuration between two tasks.
load-grunt-config exposes the 'data' object that lets you tweak the grunt configuration.
module.exports = function(grunt) {
require('load-grunt-config')(grunt, {
// data passed into config.
data: {
files: ['file1', 'file2']
}
});
}
that you can later use, in your example, as such
module.exports = function(grunt) {
combine: {
files: {
'dist/lib.min.css' : '<%= files %>'
}
}
}
module.exports = function (grunt) {
for (var file in grunt.config('files')) {
//do stuff with files
}
}

run concat task dynamically with destination file dynamically created

I'm running a grunt concat task on one of my projects and it looks something like this:
/**
* Concatenate | Dependencies Scripts
*/
concat: {
dependencies: {
files: {
"./Ditcoop/js/plugins.min.js": ["./Ditcoop/js/vendor/**/*.min.js", "!./Ditcoop/js/vendor/modernizr/*.js", "!./Ditcoop/js/vendor/jquery/*.js"],
"./Global/js/plugins.min.js": ["./Global/js/vendor/**/*.min.js", "!./Global/js/vendor/modernizr/*.js", "!./Global/js/vendor/jquery/*.js"],
"./Webshop/js/plugins.min.js": ["./Webshop/js/vendor/**/*.min.js", "!./Webshop/js/vendor/modernizr/*.js", "!./Webshop/js/vendor/jquery/*.js"]
}
}
}
My question would be if I could somehow make that more dynamic without having to specify each root folder. I was thinking of something like this:
concat: {
dependencies: {
files: {
"./*/js/plugins.min.js": ["./*/js/vendor/**/*.min.js", "!./*/js/vendor/modernizr/*.js", "!./*/js/vendor/jquery/*.js"],
}
}
}
I'm pretty sure I cannot do it this way, but I could use the expand option, I'm just not sure how I could use it so I can do that under the right root folder, so I won't create the same destination file as many times I run the concat.
Always remember Gruntfiles are javascript :)
grunt.initConfig({
concat: {
dependencies: {
files: (function() {
var files = Object.create(null);
grunt.file.expand({filter: 'isDirectory'}, '*').forEach(function(dir) {
files[dir + '/js/plugins.min.js'] = [
dir + '/js/vendor/**/*.min.js',
'!' + dir + '/js/vendor/modernizr/*.js',
'!' + dir + '/js/vendor/jquery/*.js'
];
});
return files;
}()),
},
},
});
But if your dependency handling logic is this complex you may want to consider using a module loader such as browserify or requirejs. The concat task is really just for joining simple files together.

Conditionally running tasks in grunt if some files are changed

I'm new to Grunt, and from what I understood up till now, Grunt has the "watch" task, which continuously checks files for modifications, and each time modification happens, runs corresponding tasks.
What I'm looking for would be a kind of discrete version of this - a task, that would run other tasks, if and only if some files were changed since the last build.
Seems to be a natural thing to ask for, but I couldn't find this. Is it just me, or is this really an issue?
Configuration file should look like this:
grunt.initConfig({
foo: {
files: "foo/*"
// some task
},
bar: {
files: "bar/*"
// some other task
},
ifModified: {
foo: {
files: "foo/*",
tasks: ['foo']
},
bar: {
files: 'bar/*',
tasks: ['bar', 'foo']
}
}
});
grunt.registerTask('default', ['bar', 'foo']);
Running grunt should always execute tasks 'bar', 'foo', while running grunt ifModified should execute any tasks only if some of the files were actually changed since the previous build.
Made my own task for that. It turned out to be not hard, here is the code:
build/tasks/if-modified.js:
var fs = require('fs');
var crypto = require('crypto');
module.exports = function (grunt) {
grunt.registerMultiTask('if-modified', 'Conditionally running tasks if files are changed.', function () {
var options = this.options({});
grunt.verbose.writeflags(options, 'Options');
var hashes = {};
if (grunt.file.exists(options.hashFile)) {
try {
hashes = grunt.file.readJSON(options.hashFile);
}
catch (err) {
grunt.log.warn(err);
}
}
grunt.verbose.writeflags(hashes, 'Hashes');
var md5 = crypto.createHash('md5');
this.files.forEach(function (f) {
f.src.forEach(function (filepath) {
var stats = fs.statSync(filepath);
md5.update(JSON.stringify({
filepath: filepath,
isFile: stats.isFile(),
size: stats.size,
ctime: stats.ctime,
mtime: stats.mtime
}));
});
});
var hash = md5.digest('hex');
grunt.verbose.writeln('Hash: ' + hash);
if (hash != hashes[this.target]) {
grunt.log.writeln('Something changed, executing tasks: ' + JSON.stringify(options.tasks));
grunt.task.run(options.tasks);
hashes[this.target] = hash;
grunt.file.write(options.hashFile, JSON.stringify(hashes));
}
else
grunt.log.writeln('Nothing changed.');
});
};
Gruntfile.js:
grunt.initConfig({
foo: {
src: ["foo/**/*"],
dest: "foo-dest"
// some task
},
bar: {
src: ["bar/**/*", "foo-dest"]
// some other task
},
'if-modified': {
options: {
hashFile: 'build/hashes.json'
},
foo: {
src: ['foo/**/*', 'Gruntfile.js', 'package.json'],
options: {tasks: ['foo']}
},
bar: {
src: ['bar/**/*', "foo-dest", 'Gruntfile.js', 'package.json'],
options: {tasks: ['bar']}
}
}
});
grunt.loadTasks('build/tasks'); // if-modified.js in this dir
grunt.registerTask('default', ['foo', 'bar']);
run:
grunt if-modified
You could create a task that runs conditionally other tasks, from https://github.com/gruntjs/grunt/wiki/Creating-tasks :
grunt.registerTask('foo', 'My "foo" task.', function() {
// Enqueue "bar" and "baz" tasks, to run after "foo" finishes, in-order.
grunt.task.run('bar', 'baz');
// Or:
grunt.task.run(['bar', 'baz']);
});
What you need might be grunt-newer :
The newer task will configure another task to run with src files that are a) newer than the dest files or b) newer than the last successful run (if there are no dest files). See below for examples and more detail.
https://github.com/tschaub/grunt-newer

Can GruntJS fetch external files?

For example I have index.css that contains links to another css files:
#import "http://<some_url>/bootstrap.css";
#import "http://<some_url>/plugin.css";
#import "app.css";
It is possible to concatenate these files with Grunt JS?
Looking at the Grunt API, the file stuff appears to apply exclusively to local files.
http://gruntjs.com/api/grunt.file
Furthermore I haven't seen anything in the source code where it parses CSS files looking for imports.
To get them concatenated together:
I'd recommend that you download the files locally, put them in your usual css folder then use Grunt concat as normal.
I'd then write a small script using wget to download fresh copies of these dependencies before building with grunt.
I know it's been a while since this was asked, but I came across it while trying to do something similar. Here is one way to save a file from a url using a grunt task.
module.exports = function(grunt) {
'use strict';
var http = require('http');
grunt.initConfig({
watch: {
scripts: {
files: ['**/*.cfc'],
tasks:['saveURL']
}
},
open:{
error:{
path:'http://<server>/rest/error.html'
}
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-open');
grunt.registerTask('default', ['watch']);
grunt.registerTask('saveURL', 'Write stuff to a file', function() {
var done = this.async();
var reloadurl = 'http://<server>/rest/index.cfm?rl';
grunt.log.writeln('Loading URL:' + reloadurl + ' ...');
http.get(reloadurl, function(res) {
var pageData = "";
if(res.statusCode != '200'){
//if we don't have a successful response queue the open:error task
grunt.log.error('Error Reloading Application!: ' + res.statusCode);
grunt.task.run('open:error');
}
res.setEncoding('utf8');
//this saves all the file data to the pageData variable
res.on('data', function (chunk) {
pageData += chunk;
});
res.on('end', function(){
//This line writes the pageData variable to a file
grunt.file.write('error.html', pageData)
done();
});
}).on('error', function(e) {
console.log("Got error: " + e.message);
done(false);
});
});
};

Resources