Let’s modify the configuration section of Gruntfile.js so it specifies folders as well as files:
files/recursive/deploying/Gruntfile.js | |
| grunt.config.init({ |
| copyFiles: { |
| options: { |
| workingDirectory: 'working', |
| manifest: [ |
| 'index.html', 'stylesheets/', 'javascripts/' |
| ] |
| } |
| } |
| }); |
If we tried to run our tasks right now we’d get errors because Grunt’s built-in copy doesn’t support directories. But we can iterate over the files and folders in our list and then detect if the entry is a file or a folder. If it’s a file we can copy it like before, but if it’s a folder we’ll just have to iterate over the files and folders inside that folder. If you’ve done anything like this in other languages, you’ll know that the solution is to use recursion. And Grunt provides a built-in function for that.
Currently, our copyFiles task loops over the files like this:
files/simple/deploying/Gruntfile.js | |
| files.forEach(function(file) { |
| var destination = workingDirectory + '/' + file; |
| grunt.log.writeln('Copying ' + file + ' to ' + destination); |
| grunt.file.copy(file, destination); |
| }); |
But let’s change this task so it instead calls a function we’ll create, called recursiveCopy:
files/recursive/deploying/Gruntfile.js | |
| files.forEach(function(item) { |
| recursiveCopy(item, workingDirectory); |
| }); |
The recursiveCopy function takes in the source, which is either a file or folder, and the destination folder. This function then checks to see if the source is a file or a folder. If it’s a file, we’ll copy it. But if it’s a folder, we’ll have to dig into the files in the folder and call the recursiveCopy function again. And if there are folders within folders, we’ll have to handle those the same way. But thanks to the power of recursion, we can declare the function like this:
files/recursive/deploying/Gruntfile.js | |
| var recursiveCopy = function(source, destination){ |
| if(grunt.file.isDir(source)){ |
| |
| grunt.file.recurse(source, function(file){ |
| recursiveCopy(file, destination); |
| }); |
| |
| }else{ |
| grunt.log.writeln('Copying ' + source + ' to ' + destination); |
| grunt.file.copy(source, destination + '/' + source); |
| } |
| } |
We use grunt.file.isDir to detect whether the source element is a file or a directory. If it’s a file, we print the file to the screen and do the file copy like before.
When the file is a directory, we use grunt.file.recurse, which runs the callback function of our choice against each file or folder in the structure. When grunt.file.recurse executes the callback, it sends the source file’s absolute path as the first parameter. It can send the root directory, the current file’s directory, and the current file’s name as arguments if the callback function accepts them. But in our case we’ll keep things really simple; we just pass the source to our recursiveCopy function inside the callback.
A quick run of our copyFiles task shows it’s working:
| $ grunt copyFiles |
| |
| Running "copyFiles" task |
| Copying index.html to working |
| Copying stylesheets/layout.css to working |
| Copying stylesheets/style.css to working |
| Copying javascripts/app.js to working |
| |
| Done, without errors. |
By combining a little JavaScript code with Grunt’s built-in utilities, we can now clone a directory structure with ease.
3.15.226.147