Yii 1.1: Using Grunt for deploying client scripts

9 followers

For a long time I have struggled to find a suitable tool for doing server tasks such as compiling LESS files, concatenating and minifying of JavaScript files and copying the minified JavaScript files. While most of these tasks can be done with Yii extensions I would still not recommend using them because it requires you to include these extensions in your project, and therefore also deploying them in your production environment.

After a long search I believe that I have found the perfect tool for this. Let me introduce you to Grunt, the JavaScript task runner for Node.js. In this article I will explain how you can set up Grunt to perform these tasks.

Getting started

To install Grunt you first need to install Node.js 0.8.0 or newer, it can be downloaded from the project website at http://nodejs.org/download. When Node is installed you can use npm (the package manager for Node.js) to install Grunt by running the following command:

npm install –g grunt-cli

Note if you have already installed grunt globally you need to uninstall it first.

When you have installed Grunt successfully you need to create two files in the root directory of your project, package.json and Gruntfile.js. Next we will go over these two files in detail.

package.json

The packages.json file is where you specify your Node.js dependencies, in this case your Grunt plugins. Here is a starting point for your package.json file:

{
    "name": "project-name",
    "version": "0.1.0",
    "devDependencies": {
        "grunt": "~0.4.1"
    }
}

Now you can install Grunt locally using npm by running the following command:

npm install

Gruntfile.js

The Gruntfile.js is where you configure your Grunt tasks. This is what an "empty" Gruntfile looks like:

module.exports = function(grunt) {
 
    // Project and task configuration
    grunt.initConfig({
 
    });
 
    // Load plugins
    // Nothing here yet...
 
    // Define tasks
    grunt.registerTask('default', []);
 
};

Compilation and watching of LESS files

If you do not know what LESS is, it is a dynamic stylesheet language that is compiled into CSS. You can read more about it on http://www.lesscss.org. When you are working with LESS you naturally need to set up compilation of your files which usually is a bit troublesome. However, setting up LESS compilation with Grunt is a breeze and you will have it up and running in a few minutes.

First you need to install the grunt-contrib-less plugin by running the following command:

npm install grunt-contrib-less --save-dev

This will download the plugin and add it to your packages.json file's development dependencies. Next you need to enable it by adding it to your Gruntfile:

// Load plugins
grunt.loadNpmTasks('grunt-contrib-less');

When you have enabled the plugin we can configure it to compile your LESS. In this example I will assume that your less files are in under protected/less.

// Project and task configuration
grunt.initConfig({
    less: {
        development: {
            options: {
                compress: true,
                optimization: 2
            },
            files: {
                "css/main.css": "protected/less/main.less"
            }
        }
    }
});

Next we will setup watching of your LESS files. For this we will need the grunt-contrib-watch plugin by running the following command:

npm install grunt-contrib-watch --save-dev

Enable the plugin by adding it to your Gruntfile:

// Load plugins
.....
grunt.loadNpmTasks('grunt-contrib-watch');

Configure the watch plugin to watch your less files:

// Project and task configuration
grunt.initConfig({
    .....
    watch: {
        styles: {
            files: ['protected/less/**/*.less'],
            tasks: ['less'],
            options: {
                nospawn: true
            }
        }
    }
});

Grunt will not run any of your plugins unless you add them to a task, let us add watch to the default task:

// Define tasks
grunt.registerTask('default', ['watch']);

Now when you run Grunt, it will watch your LESS files and compile them into CSS if when any LESS file is modified.

Concatenation and watching of JavaScript files

It is considered good practice to concatenate all of your JavaScript files into a single file before minifying them. This can easily be done with the grunt-contrib-concat plugin by running the following command:

npm install grunt-contrib-concat --save-dev

Enable the plugin by adding it to your Gruntfile:

// Load plugins
.....
grunt.loadNpmTasks('grunt-contrib-concat');

When you have enabled the plugin we can configure it to concatenate your JavaScript. In this example I will assume that your JavaScript files are under protected/js.

// Project and task configuration
grunt.initConfig({
    .....
    concat: {
        options: {
            separator: "\n\n"
        },
        dist: {
            src: ['protected/js/app.js'],
            dest: 'js/main.js'
        }
    },
});

