Using grunt to concat many files from many dirs into single renamed file in new directory

coblr picture coblr · Feb 20, 2014 · Viewed 15.2k times · Source

I have an Angular project with potentially many modules. Each module has it's own directory with subdirectories for controllers, directives, services, etc. Something like this:

src
|-- js
    |-- modules
        |-- moduleOne
            | module.js
            |-- controllers
                | listController.js
                | detailController.js
            |-- directives
                | listItem.js
                | summaryWidget.js
            |-- filters
            |-- services
                | moduleService.js

My build essentially bundles and compiles files from src/ and puts into dev/, then minifies the files in dev/ and moves into prod/. During dev, the server points to the dev/ folder and in production, the server points to the prod/ folder (also why the files are ending in .min.js even though they are only compiled/concated). This process is working well.

Currently, my concat task is grabbing all the files in moduleOne/ and creating a single moduleOne.js file in my dev directory. This is what I want to happen, but more dynamically:

concat: {
    modules: {
        files: {
            "dev/js/modules/moduleOne.min.js": [
                "src/js/modules/moduleOne/*.js",
                "src/js/modules/moduleOne/**/*.js"
            ],
            "dev/js/modules/moduleTwo.min.js": [
                "src/js/modules/moduleTwo/*.js",
                "src/js/modules/moduleTwo/**/*.js"
            ]
        } 
    }
}

The problem is that I have to do this for every module, but don't think I would need to.

I tried doing the following because it's sort of what I want to do:

concat: {
    modules: {
        files: [{
            expand: true,
            cwd: "src/js/modules",
            src: "**/*.js",
            dest: "dev/js/modules",
            ext: ".min.js"
        }]
    }
}

But the result was all my files and directory structure moved over from src/ to dev/. I basically used concat to do a copy, not helpful.

I'd like to do something like this:

concat: {
    modules: {
        files: [{
            expand: true,
            cwd: "src/js/modules",
            src: "**/*.js",
            dest: "dev/js/modules/<foldername>.min.js",  <- how do I achieve this?
        }]
    }
}

I've been reading a lot, but it seems that I only get close to finding the answer and am having trouble putting the concepts together. A lot of what I find is just single files into a new directory, with a rename. I'd like multiple files to single file into new directory with a rename. Cuz that's how I roll :)

Answer

coblr picture coblr · Feb 20, 2014

So, I found the answer I was looking for.

This SO post was basically the same question with a good answer. Unfortunately it didn't come up when I was creating my question or else you wouldn't be reading this.

There was a slight tweak to my needs. I needed to do it dynamically per module instead of just one compile.js file so my final code is as follows, placed just after my initConfig():

grunt.registerTask("prepareModules", "Finds and prepares modules for concatenation.", function() {

    // get all module directories
    grunt.file.expand("src/js/modules/*").forEach(function (dir) {

        // get the module name from the directory name
        var dirName = dir.substr(dir.lastIndexOf('/')+1);

        // get the current concat object from initConfig
        var concat = grunt.config.get('concat') || {};

        // create a subtask for each module, find all src files
        // and combine into a single js file per module
        concat[dirName] = {
            src: [dir + '/**/*.js'],
            dest: 'dev/js/modules/' + dirName + '.min.js'
        };

        // add module subtasks to the concat task in initConfig
        grunt.config.set('concat', concat);
    });
});

// the default task
grunt.registerTask("default", ["sass", "ngtemplates", "prepareModules", "concat", "uglify", "cssmin"]);

This essentially makes my concat task look like it did when I was hand coding it, but just a little simpler (and scalable!).

concat: {
    ...
    moduleOne: {
        src: "src/js/modules/moduleOne/**/*.js",            
        dest: "dev/js/modules/moduleOne.min.js"
    },
    moduleTwo:{
        src: "src/js/modules/moduleTwo/**/*.js",
        dest: "dev/js/modules/moduleTwo.min.js"
    }
}

Another deviation I made from the SO post was that I chose not to have prepareModules run concat on it's own when it was done. My default task (which watch is setup to run during dev) still does all my processing.

This leaves me with the following structure, ready for minification into prod/:

| dev
    | js
        | modules
            |-- moduleOne.min.js
            |-- moduleTwo.min.js