How to Use Grunt with Keystone.js for Minifying CSS, JavaScript, and HTML

2016/02/014 min read
bookmark this
Responsive image

Table of Contents

  1. Introduction
  2. Grunt File Setup
  3. Place JavaScript/CSS Files in Jade Template
  4. Keystone.js Init
  5. Conclusion

Introduction

I'm using Keystone.js with the Jade view template engine, and the back-end is MongoDB. I hadn't tried Grunt with Keystone.js before. This blog covers how I tried it — it's not perfect, but the output is close to what I want. From the browser source code point of view, here is what I wanted to achieve:

  • Minify CSS/JavaScript
  • Minify HTML
  • In development mode, I should have the choice to not minify CSS/JavaScript and not minify HTML.

The following are the steps I took to make it happen.

Grunt File Setup

Use grunt-jade-usemin

The first thing I'm doing here is using jadeUsemin to set up a task for the Jade template. Then, there is some cleanup so I can make my web application work, which is the copy task.

/*
 * grunt-jade-usemin
 *
 *
 * Copyright ©2014 Gilad Peleg
 * Licensed under the MIT license.
 */
'use strict';

module.exports = function (grunt) {
    // load all npm grunt tasks
    require('load-grunt-tasks')(grunt);
    // Project configuration.
    grunt.initConfig({
        // Before generating any new files, remove any previously-created files.
        clean: {
            tests: ['tmp', 'test/compiled']
        },
        filerev: {
            jadeUsemin: {
                options: {
                    noDest: true
                }
            }
        },
        // Configuration to be run (and then tested).
        jadeUsemin: {
            options: {
                replacePath: {
                    '#{baseDir}': 'test', //optional - key value to replace in src path
                    '#{baseDistPath}': '/'
                }
            },
            basic: {
                options: {
                    tasks: {
                        js: ['concat', 'uglify'],
                        css: ['concat', 'cssmin']
                    }
                },
                files: [
                    {
                        dest: 'templates/compiled/layouts/default.jade',
                        src: 'templates/layouts/default.jade'
                    }]
            }
        },
        copy: {
            // copy UI assets for development
            development: {
                cwd: 'public',
                src: '**/*',
                dest: 'public/public',
                expand: true
            },
            production: {
                cwd: '/dist',
                src: '**/*',
                dest: 'public/dist',
                expand: true
            },
            production_fonts: {
                cwd: 'public/fonts',
                src: '**/*',
                dest: 'public/dist/fonts',
                expand: true
            },
            production_fontsAwesome: {
                cwd: 'bower_components/font-awesome-bower/fonts',
                src: '**/*',
                dest: 'public/dist/fonts',
                expand: true
            }
        }
    });
    // Actually load this plugin's task(s).
    grunt.loadTasks('tasks');
    // Whenever the "test" task is run, first clean the "tmp" dir, then run this
    // plugin's task(s), then test the result.
    grunt.registerTask('[production]', [
        'jadeUsemin:basic',
        'copy:development',
        'copy:production',
        'copy:production_fonts',
        'copy:production_fontsAwesome'
    ]);
};

Place JavaScript/CSS Files in Jade Template

The next thing to do is add HTML comments as follows for your JavaScript or CSS. The file path after build:js will be the actual file path when running the Grunt task.

There's a trick here: when I wrote the path starting with a slash /, the Grunt task created post.js in my local drive's root folder. If I didn't add the slash, it would create the file under my current application's root folder. That's why I'm using a workaround in my copy task command. I think I need to figure out a better way to do it.

block js
	script(src='https://apis.google.com/js/platform.js', async='', defer='')

	//-<!-- build:js /dist/js/post.js -->
	script(src='./public/js/shared/socials/facebook_sdk.js', type='text/javascript')
	script(src='./public/js/shared/socials/share_core.js', type='text/javascript')
	script(src='./public/js/shared/socials/share_facebook2.js', type='text/javascript')
	script(src='./public/js/shared/socials/share_googlePlus.js', type='text/javascript')
	//-<!-- endbuild -->

Keystone.js Init

I copy all the Jade templates to another folder in the Grunt task. Because of this, I need the ability in Keystone.js to use different view templates in development and production.

The following is what I did in Keystone.js:

var viewPath = 'templates/compiled/views';
if (process.env.NODE_ENV === 'development') {
	viewPath = 'templates/views';
}
keystone.init({
	'views':  viewPath,
....
});

Conclusion

I'm happy that in Keystone.js I was able to achieve the following by using a Grunt task. In production, you'll get the most optimized environment:

  • Minify CSS
  • Minify JavaScript
  • Minify HTML
  • Ability to switch minification on/off in development

There are a few things I don't think I'm doing right, so I need to figure them out in the future:

  • How to set up paths in the Jade template correctly