GruntJS, Multiple Tasks and grunt.option - javascript

I am starting to use GruntJS for one of my projects.
I have easily accomplished to write a simple build script using simple aliases.
However my script contains many tasks that are basically the same, the only difference are some parameters like the source folder and destination folder.
For example:
sass: {
options:{
trace: true,
debugInfo: true,
style: 'compressed'
},
html: {
files: {
'build/html/css/main.css': 'sass/html.sass'
}
},
html2: {
files: {
'build/html2/css/main.css': 'sass/html2.sass'
}
},
html3: {
files: {
'build/html3/css/main.css': 'sass/html3.sass'
}
}
}
What I would like to achieve is to have only one task and then pass parameters (dest,src) to that task.
I tried to implement this using MultiTasks:
grunt.registerTask('sass2', 'Run all Sass compilation tasks.', function() {
var projects = ['html','html2','html3'];
projects.forEach(function(proj){
grunt.config.set('sass.files.dest', 'build/' + proj + '/css/main.css');
grunt.config.set('sass.files.src', 'sass/' + proj + '.sass');
grunt.log.writeln(grunt.config.get('sass.files.dest'));
grunt.log.writeln(grunt.config.get('sass.files.src'));
grunt.task.run('sass');
});
});
Grunt log outputs the right values for the params, however only the html3 sass gets compiled.
I do not understand why only one of the projects gets compiled and how could I fix this.
Perhaps there is another way to solve this problem. A better way. Maybe using templates?
Any kind of help or tips would be appreciated.
Thank you!

Only the last configuration is used because grunt.task.run queues them to run after the current task is complete. From the API:
Enqueue one or more tasks. Every specified task in taskList will be run immediately after the current task completes, in the order specified
What you have is:
Set config 1
Set config 2
Set config 3
Run three tasks using the currently active config, which is #3.
Instead, you can do something like this to dynamically create lots of config sections and then run those tasks:
grunt.config.set('sass.' + proj + '.files', [{
src: 'sass/' + proj + '.sass',
dest: 'build/' + proj + '/css/main.css'
}]);
and then call runtask with your newly-created section:
grunt.task.run('sass:' + proj);

Related

How to run unit tests in mtime order with Grunt and Mocha

So when you do TDD, do you wait it to run all tests till the one you're working on? It takes too much time. When I'm in rush, I rename test file to something like aaaaaaaaaaaaaaaa_testsomething.test.js so it's run first and I see errors asap.
I don't like this approach and I'm sure there's solution, but I can't find it. So what is the easiest way to run unit tests in mtime order with Mocha? There's -sort option, but it sorts files by name only. How can I sort them by modification time?
There's my Gruntfile.js:
module.exports = function(grunt) {
grunt.initConfig({
watch: {
tests: {
files: ['**/*.js', '!**/node_modules/**'],
tasks: ['mochacli:local']
}
},
mochacli: {
options: {
require: ['assert'],
reporter: 'spec',
bail: true,
timeout: 6000,
sort: true,
files: ['tests/*.js']
},
local: {
timeout: 25000
}
}
});
grunt.loadNpmTasks('grunt-mocha-cli');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('test', ['mochacli:local']);
grunt.registerTask('livetests', [ 'watch:tests']);
};
Note: it's not duplicate. I don't want to edit my tests or Gruntfile.js each time I save source code file. I'm asking about how to modify Grunt task so it runs tests from last modified *.test.js file first. Sort unit tests by mtime, as stated in Title.
Simple scenario: I open test1.test.js in editor, change it, hit Ctrl+B and it runs unit tests from test1.test.js then test4.test.js. I open test4.test.js, hit Ctrl+S, Ctrl+B and it runs tests from test4.test.js then test1.test.js
I'm thinking about some Grunt plugin to sort files first, so I can put its results there insted of 'tests/*.js' with grunt.config.set('mochacli.options.files', 'tests/recent.js,tests/older.js', ....); but I can't find anything I can use as middleware there, don't want to invent bycicle as I'm sure there's something for this implemented already.
don't want to invent bicycle as I'm sure there's something for this implemented already.
...Sometimes you have to ride the bicycle ;)
Solution
This can be achieved by registering an intermediate custom-task in your Gruntfile.js to perform the following dynamically:
Obtain filepaths for all unit test files (.js) utilizing grunt.file.expand with the appropriate globbing pattern.
Sort each matched filepath by the files mtime/modified-date.
Configure the mochacli.options.file Array with the chronologically sorted filepaths using grunt.config
Run the local Target defined in the mochacli Task using grunt.task.run
Gruntfile.js
Configure your Gruntfile.js as follows:
module.exports = function(grunt) {
// Additional built-in node module.
var statSync = require('fs').statSync;
grunt.initConfig({
watch: {
tests: {
files: ['**/*.js', '!**/node_modules/**', '!Gruntfile.js'],
tasks: ['runMochaTests']
}
},
mochacli: {
options: {
require: ['assert'],
reporter: 'spec',
bail: true,
timeout: 6000,
files: [] // <-- Intentionally empty, to be generated dynamically.
},
local: {
timeout: 25000
}
}
});
grunt.loadNpmTasks('grunt-mocha-cli');
grunt.loadNpmTasks('grunt-contrib-watch');
/**
* Custom task to dynamically configure the `mochacli.options.files` Array.
* All filepaths that match the given globbing pattern(s), which is specified
# via the `grunt.file.expand` method, will be sorted chronologically via each
* file(s) latest modified date (i.e. mtime).
*/
grunt.registerTask('runMochaTests', function configMochaTask() {
var sortedPaths = grunt.file.expand({ filter: 'isFile' }, 'tests/**/*.js')
.map(function(filePath) {
return {
fpath: filePath,
modtime: statSync(filePath).mtime.getTime()
}
})
.sort(function (a, b) {
return a.modtime - b.modtime;
})
.map(function (info) {
return info.fpath;
})
.reverse();
grunt.config('mochacli.options.files', sortedPaths);
grunt.task.run(['mochacli:local']);
});
grunt.registerTask('test', ['runMochaTests']);
grunt.registerTask('livetests', [ 'watch:tests']);
};
Additional Notes
Using the configuration above. Running $ grunt livetests via your CLI, then subsequently saving a modified test file will cause Mocha to run each test file in chronological order based on the files last modified date (I.e. The most recent modified file will run first and the last modified file will run last). This same logic applies when running $ grunt test too.
However, if you want Mocha to run the most recent modified file first, yet run the other files in normal order (I.e. by name), then the custom runMochaTests Task in the Gruntfile.js above should be replaced with the following logic instead:
/**
* Custom task to dynamically configure the `mochacli.options.files` Array.
* The filepaths that match the given globbing pattern(s), which is specified
# via the `grunt.file.expand` method, will be in normal sort order (by name).
* However, the most recently modified file will be repositioned as the first
* item in the `filePaths` Array (0-index position).
*/
grunt.registerTask('runMochaTests', function configMochaTask() {
var filePaths = grunt.file.expand({ filter: 'isFile' }, 'tests/**/*.js')
.map(function(filePath) {
return filePath
});
var latestModifiedFilePath = filePaths.map(function(filePath) {
return {
fpath: filePath,
modtime: statSync(filePath).mtime.getTime()
}
})
.sort(function (a, b) {
return a.modtime - b.modtime;
})
.map(function (info) {
return info.fpath;
})
.reverse()[0];
filePaths.splice(filePaths.indexOf(latestModifiedFilePath), 1);
filePaths.unshift(latestModifiedFilePath);
grunt.config('mochacli.options.files', filePaths);
grunt.task.run(['mochacli:local']);
});
If you're using mocha, you can set .only on the test you are interested in:
describe(function () {
// these tests will be skipped
});
describe.only(function () {
// these tests will run
})

