Grunt: Targeted environment builds for Node.js + JSHint + Closure Compiler

I’ve been using Node.js and was looking for a way to target my Node.js app for different environments (e.g., local, NONPROD, PROD). This would be especially useful when configuring different URLs and appnames and even setting up NewRelic, which I use for monitoring my application in different environments. Up to now, I’ve been doing these changes manually but discovered this tool Grunt that fit the bill. I come from a Maven build background for Java so we were looking for a tool that was similar for JavaScript. Not only can I do filtering but I can add plugins for things like JSHint, JS compression, unit testing, and a bunch of other nifty plugins.

To use Grunt for your Node app follow the getting started guide. After you install and configure it, you can refer my Gruntfile.js below which I used to set-up different environment variables, run JSHint and use Google’s Closure Compiler to minify it all. My basic approach is to create a “dist” directory where I copy the pertinent files to it. In my base directory, I have used $ variables assigned to the things I wanted to change in the files I wanted updated. I then used grunt-string-replace to update those based on the environment I was targeting. Let’s take a look.

On line 6, I clean the “dist” directory from any previous builds. On lines 7-13, I copy the files I want from the root directory to the “dist” directory (NOTE: user “!” to exclude files). Starting on line 14, I use string replace to update variables depending if I’m running dev or prod builds. Line 114, I run JSHint to make sure my code is in order. Line 127, I run node-unit for my unit tests and on line 130, I run Google’s closure-compiler to minify my JavaScript and use Advanced Optimizations for peak performance.

On lines 166-67, you’ll see how I call out different tasks depending on the target environment I’m after, in this case dev or prod. On the CLI, you can kick off the default which targets the DEV environment just by running “grunt” and for prod, just add that modifier, “grunt prod”. That’s all there’s to it!

(function () {
    'use strict';
    module.exports = function (grunt) {
        grunt.initConfig({
            pkg: grunt.file.readJSON('package.json'),
            clean: ["dist"],
            copy: {
                build: {
                    files: [
                        {src: ['./**/*.js', './*.json', './stackato.yml', './README.md', '!./nunit.js', './test/**/*', '!./dist/**/*', '!./node_modules/**/*', '!./Gruntfile.js'], dest: 'dist/'}
                    ]
                }
            },
            'string-replace': {
                dev: {
                    files: {
                        "dist/": ["newrelic.js", "stackato.yml", "package.json"]
                    },
                    options: {
                        replacements: [
                            {
                                pattern: '$APPNAME',
                                replacement: "services-people"
                            },
                            {
                                pattern: '$VERSION',
                                replacement: "1.0.6"
                            },
                            {
                                pattern: 'server.js',
                                replacement: "server.min.js"
                            },
                            {
                                pattern: '$ENV',
                                replacement: "DEV"
                            },
                            {
                                pattern: '$PDS_PWD',
                                replacement: "xxx!"
                            },
                            {
                                pattern: '$INSTANCES',
                                replacement: "1"
                            },
                            {
                                pattern: '$NEWRELIC_TRACE_LVL',
                                replacement: "trace"
                            },
                            {
                                pattern: '$URL1',
                                replacement: "xxx1-dev.com"
                            },
                            {
                                pattern: '$URL2',
                                replacement: "xxx2-dev.com"
                            },
                            {
                                pattern: '$URL3',
                                replacement: "xxx3-dev.com"
                            }
                        ]
                    }
                },
                prod: {
                    files: {
                        "dist/": ["newrelic.js", "stackato.yml", "package.json"]
                    },
                    options: {
                        replacements: [
                            {
                                pattern: '$APPNAME',
                                replacement: "services-people"
                            },
                            {
                                pattern: '$VERSION',
                                replacement: "1.0.6"
                            },
                            {
                                pattern: 'server.js',
                                replacement: "server.min.js"
                            },
                            {
                                pattern: '$ENV',
                                replacement: "PROD"
                            },
                            {
                                pattern: '$PDS_PWD',
                                replacement: "xxx!"
                            },
                            {
                                pattern: '$INSTANCES',
                                replacement: "2"
                            },
                            {
                                pattern: '$NEWRELIC_TRACE_LVL',
                                replacement: "info"
                            },
                            {
                                pattern: '$URL1',
                                replacement: "xxx1.com"
                            },
                            {
                                pattern: '$URL2',
                                replacement: "xxx2.com"
                            },
                            {
                                pattern: '$URL3',
                                replacement: "xxx3.com"
                            }
                        ]
                    }
                }
            },
            jshint: {
                options: {
                    curly: true,
                    eqeqeq: true,
                    eqnull: true,
                    strict: true,
                    globals: {
                        jQuery: true
                    },
                    ignores: ['dist/test/**/*.js']
                },
                files: ['Gruntfile.js', 'dist/**/*.js']
            },
            nodeunit: {
              all: ['dist/test/*-tests.js']
            },
            'closure-compiler': {
                build: {
                    closurePath: '.',
                    js: 'dist/**/*.js',
                    jsOutputFile: 'dist/server.min.js',
                    maxBuffer: 500,
                    options: {
                        compilation_level: 'ADVANCED_OPTIMIZATIONS',
                        language_in: 'ECMASCRIPT5_STRICT',
                        debug: false
//                        formatting: 'PRETTY_PRINT'
                    }
                }
            },
            // Uglify is somewhat the defacto standard for minifying Node.js but Closure compiler yields better perf (ops/sec)
            // http://jsperf.com/testing-code-performance-by-compression-type/3
            uglify: {
                options: {
                    banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
                },
                build: {
                    src: 'dist/**/*.js',
                    dest: 'dist/server.min.js'
                }
            }
        });

        grunt.loadNpmTasks('grunt-contrib-uglify');
        grunt.loadNpmTasks('grunt-closure-compiler');
        grunt.loadNpmTasks('grunt-contrib-copy');
        grunt.loadNpmTasks('grunt-contrib-clean');
        grunt.loadNpmTasks('grunt-contrib-jshint');
        grunt.loadNpmTasks('grunt-contrib-nodeunit');
        grunt.loadNpmTasks('grunt-string-replace');

        // Default task(s).
        grunt.registerTask('default', ['clean', 'copy:build', 'string-replace:dev', 'jshint', 'closure-compiler:build']);
        grunt.registerTask('prod', ['clean', 'copy:build', 'string-replace:prod', 'closure-compiler:build']);
    };
})();

Advertisements