Learning CoffeeScript (with a dash of Node.js)

This post is part of my weekly tech learning series, where I take one hour each week to try out a piece of technology that I’d like to learn.

Today I decided to start my tech learning with CoffeeScript. CoffeeScript is “a little language that compiles into JavaScript” and has been really popular over the past year.

I know a good amount of JavaScript and have written a few JavaScript applications over the past few months so I was ready to see what improvements CoffeeScript includes.

Installing CoffeeScript was easy since I just used version 1.2.0 which is included in Ubuntu. It uses node which is surprising, I thought it was a RubyGem.

Todo App

For this session I decided to build a simple todo list. I have experience with them and it’s a small enough application that I can try in later weeks.

Right off the bat the syntax was different, not better or worse than JavaScript but different. I think it might be a little better after some heavy usage due to it’s sparseness (e.g. removing function(), curly braces, and semi-colons).

Flow

Once I got into CoffeeScript, I quickly got into a flow.

  1. I had my coffee file open in one window,
  2. the CoffeeScript compiler in the background running --watch to rebuild my code,
  3. and a console open with node to run my compiled JavaScript.

It wasn’t quite a REPL nor was it TDD but I was able to cycle between code/build/test quickly. I ended up with a basic TodoList class that let you add, remove, count, and summarize your list of todos. Nothing fancy.

Node.js Problems

The biggest problem I ran into was with node.js. Since I didn’t want to load everything into a browser I tried to build a small JavaScript program that would run in node. Since I was writing my application code in a separate file, getting it imported into node was a huge pain (eval helped). I know I was doing something wrong, hopefully I can figure out the right way when I spend some time learning node.js later.

Summary

Overall I was happy with CoffeeScript. With a little bit of regular usage I should be just as productive as with JavaScript. I can see it being especially useful when needing to model things more into classes.

Code

# todo.coffee
class TodoList
  constructor: ->
    @items = []
 
  add: (todoItem) ->
    @items.push(todoItem)
 
  remove: (todoItem) ->
    @items = _.reject @items, (existingTodo) ->
      existingTodo == todoItem
 
  count: ->
    @items.length
 
  summary: ->
    this.puts("==================================================")
    this.puts("= You have #{ this.count() } todo items")
    for todo in @items
      this.puts(todo)
    this.puts("--------------------------------------------------")
 
  puts: (line) ->
    console.log(line)
# app.coffee
 
# Node app
global._ = require("../vendor/underscore-min.js")
 
# Hackery to get node to load todo.js in scope
eval(require('fs').readFileSync('lib/todo.js', 'utf8'));
 
# Working on todo list
list = new TodoList()
list.add("Install CoffeeScript")
list.add("Install Node")
list.add("Install coffee-mode")
list.remove("Install CoffeeScript")
 
list.summary()

Compiled output:

var TodoList;
 
TodoList = (function() {
 
  function TodoList() {
    this.items = [];
  }
 
  TodoList.prototype.add = function(todoItem) {
    return this.items.push(todoItem);
  };
 
  TodoList.prototype.remove = function(todoItem) {
    return this.items = _.reject(this.items, function(existingTodo) {
      return existingTodo === todoItem;
    });
  };
 
  TodoList.prototype.count = function() {
    return this.items.length;
  };
 
  TodoList.prototype.summary = function() {
    var todo, _i, _len, _ref;
    this.puts("==================================================");
    this.puts("= You have " + (this.count()) + " todo items");
    _ref = this.items;
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      todo = _ref[_i];
      this.puts(todo);
    }
    return this.puts("--------------------------------------------------");
  };
 
  TodoList.prototype.puts = function(line) {
    return console.log(line);
  };
 
  return TodoList;
 
})();
var list;
 
global._ = require("../vendor/underscore-min.js");
 
eval(require('fs').readFileSync('lib/todo.js', 'utf8'));
 
list = new TodoList();
 
list.add("Install CoffeeScript");
 
list.add("Install Node");
 
list.add("Install coffee-mode");
 
list.remove("Install CoffeeScript");
 
list.summary();