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.
Related
copy: {
build: {
cwd: 'app',
src: ['**', '!**/vendors/**', '!**src/js/*.js',],
dest: 'dist',
expand: true
}
}
I am using grunt build scripts to build a distribution folder for the completed product. However, its not 100% automatic and dynamic. For example, I have a folder of xml content files. Yet, I don't use them all. Right now, the whole folder is copied over to the build version. Manually I have to go in and delete the xml files I don't want in the build version then run it. Or I could go into the grunt file and and tell it to ignore those files.
The problem is that I don't want to do that every time. A theoretical idea I had would be to have an xml file where I define elements to represent certain other files.
<bootstrap>true</bootstrap>
<extraContent>false</extraContent>
This would say that the file correlated to bootstrap and extraContent should or shouldn't be ignored in the build. I am trying to figure out if you could do this in grunt.
something like the following is how I see the logic playing out...
var bootstrap = $(xml).find("bootstrap").text()
if(bootstrap == "false"){
var url = src/bootstrap.css
//Here add the correlated filepath defined above to be ignored
}
The problem is not only writing this so grunt knows what it is, but also combining that logic with the actual "copy:{}" script I showed above
If you want to include/exclude files based on their contents you can use filter function for this. Examples can be found in the official documentation: https://gruntjs.com/configuring-tasks#custom-filter-function.
The filter property can help you target files with a greater level of detail.
In your case this could be something like this:
copy: {
build: {
cwd: 'app',
src: ['**', '!**/vendors/**', '!**src/js/*.js',],
dest: 'dist',
expand: true,
// this filter function will copy xml files only when `bootstrap` is set to 'true'
filter: filepath => {
if (require('path').extname(filepath) !== 'xml')
return true;
const xml = require('fs').readFileSync(filepath, 'utf8');
const json = require('xml2json').toJson(xml);
return json.bootstrap === 'true';
}
}
}
You can then use the process function to copy only certain contents from specific files: https://github.com/gruntjs/grunt-contrib-copy#process
This option is passed to grunt.file.copy as an advanced way to control the file contents that are copied.
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.
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
})
As a part of my Gruntfile.js:
...
concat: {
...
js: {
src: [
'src/**/*.js'
],
dest: 'build/js/main.js',
nonull: true
}
},
...
How can I prevent concat from generating a blank main.js if my src directory contains no scripts? Must I really create a separate task if I know I won't need to build any scripts?
Not exactly a proper solution, but I managed to find a package called grunt-cleanempty which I set to run after concat to delete any generated empty files.
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