Next we will setup watching of your JavaScript files. This can also be done using the watch plugin. Lets add an additional section to the watch plugin:

// Project and task configuration
grunt.initConfig({
    .....
    watch: {
        .....
        scripts: {
            files: ['protected/js/**/*.js'],
            tasks: ['concat'],
            options: {
                nospawn: true
            }
        }
    }
});

Now Grunt will also watch your JavaScript files and concatenate them when any JavaScript file is modified.

Resources

Total 8 comments

#12453 report it
jacmoe at 2013/03/21 03:00pm
You are in good company Chris

There are lots of people who use (or at least you can get that impression) Git as a means to distribute/sync development to production.

Heroku calls it 'agile deployment' : http://www.heroku.com/ :)

Haven't looked into how they deal with generated content, ...

And with that, I think, it would probably be a good idea not to hi-jack this topic much further ;P

#12447 report it
Chris83 at 2013/03/21 06:35am
@jacmoe

Alright, I can see your point. I will do some research in rsync and get back to you.

#12423 report it
jacmoe at 2013/03/19 07:38pm
About using VCS for web publishing

I think it's a flawed idea to use a VCS to aid in publishing your site.

Rsync is really great, and I recommend using it: http://wiki.dreamhost.com/Rsync#Using_rsync_to_publish_your_files

I tried the VCS approach a couple of times and it's really error prone. And generated files under source control? No, and no.

It just clutters the changelog with unnecessary changes.

When I develop in C++, I do not add generated files to my VCS either. (lib and generated header files, etc.) That is not what a VCS should do. IMO.

I wonder if there's a RSync plugin for Grunt? Probably..

#12418 report it
andyhot at 2013/03/19 08:23am
Re: extensions

@jacmoe, @Chris83 As a disclaimer, i too use grunt a lot and it's a great tool for automating offline asset management. My argument (which perhaps requires a complete post) is really that i see more pain in offline vs online asset management.

So, to reiterate, if i go the offline route it means that

  • i either need to check the generated files into a VCS (which i'm fundamentally opposed to)

  • or have users that clone or pull my project install and run some command line in order to use/work on it (i also believe in using the min-files while developing to identify minification problems as soon as they appear)

And that doesn't cover how to introduce a cache buster into the process - do you manually append a number to the compressed file's url?

On the other hand, if i go the online route it means i use an extension that only runs when one of my source js, sass, less, coffee files changes. It generates a file that is then served directly and it is responsible for updating the requested url (so the client never gets stale data). And best of all, i don't have to remember to do anything!

Now, if only it could have support for source maps...

#12412 report it
Chris83 at 2013/03/19 02:53am
@andyhot

The reasons are very much the same as those jacmoe mentioned.

However, I disagree on adding min-files to VCS. I actually think that that's the only way to go about it mainly because I would never install Node on a production server unless I'd absolutely had to (which I don't).

Instead I'd compile the files in my development environment and check them into VCS. This way you don't need to compile anything in your production environment and you can always use either your normal files while developing and min-files in production.

I don't really see to point of compiling anything in the production environment and I consider it bad practice overall.

#12411 report it
jacmoe at 2013/03/19 01:49am
Re: extensions

@Andy: IMO, it's really overkill to add Yii extensions to your project when all they do is generate files used in production.

I very much prefer - I am using Sass/Compass - to put up the minified css to the production server - rather than adding a Yii extension to compile my scss scripts.

I mean: it's only one command: 'compass watch' ...

I never check css into VCS, ever!

The scss files: yes.

No generated files should be under version control, IMO. :)

I could see myself using Grunt, for a lot of things.

If fact, it would be one of the few reasons for me to use Node.js.

#12406 report it
andyhot at 2013/03/18 09:38pm
Extensions

Could you explain why you consider including extensions to the project problematic? I've gone with that approach wrt client scripts and never looked back (using a fork of minScript that also does less, sass and coffeescript).

My pain point with manually generated minified files is that it's always an extra step and even if automated you still have to check them into your VCS (which doesn't make sense to me since they're generated files)

#12405 report it
Cherif at 2013/03/18 06:38pm
Js templating

this is nice Chris, I think javascript templating like mustache and handlebars or others worth some diggin too maybe some Yii widgets with precompiled JS templates ;).

Leave a comment

Please to leave your comment.

Write new article