Randomness
in Code

Bundling Production Assets for MEAN.JS

November 19, 2014

Lately, I've been writing a lot of Angular on the client side and Node on the server side. More specifically, I've been using the MEAN.JS template in projects I create, which helps layout a structure to organize the code. Yes, there's a lot of bloat that comes with some of these template projects (and especially with the generators), but once it's all cleaned up and tailored to your project, I think it works very well.

One of the best features of this template is the way client side code is organized and prepared for the various deploy environments. The stack comes pre-configured with a default environment (all.js) which contains, among other things, a list of assets that should be included when loading your client side application. These assets are automatically added to your initial rendered view, which starts your Angular app and runs your client side code. There are also environment overrides for development, production, etc. For example, the default all.js contains both vendor assets and your application assets under the assets object (tests were removed from this example for brevity):

assets: {
    lib: {
        css: [
            "public/lib/bootstrap/dist/css/bootstrap.css",
            "public/lib/bootstrap/dist/css/bootstrap-theme.css"
        ],
        js: [
            "public/lib/angular/angular.js",
            "public/lib/angular-resource/angular-resource.js",
            "public/lib/angular-ui-router/release/angular-ui-router.js"
        ]
    },
    css: [
        "public/modules/**/css/*.css"
    ],
    js: [
        "public/config.js",
        "public/application.js",
        "public/modules/*/*.js",
        "public/modules/**/*.js"
    ]
}

A problem I ran into in using this was creating minified assets and bundling those together for the production environment. The MEAN.JS stack has a solution for your application assets, but for vendor assets, suggests including the same set of resources in production as in development. However, in production include the minified version of those vendor libraries instead of the full source. This means we're still making the same number of requests for vendor libraries, just for minified versions. Here's the assets from the production.js file:

assets: {
    lib: {
        css: [
            "public/lib/bootstrap/dist/css/bootstrap.min.css",
            "public/lib/bootstrap/dist/css/bootstrap-theme.min.css"
        ],
        js: [
            "public/lib/angular/angular.min.js",
            "public/lib/angular-resource/angular-resource.min.js",
            "public/lib/angular-ui-router/release/angular-ui-router.min.js"
        ]
    },
    css: "public/dist/application.min.css",
    js: "public/dist/application.min.js"
}

CDN's are a possible solution to reduce bandwidth, but in the end this creates a more scattered set of assets to manage. What I would rather do is have a single file for vendor assets in the same way I have a single file for my source code. Additionally, Angular's very useful template cache can bundle all of our HTML templates into a single JavaScript file, and that can be minified and served up as well (which would mean we wouldn't have to make HTML requests from the client for each template after application load). What I would like is for my assets in the production.js file to look something like this:

assets: {
    lib: {
        css: "public/dist/vendor.min.css",
        js: "public/dist/vendor.min.js"
    },
    css: "public/dist/application.min.css",
    js: [
        "public/dist/application.min.js",
        "public/dist/templates.min.js"
    ]
}

In the example above, you can see that all the vendor assets are now reduced to a single file (one for JavaScript, one for CSS), and the HTML templates are included in the templates.min.js file. (We could reduce this even further by concatenating these files together, but in this post we'll keep these set of files to make it easier to understand.)

The build Environment

We can solve this problem by introducing the build environment, and the associated build.js file. The intention here is to have a set of assets that are used when we run our build, and generate our production minified files. These assets are then almost a mirror of what's contained in the all.js file, but instead contain minified 3rd party libraries where possible. Doing this in conjunction with some additional build steps will provide us with the production assets we've described in the example above.

So continuing with the same example set of assets, this would be the build.js file:

// Include the same assets from all.js, but for the vendor libraries (3rd party),
// include the minimized versions. These will not be minified during the build
// but instead concat together to form 1 vendor js file.
assets: {
    lib: {
        css: [
            "public/lib/bootstrap/dist/css/bootstrap.min.css",
            "public/lib/bootstrap/dist/css/bootstrap-theme.min.css"
        ],
        js: [
            "public/lib/angular/angular.min.js",
            "public/lib/angular-resource/angular-resource.min.js",
            "public/lib/angular-ui-router/release/angular-ui-router.min.js"
        ]
    },
    css: [
        "public/modules/**/css/*.css"
    ],
    js: [
        "public/config.js",
        "public/application.js",
        "public/modules/*/*.js",
        "public/modules/**/*.js"
    ]
}

This file is basically a mix between the all.js file and the production.js file. The 3rd party vendor assets included are all minified versions (like the production.js file), while the application assets are all those contained within the project (like the all.js file).

Running the Build with Grunt

With the build file in place, we can turn our attention to the Gruntfile.js and add some additional tasks to generate our production assets. We'll do this by taking advantage of the grunt-angular-templates, grunt-contrib-uglify, grunt-contrib-cssmin, and grunt-contrib-concat tasks.

Before any of those tasks are executed, we need to setup a few things. First, we create another env task (in addition to the test environment) to setup the build environment:

env: {
    test: {
        NODE_ENV: "test"
    },
    build: {
        NODE_ENV: "build"
    }
}

Within the loadConfig task we store off the assets we're going to work with (from the build.js file). Below is the updated loadConfig:

grunt.task.registerTask("loadConfig", "Task that loads the config into a grunt option.", function() {
    var init = require("./config/init")();
    var config = require("./config/config");

    grunt.config.set("vendorJavaScriptFiles", config.assets.lib.js);
    grunt.config.set("vendorCSSFiles", config.assets.lib.css);
    grunt.config.set("applicationJavaScriptFiles", config.assets.js);
    grunt.config.set("applicationCSSFiles", config.assets.css);
});

Notice that we're storing the assets for both the vendor code as well as application code within these four variables. With that setup, we can run the ngTemplates task to package up all the HTML templates we have on the client side into a single templates.js file. The configuration for this task includes updating the URLs to use our /assets path, and assumes the Angular application module name is my-app for our application:

ngtemplates: {
    options: {
        htmlmin: {
            collapseWhitespace: true,
            removeComments: true
        },
        url: function(url) {
            return url.replace("public", "assets");
        },
        prefix: "/"
    },
    "my-app": {
        src: "public/modules/**/**.html",
        dest: "public/dist/templates.js"
    }
}

Finally, we can uglify all our JavaScript, minify the CSS, and concat all those files together to form the assets needed by the production environment. The following shows the configuration for these three tasks:

uglify: {
    production: {
        options: {
            mangle: true,
            compress: false,
            sourceMap: true
        },
        files: {
            "public/dist/application.min.js": "<%= applicationJavaScriptFiles %>",
            "public/dist/templates.min.js": "public/dist/templates.js"
        }
    }
},
cssmin: {
    combine: {
        files: {
            "public/dist/application.min.css": "<%= applicationCSSFiles %>",
            "public/dist/vendor.min.css": "<%= vendorCSSFiles %>"
        }
    }
},
concat: {
    production: {
        options: {
            stripBanners: true
        },
        files: {
            "public/dist/vendor.min.js": "<%= vendorJavaScriptFiles %>"
        }
    }
}

Notice that the uglify task will perform the operation on all application JavaScript files and separately on the templates.js to create the minfied versions. The cssmin task minifies the application and vendor CSS files separately, and the concat task lumps all the vendor's (pre-)minified assets together into one file (this avoids having to minify what should already be available in a minified version, from the vendor).

With that all configured, we can wrap it all in a single Grunt build task:

grunt.registerTask("build", ["env:build", "loadConfig", "ngtemplates", "uglify", "cssmin", "concat"]);

For a working example of this, please see my meanjs-template project, which takes advantage of this new build environment to create the production assets.