nodejs - run grunt task programmatically - javascript

i'm using grunt tasks to watch and concatenate some js files.
my first configuration worked fine:
grunt.initConfig({
watch: {
js: {
files: ['web/**/*.js'],
tasks: ['concat:JS']
}
},
concat: {
JS: {
files: {
"index.js": [
"test-1.js",
"test-2.js",
],
"about_us.js": [
"test-3.js",
"test-4.js",
],
"store.js": [
"test-5.js",
"test-6.js",
]
}
}
}
});
my problem is that with this configuration whenever i change a file it concatenates everything.
i would be able to concatenate only a specific file (based on which file changed).
e.g. if i change test-1.js i would like concatenate only the index.js file (..so the task to run should be 'concat:JS:index.js' )
so i tryed adding a watch event
grunt.event.on('watch', function(action, filepath, target) {
var taskName = "concat:JS"; //here i calculate dinamically the specific task name to be run
grunt.task.run(taskName);
});
but nothing happens.. any idea? thanks

New answer post edit to question
Using the grunt.event.on('watch', ...) listener is the wrong tool for this kind of requirement. Instead you should utilize multiple Targets for both the the watch and concat tasks as shown in the Gruntfile.js excerpt below.
Gruntfile.js
grunt.initConfig({
watch: {
'JS-index': {
files: ['web/js/test-1.js', 'web/js/test-2.js'],
tasks: ['concat:JS-index']
},
'JS-about-us': {
files: ['web/js/test-3.js', 'web/js/test-4.js'],
tasks: ['concat:JS-about-us']
},
'JS-store': {
files: ['web/js/test-5.js', 'web/js/test-6.js'],
tasks: ['concat:JS-store']
}
},
concat: {
'JS-index': {
files: {
'dist/js/index.js': [
"web/js/test-1.js",
"web/js/test-2.js",
]
}
},
'JS-about-us': {
files: {
"dist/js/about_us.js": [
"web/js/test-3.js",
"web/js/test-4.js",
]
}
},
'JS-store': {
files: {
'dist/js/store.js': [
"web/js/test-5.js",
"web/js/test-6.js",
]
}
}
}
});
Notes
Given the configuration shown above, the following will happen after running grunt watch via your CLI:
Changing either test-1.js or test-2.js will concatenate both files to dist/js/index.js.
Changing either test-3.js or test-4.js will concatenate both files to dist/js/about_us.js.
Changing either test-5.js or test-6.js will concatenate both files to dist/js/store.js.
Your paths will need to be configured as necessary
Original answer before edit to question
how do i execute a task with grunt?
Using grunt.task.run is the correct way to programmatically run a task.
However, see this comment in the grunt-contrib-watch github repo. Excerpts from it read:
"Don't use grunt.event.on('watch') to run your tasks."
and...
"The watch event is not intended for running tasks."
The following solution assumes your requirement is to concatenate some .js files when one of them is changed/edited. In which case you need to utilize the tasks property of the watch task to define which task to run (I.e. 'concat:JS')
Gruntfile
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.initConfig({
watch: {
js: {
files: ['path/to/js/**/*.js'], // <-- 1. Glob pattern to .js files
tasks: ['concat:JS'] // <-- 2. Task to run when .js file changes.
}
},
concat: { // <-- 3. Configure your `concat` task as necessary.
JS: {
src: [
'path/to/js/foo.js',
'path/to/js/bar.js',
'path/to/js/quux.js'
],
dest: 'path/to/output/combined.js'
}
}
});
grunt.registerTask('default', ['watch']);
grunt.event.on('watch', function(action, filepath, target) {
console.log("yep, this code is being executed");
// 4. <-- Do not run task from 'on' listener event.
});
}
Notes
In your watch task define the glob pattern to the source .js files to watch for changes.
Specify the task to run when a .js file changes; I.e. 'concat:JS'
Configure your concat task as necessary.
Remove grunt.task.run('concat:JS'); from the grunt.event.on('watch', ...) listener.
Using the gist above and running grunt via your cli creates a new combined.js file when any change is made to a .js file stored in the path/to/js/ directory. (You'll need to configure the paths as per your requirements)
Whilst the grunt.event.on('watch', ...) listener should not be used to run a task, it can be utilized to configure other tasks.

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
})

Grunt uglify task goes blank/idle