Register new grunt task to work with a supplied target

My Gruntfile defines 3 targets "dev", "beta", and "production" across several tasks. If I run grunt task:target it works correctly for that task.
Now I want to combine all my tasks into a single build task. I tried:
grunt.registerTask('build', ['task1', 'task2', 'task3']);
but if I run grunt build:dev, it ignores my target and simply runs all targets for all the build tasks.
This works:
grunt.registerTask('build:dev', ['task1:dev', 'task2:dev', 'task3:dev']);
grunt.registerTask('build:beta', ['task1:beta', 'task2:beta', 'task3:beta']);
grunt.registerTask('build:production', ['task1:production', 'task2:production', 'task3:production']);
But I'm sure it's not the correct way, nor does it scale well.
I was able to do what I wanted with this:
grunt.registerTask('build', 'My build task', function(targetName) {
runTask('task1', targetName);
runTask('task2', targetName);
runTask('task3', targetName);
});
function runTask(task, targetName) {
if (targetName) {
grunt.task.run(task + ':' + targetName);
} else
{
grunt.task.run(task);
}
}
It works, but I'm wondering if I'm missing something already built into Grunt to accomplish the same.

Most efficient way to Grunt concat directories files?

I apologize for the very awkward question title, if anyone can think of a better way to phrase this question I'll change it immediately.
I'm building an app with Angular and RequireJS and to try to optimize performance, dependencies and lazy-loading I'm looking to build a file structure like this:
/app
----/regisitration
--------_registration.module.js
--------registration.ctrl.js
--------registration.svc.js
--------registration.directive.js
----/courses
--------_courses.module.js
--------courses.directive.js
--------courses.controller.js
--------courses.service.js
--------/course
------------course.controller.js
----/admin
--------_admin.module.js
--------admin.controller.js
Where I set up my routing, I want to be able to have a user who goes to any /registration/ view load the entirety of _registration.module.js which would be the concatenation of all the other .js files within the /registraion directory (and any sub directories) so that my team isn't bogged down by needing to include several and possibly duplicate dependencies as well as serve the entirety of the site "section" to the user in one shot. Hopefully the sample above shows why I wouldn't want to just front-load all the files, because most users will never hit the admin section of the site. I'm trying to figure out the most efficient way to achieve this with grunt, but so far I'm working very manually with code like this:
grunt.initConfig({
concat: {
app: {
files: [
{
src: ['..app/registration/*.js', '!..app/registraion/*.module.js'],
dest: '..app/registration/_registration.module.js'
},
{
src: ['..app/courses/*.js', '!..app/courses/*.module.js'],
dest: '..app/courses/_courses.module.js'
},
{
src: ['..app/admin/*.js', '!..app/admin/*.module.js'],
dest: '..app/admin/_admin.module.js'
}
],
}
},
});
I think there must be a more efficient and less manual way to do what I'm trying to achieve. Does anyone have any suggestions?
Remember that you can still execute JavaScript within your Gruntfile.
grunt.initConfig({
concat: {
app: {
files: grunt.file.expand({ cwd: 'app', filter: 'isDirectory' }, '*')
.map(function(ngModule) {
return {
src: ['app/' + ngModule + '/*.js', '!app/' + ngModule + '/*.module.js'],
dest: 'app/' + ngModule + '/_' + ngModule + '.module.js'
};
})
}
},
});
With this, you should be able to create new modules without needing to remember to update a config entry for them.

