Build an app for deployment

Last updated on Jan 20th, 2014

By now, you should be comfortable with working with one task at a time. It's time to really make Grunt shine by integrating the variety of tasks involved in preparing a web application for deployment, such as precompiling, minifying, concatenating and moving files around.

What we're trying to accomplish

We want to take our main HTML file, identify all the stylesheets and scripts it references, optimize those (e.g. minification, concatenation) and then update the references from the HTML with the optimized version.

At the end of the task, we want to have a dist folder that contains the optimized version of our project, readily deployable to a production environment.

Tasks we'll use in this recipe

Now, don't get too intimidated! If you think about it each task has a very specific purpose in the workflow, and you'll see how easy is to choreograph them into one fluent, beautiful task.

Let's install everything

npm install grunt-contrib-uglify grunt-contrib-concat grunt-contrib-cssmin grunt-contrib-htmlmin grunt-contrib-copy grunt-usemin --save-dev

... in one fell swoop, even. Now let's add all of it to our Gruntfile:

grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-htmlmin');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-usemin');

Basic configuration

grunt.initConfig({

    // Minimize tasks
    cssmin: {
        all: {
            files: {
                'dist/css/app.css': 'app/css/**/*.css'
            }
        }
    },

    htmlmin: {
        all: {
            files: [{
                expand: true,
                cwd: 'app/',
                src: '**/*.html',
                dest: 'dist/'
            }]
        }
    },

    // Move other things around
    copy: {
        all: {
            files: [{
                expand: true,
                cwd: 'app/',
                dest: 'dist/',
                src: [
                    '.htaccess',
                    '*.ico',
                    'images/**/*.{jpg,png,gif}',
                    'css/fonts/*'
                ]        
            }]
        }
    }

});

Let's take the code above apart, piece by piece.

First, we configure the cssmin task to take all stylesheets from app/css and minimize and concatenate them in a single file in dist/css called app.css. We do the same for HTML, except we're using a dynamic declaration of the files object since we want to have a separate HTML file in the dist folder for each HTML in our app folder.

Then we configure the copy task to copy into the dist folder one by one any files not already covered by the other tasks — stuff like icons, images, fonts, etc.

We already notice we've been using app and dist a lot, so let me take this moment to introduce a handy way to Not Repeat Yourself: enter Grunt templates. They are tiny dynamic snippets delimited by <% and %> that you can add to your strings. We're interested in templates of the form <%= object.property.path %> which expands to the corresponding value from Grunt's configuration object. Let's see it in action:

grunt.initConfig({

    appConfig: {
        appRoot: 'app',
        distRoot: 'dist'
    },

    // Minimize tasks
    cssmin: {
        all: {
            files: {
                '<%= appConfig.distRoot %>/css/app.css': '<%= appConfig.appRoot %>/css/**/*.css'
            }
        }
    },

    htmlmin: {
        all: {
            files: [{
                expand: true,
                cwd: '<%= appConfig.appRoot %>/',
                src: '**/*.html',
                dest: '<%= appConfig.distRoot %>'
            }]
        }
    },

    // Move other things around
    copy: {
        all: {
            files: [{
                expand: true,
                cwd: '<%= appConfig.appRoot %>/',
                dest: '<%= appConfig.distRoot %>',
                src: [
                    '.htaccess',
                    '*.ico',
                    'images/**/*.{jpg,png,gif}',
                    'css/fonts/*'
                ]        
            }]
        }
    }

});

We've defined an appConfig property to hold our commonly-used paths. Now we can refer to the two paths as <%= appConfig.appRoot %> and <%= appConfig.distRoot %> in all our tasks.

Note: You can define any number of custom properties in your Grunt configuration (like we did above with appConfig), but make sure the names don't conflict with any task names and stay away from the grunt keyword, which is reserved to access the Grunt object in the templates.

Another cool thing we can do is to get a timestamp for the current Grunt run and add that to the generated CSS/JS files to ensure they are unique.

{
    appConfig: {
        appRoot: 'app',
        distRoot: 'dist',
        timestamp: Date.now()
    }
}

And here's how we might use this to append the timestamp to our generated CSS file in cssmin:

cssmin: {
    all: {
        files: {
            '<%= appConfig.distRoot %>/css/app.<%= appConfig.timestamp %>.css': '<%= appConfig.appRoot %>/css/**/*.css'
        }
    }
}

Take five