Friday, 6 February 2015

Setting up your project with Grunt and RequireJS

Before we get into the meat and potatoes of our app. We must get the mundane details out of the way.
I would like to dive straight into the graphics and AI stuff, but thought a small primer on getting a nice working environment set up would be worth while in the long run.

There are many ways you can set up your project structure and many ways to host it.

I have chosen to host mine on Heroku via a Ruby On Rails project.

This Rails project will serve up our Javascript application and also may some day serve as a back end to serve data to our games.
I am thinking of using Pusher to share data across devices at some stage, and the Rails application will allow us to do this.

Setting up the Rails app and running it on heroku is outside of the scope of this blog. There are many tutorials online on how to achieve this.

I have included the source code of the project I am working on in tandem with this blog, and will link to the tags where necessary.

Here is the tagged version of the codebase, relating to this blog post.
And here is the current codebase.

See here for an example of the end goal we are trying to achieve


So, now let's get to some coding.


Setting Up Grunt


We will use a Javascript build tool called Grunt to automate some of the tasks required to bundle our code into a runnable application.

I have created a source directory in the rails project root called 2djssrc. This is where our source code will reside. We will rely on some grunt tasks to compile all of our code into the rails public folder which will then be served up to the user. We will also use a Javascript package manager called Bower to manage our Javascript plugins.

To get Grunt set up, you will need to install nodeJS and the node package manager. Here are some guidelines.

These steps will mean you now have a package.json file in your source directory that specifies the node dependencies. This file will list our grunt plugins later on.

Now, our Gruntfile.js is where all the magic happens. This simply contains all of our tasks that will help us to get our code to run.
To run a grunt task, we simply run:

grunt TASKNAME
Tasks can depend on eachother as follows (Don't worry about what each task does right now, the fact is that they can depend on eachother):

grunt.registerTask('compile', ['haml', 'sass', 'coffee', 'requirejs']);
grunt.registerTask('dist', ['compile', 'copy:assets']);
grunt.registerTask('default', 'dist');

If you leave out the taskname, then grunt will run whatever task you have configured as the default. More on Grunt later.


Setting Up Bower


So, we want to use some nice JS plugins. There are so many out there that do great things, so why reinvent the wheel?
Bower to the rescue.

npm install -g bower
This will give you access to the command:

bower install
We just need to tell bower where to install our downloaded plugins.
We do this by placing a .bowerrc file in our project root, with the content:

{ "directory": "src/javascripts/bower_lib" }
Now, let's try it out and install jquery.

bower install --save-dev jquery
You will see the library now in src/javascripts/bower_lib
You can search bower for packages with:

bower search PACKAGENAME
We will use bower to install dependencies needed in our project such as box2d. The source will be included in later blog posts so you will actually not have to run these commands, but get used to the syntax.


Directory Structure And RequireJS


So, we are working inside our 2djssrc directory.

We are going to use Coffeescript to write the our application. Coffeescript will compile down to javascript.
I find it saves a lot of needless typing and has really concise, readable syntax.

Looking at the source, you will notice that there is a directory named coffeescripts and also a directory named javascripts. We will write our code in the coffeescripts directory. We will use a Grunt plugin to compile our coffeescript code into it's associated javascript counterpart, and then we will feed all of that code into another grunt plugin (grunt-contrib-requirejs) that will compile it all into one single file by traversing all of the requireJS dependencies. That was a mouthful! But, really all it means is that we need to do the following (see the github repo for the actual code):

Tell node you want some packages:

npm install grunt-contrib-coffee --save-dev
npm install grunt-contrib-requirejs --save-dev

Now, enable these packages in your Gruntfile.js, by adding the following to the file:

grunt.loadNpmTasks('grunt-contrib-coffee');
grunt.loadNpmTasks('grunt-contrib-requirejs');

In the Javascripts directory, we see two already existing directories lib and bower_lib. The lib directory contains some Javascript libraries that we could not get through bower. They will be copied into the main js file by grunt once we run the RequireJS grunt task. The bower_lib directory is our bower library repository, where any library we get by running:

bower install --dev
is stored.

To achieve this, we must configure our grunt Coffeescript task to find our Coffeescript files and copy them into the Javascripts directory after compilation. We do this with:

    coffee: {
      options: {
        bare: true
      },
      compile: {
        expand: true,
        cwd: 'src/coffeescripts/',
        src: ['**/*.coffee'],
        dest: 'src/javascripts/',
        ext: '.js'
      }
    }

So, once our compiled js files are sitting in the javascripts directory, we can then run the requireJS task to traverse the dependencies and create one big monolithic file! We must tell requireJS where our libraries are. This is done by creating a config.coffee file and referencing it in the grunt task like so:

mainConfigFile: 'src/javascripts/config.js'
In this file we set up the paths to our dependencies. More on this here. Have a look at the source code again if you are unsure.

We then configure the task, as follows, passing it the entry point file "main" as follows:

    requirejs: {
      compile: {
        options: {
          name: 'main',
          baseUrl: 'src/javascripts',
          mainConfigFile: 'src/javascripts/config.js',
          out: '../public/javascripts/main-built.js',
          optimize: "none"
        }
      }
    }

The way the above works is that it looks for the file main.js in the "baseUrl" directory. This file, you write yourself, and it is the entry point into your application.
It will load all other required files using the following syntax:

require([FILELIST], function(DEPENDENCIES){})
Read the requireJS docs for more on this.
Common practice is that main.js will be the entry point into your application.
From there app.js will be called, which contains your application logic.
If you look at the source, you will see that our app, at the moment simply alerts a message to the screen. Ground breaking stuff!


This is not all grunt can do.
We can use it to clean up compiled JS files in the javascripts directory that are no longer needed after compilation.
We can use it to copy over assets or any other files to other destinations in our project.
Furthermore we can run a watcher task that reloads our browser when a file changes. More on this later!
So, if you run the grunt dist task in the included project, you will see a file in the public/javascripts directory called main-build.js which includes all of our source and any included libraries we need.


HAML/SASS And Serving Up Our App


Ok, so we now have two grunt tasks which get us from a bunch of coffeescript files to a monolithic javascript file. What next?

Well, there are some libraries we need that we have to include manually.
I have manually included the requirejs library for now as I had some complications with referencing it from the html.
There is a directory in our source directory called lib. This contains any other libraries we need outside of the requirejs compilation.
We'll we use a grunt task to copy any files in this directory to public/javascripts/lib to be easily referenced.

We have to somehow serve up our app on a web page.
I like to use HAML to write my HTML, but it requires some setting up.

Again, grunt has a task for this: https://github.com/jhchen/grunt-haml2html
We create our index.haml file in our 2djssrc directory and use this plugin to compile it to public/game.html.
If you look in the index.haml file you will see that all it does is reference a file "requirejs" and also has a data-main attribute in the script tag which is used by requireJS.
This attribute must point to our fully compiled JS file from the previous grunt task.

<script data-main='javascripts/main-built' src='javascripts/lib/require.js'></script>
Note that you must install the haml gem for this. It is already included in the Rails app Gemfile, so a simple bundle command will solve this.

One last thing is that I will also use the grunt SASS plugin to compile some SASS files down to CSS. The idea is basically the same as what we did for the coffeescript files. Take a look in the source to get a better understanding.

In theory, this is all we need to get going. If we view our compiled game.html (via the /game url in our rails app) page we will hopefully see the alert message showing up.

It seems like a lot of work to get all this set up, and yes, it is a very steep learning curve.
However, once you have mastered these steps, you will benefit from fast development and modular javascript.
It will allow us to concentrate more on our application logic rather than where stuff should go.
Grunt will serve us as a great tool to automate a lot of things as we progress, so read up on it.

Hopefully, you got something from this tutorial, and it won't be long before we get into some of the good stuff.

Stay tuned!

No comments:

Post a comment