I just started using Grunt. I'm trying to uglify a set of files with Grunt, and then create a revision of that file (with grunt-filerev). Everything is fine, except the sourcemaps for the file.
I uglify my files into 1 file called shared.min.js. I also create a sourcemap for this file, and filerev creates a revisioned file and a sourcemap for the revisioned file as well. This means it will generate 4 files:
shared.min.js
shared.min.js.map
shared.min.153987b3.js
shared.min.153987b3.js.map
The problem is that the shared.min.153987b3.js.map uses shared.min.js as it's source, I think it should be shared.min.153987b3.js instead. As you can see in my Gruntfile, I dirty-fixed this with a task called fixFilerevSourcemap.
Then the next problem occured: shared.min.153987b3.js uses shared.min.js.map as it's sourcemap, I think it should be shared.min.153987b3.js.map. Should I dirty-fix this as well? Or is there a better solution?
I have the following Grunt configuration:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
sourceMap: true,
sourceMapIncludeSources: true
},
dist: {
files: {
'somedir/shared.min.js': ['otherdir/file1.js', 'otherdir/file2.js'],
}
}
},
filerev: {
options: {
sourceMap: true
},
scripts: {
src: ['somedir/shared.min.js'],
dest: 'somedir/'
}
}
});
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-filerev');
grunt.registerTask('fixFilerevSourcemap', 'Blaat', function() {
var revisionedSource;
for (var file in grunt.filerev.summary) {
var revisionedFile = grunt.filerev.summary[file];
if (revisionedFile.substr(revisionedFile.length - 3) == 'map') {
if (null == revisionedSource) {
throw "No revisionedSource found!";
}
var mapData = grunt.file.readJSON(revisionedFile);
mapData.file = basename(revisionedSource);
grunt.file.write(revisionedFile, JSON.stringify(mapData));
} else {
revisionedSource = basename(revisionedFile);
}
}
});
grunt.registerTask('default', ['uglify', 'filerev', 'fixFilerevSourcemap']);
function basename(path, suffix) {
// discuss at: http://phpjs.org/functions/basename/
// original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// improved by: Ash Searle (http://hexmen.com/blog/)
// improved by: Lincoln Ramsay
// improved by: djmix
// improved by: Dmitry Gorelenkov
// example 1: basename('/www/site/home.htm', '.htm');
// returns 1: 'home'
// example 2: basename('ecra.php?p=1');
// returns 2: 'ecra.php?p=1'
// example 3: basename('/some/path/');
// returns 3: 'path'
// example 4: basename('/some/path_ext.ext/','.ext');
// returns 4: 'path_ext'
var b = path;
var lastChar = b.charAt(b.length - 1);
if (lastChar === '/' || lastChar === '\\') {
b = b.slice(0, -1);
}
b = b.replace(/^.*[\/\\]/g, '');
if (typeof suffix === 'string' && b.substr(b.length - suffix.length) == suffix) {
b = b.substr(0, b.length - suffix.length);
}
return b;
}
};
Related
I want to convert all my scss files(located in sass/ folder) to css files(located in css/ folder) automatically on save. And I want it to work even with the new ones. For example, I had index-style.scss and header-style.scss, already converted to css, but if I create another scss file, like footer-style.scss, I want it to convert to css AUTOMATICALLY, without editing Gruntfile or something.
scss and css files will always match
I tried:
files: {
'src/main/webapp/WEB-INF/css/' : 'src/main/webapp/WEB-INF/sass/'
}
there were no compiling errors, but it didn't convert scss file. Also tried :
files: {
'src/main/webapp/WEB-INF/css/*.css' : 'src/main/webapp/WEB-INF/sass/*.scss'
}
Is this even possible?
P.S.
I also tried adding this code to the Gruntfile.js:
let cssDir = "src/main/webapp/WEB-INF/css/";
let sassDir = "src/main/webapp/WEB-INF/sass/";
let filesObj = {}
// filesObj[cssDir+"login-page.css"] = sassDir+"login-page.scss"
let fs = require('fs')
fs.readdir(sassDir, function (err, files) {
if (err) {
console.log("could not list the dir" + err);
process.exit(1);
}
files.forEach(function (file, index) {
console.log("forEach is working!")
let cssPath = cssDir+file;
fs.access(cssPath,(err) => {
console.log("fs.access is working")
if (err) {
fs.appendFile(cssPath, '', function (err) {
console.log("appendFile is working")
if (err) {
console.log("Grunt file 17 line error");
process.exit(1);
}
})
}
})
console.log("\n\n==========================================\n\n"+cssPath+"\n\n"+sassDir+'file\n')
filesObj[cssDir+'file'] = sassDir+'file'
})
})
...
sass: {
dist: {
options: {
sourcemap: false,
compress: false,
yuicompress: false,
style: 'expanded',
},
files: filesObj
},
}
...
but, it didn't help
I want to be able to have different subprojects inside my main project. For example:
-- my-project/
- Gruntfile.js
- subproject1/
- index.html
- scss/
- main.scss
- subproject2/
- index.html
- scss/
- main.scss
I want to be able to modify a file in subproject1 without triggering subproject2 tasks.
As of right now I'm configuring my gruntfile like so:
watch: {
subproject1: {
files: ['subproject1/*.html', 'subproject1/scss/**/*.scss'],
tasks: ['sass', 'premailer:subproject1']
},
subproject2: {
files: ['subproject2/*.html', 'subproject2/scss/**/*.scss'],
tasks: ['sass', 'premailer:subproject2']
}
},
premailer: {
subproject1: {
options: {
css: 'subproject1/css/main.css',
verbose: false
},
files: [
{
'subproject1/dist/index.html' : 'subproject1/index.html'
}
]
},
subproject2: {
options: {
css: 'subproject2/css/main.css',
verbose: false
},
files: [
{
'subproject2/dist/index.html' : 'subproject2/index.html'
}
]
},
}
Is there a way to dynamically specify to grunt what task to run depending on file modified (eg, I modify folder/index.html, then run premailer:folder) or is this the only way to achieve it ?
You can check all folders in your main folder inside your Gruntfile, using the grunt.file methods (docs here), create an array of subproject names and then using forEach to create your task dynamically.
Something like this should go:
/*global module:false*/
module.exports = function(grunt) {
var mycwd = "./";
var tempFileList = grunt.file.expand(
{ filter: function (src) {
if (grunt.file.isDir(src) == true) {
return true;
}
return false;
} },
[ mycwd + "!(Gruntfile.js|node_modules|package.json)" ] // structure to analyse
);
// I create an empty array to put all elements in, once cleaned.
var fileList = [];
tempFileList.forEach(function(url){
var cwd = url;
cwd = cwd.replace(mycwd, "");
fileList.push(cwd);
})
var watchObject = {};
var premailerObject = {};
fileList.forEach(function(name) {
watchObject[name] = {
files: [name + '/*.html', name + '/scss/**/*.scss'],
tasks: ['sass', 'premailer:' + name]
};
var filesObject = {};
filesObject[name+'/css/main.css'] = name + '/index.html';
premailerObject[name] = {
options: { css: name + '/css/main.css', verbose: false },
files: [ filesObject ]
};
});
var configObject = {
watch: watchObject,
premailer: premailerObject
};
// just to check the final structure
console.log(configObject);
grunt.initConfig(configObject);
};
My Gruntfile is as follows:
module.exports = function(grunt) {
'use strict';
var dictionary = {
'all' : '**',
'html5' : 'some/path'
};
require('time-grunt')(grunt);
require('load-grunt-tasks')(grunt);
grunt.initConfig({
eslint : {
options : {
config : '.eslintrc'
},
target : ['hello/world/js/<HERE>/**.js']
}
});
grunt.registerTask('test', 'Lint a set of files', function(set) {
set = set || 'all';
var path = dictionary[set];
grunt.task.run('eslint');
});
};
Notice the <HERE> in the code. That is where I want the path variable to be inserted. I just have no idea how to do this.
If I type grunt test:html5, the path variable is set to the correct path, so I got that working, now I just need to tell ESLint where to lint. But how?
Edit:
According to the accepted answer, I now have this, which works! I want to share it in case someone else might want to take a look.
module.exports = function(grunt) {
'use strict';
var dictionary = {
'webroot' : 'app/webroot/**',
'html5' : 'app/webroot/js/some/path'
};
require('time-grunt')(grunt);
require('load-grunt-tasks')(grunt);
grunt.initConfig({
eslint : {
options : {
config : '.eslintrc'
},
target : ['<%= path %>']
}
});
grunt.registerTask('test', 'Lint a set of files', function(pathKey) {
pathKey = pathKey || 'webroot';
var path = (dictionary[pathKey] || pathKey) + '/*.js';
console.log('Parsed path as', path);
grunt.config.set('path', path);
grunt.task.run('eslint');
});
};
Save the value of your given path in the grunt config and refer to it:
module.exports = function(grunt) {
'use strict';
var dictionary = {
'all' : '**',
'html5' : 'some/path'
};
require('time-grunt')(grunt);
require('load-grunt-tasks')(grunt);
grunt.initConfig({
eslint : {
options : {
config : '.eslintrc'
},
target : ['hello/world/js/<%= dir %>/**.js']
}
});
grunt.registerTask('test', 'Lint a set of files', function(pathKey) {
var dir = dictionary[pathKey]
grunt.config.set('dir', dir );
grunt.task.run('eslint');
});
};
You can use grunt.option to pass arguments to the Gruntfile.
in the Gruntfile:
grunt.initConfig({
eslint : {
options : {
config : '.eslintrc'
},
target : ['hello/world/js/<%= grunt.option('path') %>/**.js']
}
});
from the CLI: grunt test --path=foo to get 'hello/world/js/foo/**.js'.
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.
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