How to concatenate groups of scripts separately in Grunt?

I was looking into the possibility of building a webapp that conditionally loads groups of scripts based on the screen size and/or client of the user, because there may be some UX or client specific code that I might load based on this information.
I saw another question that showed how to have files minified separately (see link), but I wasn't sure if there was an option to take all scripts in group A, B, and C and minify them separately to groupA.min.js, groupB.min.js, and groupC.min.js. Is this possible?
Thanks,
- Daniel
Example:
uglify: {
dist: {
files: {
'dist/shared.min.js': 'src/shared/*', //would recurse shared directory and concatenate/minify all JS in subdirectories
'dist/desktop.min.js': 'src/platform/desktop/*',
'dist/mobile.min.js': 'src/platform/mobile/*',
'dist/ios.min.js': 'src/platform/ios/*',
'dist/android.min.js': 'src/platform/android/*'
}
}
}
Something along these lines should work according to the docs, which I've partly butchered:
grunt.initConfig({
concat: {
groupA: {
// concat task "groupA" target options and files go here.
dist: {
// the files to concatenate
src: ['src/groupA/*.js'],
// the location of the resulting JS file
dest: 'dist/groupA.concat.js'
}
},
groupB: {
// concat task "groupB" target options and files go here.
},
},
uglify: {
groupA: {
// uglify task "groupA" target options and files go here.
},
groupB: {
// uglify task "groupB" target options and files go here.
},
},
});
Then you can run specific task with grunt concat:groupA or grunt concat:groupB.

Grunt - Dynamically Set Config Before Running a Task

So here is my hypothetical config object for a hypothetical fooTask that does something (not relevant to question) to a bunch of JS files
grunt.initConfig({
fooTask: {
app1: 'app1/*.js',
app2: 'app2/*.js',
app3: 'app3/*.js'
}
});
As you can see, with this approach, I have to run fooTask 3 times with each app specified as a target:
grunt fooTask:app1
grunt fooTask:app2
grunt fooTask:app3
Needless to say this does not scale as either the number of apps increase or the number of such foo tasks increase as one has to C&P the same code over and over for each app.
So ideally what I would like to define is just one target with the name of the app passed in as a config variable
grunt.initConfig({
fooTask: {
dist: '<%=appName%>/*.js'
}
});
I would then like to call fooTask 3 times, one for each app, with the right app set as appName
var apps = ['app1', 'app2', 'app3'];
apps.forEach(function(app) {
var currAppName = app;
// Run fooTask but how do I specify the new currAppName config?
grunt.task.run('fooTask');
});
As from code above, I know I can run my fooTask using grunt.task.run but how do I set the appName config for my task?
Note that this question is similar to this other one that also does not have the right answer yet - Pass Grunt config options from task.run
Thanks a lot.
EDIT 2:
So nevermind the garbage below the first edit, leaving as example of what doesn't work. In my case it was really important to be able to set the value within a task at run-time so I settled on the file system. Perhaps it suits your needs.
grunt.initConfig({
someTask: {
someKey: fs.readFileSync('file.txt', { encoding: 'utf8' })
}
});
of course you can do the readFile outside of the task if you need a bunch of different app names.
EDIT:
Hmmm. I swear I had this working when I wrote this...but now it is not. Grunt just sees the extra arguments as additional unfound tasks.
I was trying to figure this out myself, finally a "duh" just moment happened - why not parse process.argv before grunt.initConfig?
module.exports = function(grunt) {
var sourcefile = process.argv[2] || 'default.js'; // <- this
grunt.initConfig({
uglify: {
main: {
src: sourcefile, // <- voila :)
dest: sourcefile.substring(0, sourcefile.length-3) + '.min.js'
}
}
});
grunt.loadNpmTasks('uglify');
grunt.registerTask('default', ['uglify']);
};
and use from command line:
grunt mykillerscript.js
I didn't even try to use grunt.option for the same reason that all the examples only showed directing which task is run, but I wouldn't be surprised if there is a more "grunt" way to do this.

Categories