I am very new to task-runners in JS and this is my first attempt with GruntJS. In my Gruntfile.js I have kept several tasks like jshint, cssmin etc. I am able to run them all from command line, but when I run `grunt uglify' there is no response, the cursor goes to another line and remains so for hours. No error message is shown as well.
Below is my relevant code, I have tried various uglify configuration as I saw different users providing a different set of properties
//uglify:{
// options: {
// mangle : {
// except : ['jQuery', 'angular']
// },
// compress: {
// drop_console : true
// },
// banner: '*****Minified file for Pricing Plan*****',
// footer: '*********File ends**********',
// preserveComments: false
// },
// my_target:{
// options: {
// beautify: true,
// files: {
// 'scripts/pp.min.js': ['scripts/pp.js']
// }
// }
// }
//},
uglify: {
options: {
banner: '*****Minified file for Pricing Plan*****'
},
dist: {
src: 'scripts/pp.js',
dest: 'scripts/pp.min.js'
}
},
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('uglify', ['uglify']);
On command line I am running grunt uglify
Can someone suggest whats wrong here and the way to rectify it.
Just if required: Its an Angular1.x.x project
EDIT: I tried installing uglifyJs and ran the uglifyjs command. It successfully uglifies and minifies my code. So is there any thing that needs to be done apart from what I have above.
but when I run `grunt uglify' there is no response, the cursor goes to another line and remains so for hours.
The Issue:
I think the issue is related to how you are registering your uglify task. This following line of code:
grunt.registerTask('uglify', ['uglify']); //<-- Issue is here.
How to fix it:
When creating an Alias Task using grunt.registerTask() avoid naming the taskName the same as any of the items/tasks defined in the taskList array.
grunt.registerTask('minify', ['uglify']); //<-- Correct.
NOTE: Although in the example code above I have changed the taskName to minify it can be any valid name you prefer. Just ensure whatever taskName you choose does not also exist as one of the items in the taskList array.
You now run the revised registered uglify task via CLI as follows:
$ grunt minify
Gruntfile.js
Here is the revised Gruntfile.js:
module.exports = function(grunt) {
grunt.initConfig({
uglify: {
options: {
banner: '/*****Minified file for Pricing Plan*****/'
// ... <-- Your other options
},
dist: {
src: 'scripts/pp.js',
dest: 'scripts/pp.min.js'
}
}
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('minify', ['uglify']);
};
(Note: Also added forward slashes to your banner string to produce a valid JavaScript comment in the resultant file)

Grunt read .json file to concat list of html files

I’m trying to get grunt to concat some .css files from 2 different directories into a single css file
I got it working ok and am trying to write a similar piece of code to concat html files in the order I specify in a simple external .json file rather than editing the Grunfile.js directly each time but it keeps throwing errors
The file locations and permissions are all ok, I can do it with php or concat the css files ok, I’m not great on js syntax os using functions. I only learned to use Grunt in the last few days so am pretty sure have not called the file with the correct code??
Any advice kindly appreciated!
This is my Gruntfile.js exactly
module.exports = function(grunt) {
// 1. All configuration goes here
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
concat: {
// 2. Configuration for concatinating files goes here
file.readJSON(‘html-files.json’);
},
watch: {
files: ['../../pages/**/*.html’],
tasks: ['concat'],
options : { nospawn : true }
}
});
// 3. Where we tell Grunt we plan to use this plug-in
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
// 4. Where we tell Grunt what to do when we type "grunt" into the terminal.
grunt.registerTask('default', ['concat']);
};
This is the html-files.json file contents exactly
html: {
src: [
'../../pages/**/this-block.html',
'../../pages/**/that-block.html',
],
dest: '../../somepage/content.html',
}

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.

How to watch and process (the same way) several files into several outputs with Grunt and/or Gulp?

I am currently using Grunt, and as I was trying Gulp, the same problem I encountered first with Grunt occurred to me.
I am trying to process some js files (concat, uglify and minify them), but I don't want all of them to compile into one big file, I want multiple output files, each from the processing of some input files :
scripts =
firstOutput:
outputFilename: 'first.min.js',
inputFiles: ['one.js', 'two.js']
secondOutput:
outputFilename: 'second.min.js',
inputFiles: ['three.js']
thirdOutput:
outputFilename: 'third.min.js',
inputFiles: ['four.js', 'five.js']
The only way I found (for now) to achieve that with Grunt is with multiple watches and multiple uglify tasks (or one uglify task and a listener on watch change to dynamically modify the uglify task src and dest) :
module.exports = (grunt) ->
grunt.loadNpmTasks 'grunt-contrib-watch'
grunt.loadNpmTasks 'grunt-contrib-uglify'
grunt.initConfig
watch:
firstOutput:
files: scripts.firstOutput.inputFiles
tasks: ['uglify:firstOutput']
options :
spawn : false
secondOutput:
files: scripts.secondOutput.inputFiles
tasks: ['uglify:secondOutput']
options :
spawn : false
thirdOutput:
files: scripts.thirdOutput.inputFiles
tasks: ['uglify:thirdOutput']
options :
spawn : false
uglify:
firstOutput:
files: scripts.firstOutput.inputFiles
dest: scripts.firstOutput.outputFilename
secondOutput:
files: scripts.secondOutput.inputFiles
dest: scripts.secondOutput.outputFilename
thirdOutput:
files: scripts.thirdOutput.inputFiles
dest: scripts.thirdOutput.outputFilename
grunt.registerTask 'default', 'watch'
And, as you can imagine, this is just an example, in my case of a big web application, there's a lot more than just three output js files, and I also process a few less files into some css files
My Gruntfile is really huge, and I find it has a lot of duplicate code, is there any way to have this code refactored to have one watch and one uglify task, with an automatically guessed src and dest with some kind of dependency (to know that if the four.js file is modified, it has to process the third output) ?
If you have some way to do it with Gulp I'll take it with great pleasure, as I would like to test it in my usual workflow.
Here's how you can do this with gulp + vanilla javascript:
var _ = require("underscore")
, gulp = require("gulp")
, uglify = require("gulp-uglify")
var scripts = [
{
output: 'first.min.js',
input: ['one.js', 'two.js']
}
, {
output: 'second.min.js',
input: ['three.js']
}
, {
output: 'third.min.js',
input: ['four.js', 'five.js']
}
];
function build(files, dest) {
return gulp.src(files)
.pipe(uglify())
.pipe(gulp.dest(dest));
}
gulp.task("watch", function () {
_.each(scripts, function (script, i) {
gulp.watch(script.input, function () {
build(script.input, script.output);
});
});
});
Even better if you can use globs to match sets of files so you don't have to write out the path for every single input set. Something like input: ["one/**/*.js, "other/**/*.js"]
"I am trying to process some js files (concat, uglify and minify
them), but I don't want all of them to compile into one big file"
Can I ask why? The benefit of one larger file is that you save on HTTP requests, every resource you load will cause some slowdown of your website. May I suggest using proper dependency management with RequireJS? That way the optimiser can walk your dependency graph and output optimised files for you.
http://requirejs.org/
There's a grunt task for this too:
https://github.com/gruntjs/grunt-contrib-requirejs

Categories