NexusCS

CoffeeScript

JavaScript
Quick reference for CoffeeScript - a little language that compiles into JavaScript with Ruby-inspired syntax and significant whitespace.
coffeescript
javascript
transpiler

Getting started

Introduction

CoffeeScript is a little language that compiles into JavaScript. It provides syntactic sugar inspired by Ruby, Python, and Haskell to enhance JavaScript's readability. CoffeeScript 2.7+ compiles to modern ES6+ JavaScript.

Note: Many CoffeeScript features inspired ES6 (arrow functions, classes, destructuring). Usage has declined as modern JavaScript adopted these features, but it's still maintained and used in legacy projects.

Installation

# Global installation
npm install -g coffeescript

# Project installation
npm install --save-dev coffeescript

# Run REPL
coffee

# Compile file
coffee -c script.coffee

# Watch and compile
coffee -cw src/

Quick Example

# CoffeeScript
square = (x) -> x * x
cube   = (x) -> square(x) * x

list = [1, 2, 3, 4, 5]
squares = (square num for num in list)

Compiles to:

// JavaScript (ES6)
var cube, list, square, squares;

square = function (x) {
  return x * x;
};

cube = function (x) {
  return square(x) * x;
};

list = [1, 2, 3, 4, 5];

squares = (function () {
  var i, len, results;
  results = [];
  for (i = 0, len = list.length; i < len; i++) {
    num = list[i];
    results.push(square(num));
  }
  return results;
})();

Syntax Basics

Variables

# Assignment
name = "Alice"
age = 30

# Multiple assignment
[first, second] = [1, 2]

# Object destructuring
{name, age} = person

# Swapping
[a, b] = [b, a]

Functions

# Arrow syntax
square = (x) -> x * x

# Multi-line
greet = (name) ->
  message = "Hello, #{name}!"
  console.log message
  message

# No parameters
sayHi = -> console.log "Hi!"

# Default parameters
greet = (name = "World") ->
  "Hello, #{name}!"

# Rest parameters
sum = (nums...) ->
  nums.reduce ((a, b) -> a + b), 0

Conditionals

# If/else
if age > 18
  "adult"
else
  "minor"

# Unless (if not)
unless ready
  prepare()

# Inline if
mood = if happy then "😊" else "😢"

# Postfix conditionals
alert "Ready!" if ready
console.log "Not ready" unless ready

# Existential operator
name ?= "Anonymous"  # Set if undefined

Operators

Existential Operator

# Check existence (not null/undefined)
user?.name

# Safe method call
user?.getName?()

# Default value
name = user?.name ? "Guest"

# Existential assignment
speed ?= 100  # Set if null/undefined

Compiles to:

var name, ref, ref1;

if (user != null) {
  user.name;
}

if (typeof user !== "undefined" && user !== null) {
  if (typeof user.getName === "function") {
    user.getName();
  }
}

name = (ref = user != null ? user.name : void 0) != null ? ref : "Guest";

if (speed == null) {
  speed = 100;
}

Comparison Operators

# Equality
a is b        # a === b
a isnt b      # a !== b

# Comparison
a > b
a < b
a >= b
a <= b

# Chained comparisons
10 < age < 100

Logical Operators

# And/or
if ready and willing
  proceed()

if tired or hungry
  rest()

# Not
if not ready
  wait()

# Aliases
true, yes, on   # All compile to true
false, no, off  # All compile to false

Strings

String Interpolation

# Double quotes allow interpolation
name = "Alice"
greeting = "Hello, #{name}!"

# Expressions in interpolation
"The answer is #{2 + 2}"

# Single quotes are literal
literal = 'Hello, #{name}!'  # Not interpolated

Multi-line Strings

# Block strings (preserve indentation)
html = """
  <div>
    <h1>Title</h1>
    <p>Content</p>
  </div>
  """

# Block strings remove leading indent
message = """
  Hello,
  World!
  """  # "Hello,\nWorld!"

Heredocs

# Multi-line with interpolation
user = "Alice"
email = """
  Dear #{user},

  Welcome to our service!

  Best regards,
  Team
  """

Arrays & Objects

Arrays

# Array literals
numbers = [1, 2, 3, 4, 5]

# Multi-line arrays (no commas needed)
foods = [
  'apple'
  'banana'
  'cherry'
]

# Ranges
numbers = [1..5]     # [1, 2, 3, 4, 5]
numbers = [1...5]    # [1, 2, 3, 4]

# Slicing
list[0..2]   # First 3 items
list[2..]    # From index 2 to end
list[..-2]   # All but last item

Objects

# Object literals
person =
  name: "Alice"
  age: 30
  greet: -> "Hi, I'm #{@name}"

# Computed properties
key = "name"
obj = {[key]: "value"}

# Shorthand (ES6 style)
name = "Alice"
age = 30
person = {name, age}

Destructuring

# Array destructuring
[first, second, rest...] = [1, 2, 3, 4, 5]

# Object destructuring
{name, age} = person

# Nested destructuring
{address: {city}} = person

# Default values
{name = "Guest"} = user

Comprehensions

List Comprehensions

# Map
squares = (x * x for x in [1..10])

# Filter
evens = (x for x in [1..10] when x % 2 is 0)

# Multiple conditions
results = (x for x in list when x > 10 and x < 20)

# Nested loops
pairs = ([x, y] for x in [1..3] for y in [1..3])

Compiles to:

var evens, i, j, k, l, pairs, results, squares, x, y;

squares = (function () {
  var i, results;
  results = [];
  for (i = 1; i <= 10; i++) {
    results.push(i * i);
  }
  return results;
})();

evens = (function () {
  var i, results;
  results = [];
  for (i = 1; i <= 10; i++) {
    if (i % 2 === 0) {
      results.push(i);
    }
  }
  return results;
})();

Object Comprehensions

# Create object from array
obj = {[key]: value for key, value in pairs}

# Transform object
doubled = {key: val * 2 for key, val of numbers}

Loop Comprehensions

# Iterating arrays
for item in array
  console.log item

# Iterating objects
for key, value of object
  console.log "#{key}: #{value}"

# With index
for item, index in array
  console.log "#{index}: #{item}"

# Loop with condition
for item in array when item.active
  process item

Classes

Class Definition

class Animal
  constructor: (@name) ->
    @alive = true

  move: (meters) ->
    console.log "#{@name} moved #{meters}m"

# Create instance
dog = new Animal("Rex")
dog.move(10)

Compiles to:

var Animal, dog;

Animal = class Animal {
  constructor(name) {
    this.name = name;
    this.alive = true;
  }

  move(meters) {
    return console.log(`${this.name} moved ${meters}m`);
  }
};

dog = new Animal("Rex");
dog.move(10);

Inheritance

class Dog extends Animal
  constructor: (name, @breed) ->
    super(name)

  bark: ->
    console.log "Woof! I'm a #{@breed}"

  move: ->
    console.log "Running..."
    super(5)

# Create instance
rex = new Dog("Rex", "Labrador")
rex.bark()
rex.move()

Static Methods

class MathUtils
  @PI: 3.14159

  @square: (x) ->
    x * x

  @cube: (x) ->
    x * x * x

# Usage
MathUtils.square(5)  # 25
MathUtils.PI         # 3.14159

Getters and Setters

class Person
  constructor: (@firstName, @lastName) ->

  @getter 'fullName', ->
    "#{@firstName} #{@lastName}"

  @setter 'fullName', (name) ->
    [@firstName, @lastName] = name.split(' ')

Control Flow

Switch/Case

# Switch statement
switch day
  when "Mon", "Tue", "Wed", "Thu", "Fri"
    console.log "Weekday"
  when "Sat", "Sun"
    console.log "Weekend"
  else
    console.log "Invalid day"

# Switch with expressions
result = switch grade
  when "A" then "Excellent"
  when "B" then "Good"
  when "C" then "Average"
  else "Poor"

Try/Catch

try
  riskyOperation()
catch error
  console.error "Error:", error
finally
  cleanup()

# Inline try
value = try JSON.parse(str) catch e then {}

Loops

# While loop
while condition
  doSomething()

# Until loop (while not)
until ready
  wait()

# Loop (infinite)
loop
  doSomething()
  break if condition

# Break and continue
for item in list
  continue if item.skip
  break if item.stop
  process item

Modern JavaScript Features

Async/Await

# Async function
fetchData = (url) ->
  response = await fetch(url)
  data = await response.json()
  data

# Async arrow
processUser = (id) ->
  user = await getUser(id)
  posts = await getPosts(user)
  {user, posts}

Spread Operator

# Array spread
numbers = [1, 2, 3]
more = [0, numbers..., 4, 5]

# Function arguments
Math.max items...

# Object spread
defaults = {a: 1, b: 2}
options = {defaults..., c: 3}

Destructuring with Defaults

# Function parameters
greet = ({name = "Guest", age = 0} = {}) ->
  "Hello, #{name} (#{age})"

# Array with defaults
[a = 1, b = 2] = someArray

Template Literals

# Multi-line with interpolation
message = """
  Hello #{name},
  You have #{count} new messages.
  """

# Expression interpolation
result = "Sum: #{numbers.reduce(((a,b) -> a + b), 0)}"

Compilation

CLI Commands

# Compile single file
coffee -c script.coffee

# Compile to specific output
coffee -c -o dist/ src/script.coffee

# Watch mode
coffee -cw src/

# Join files
coffee -j output.js -c input1.coffee input2.coffee

# Compile and print
coffee -p script.coffee

# Print without running
coffee -e "console.log 'hi'"

# Bare mode (no function wrapper)
coffee -cb script.coffee

Compilation Options

# Source maps
coffee -cm script.coffee

# ES6 output format
coffee -c --transpile script.coffee

# Show tokens
coffee -t script.coffee

# Show AST
coffee --ast script.coffee

# Print version
coffee -v

Node.js Integration

// Register CoffeeScript in Node
require("coffeescript/register");

// Now you can require .coffee files
const myModule = require("./module.coffee");

Build Tools

// Webpack config
module.exports = {
  module: {
    rules: [
      {
        test: /\.coffee$/,
        use: "coffee-loader",
      },
    ],
  },
  resolve: {
    extensions: [".js", ".coffee"],
  },
};

Common Patterns

Module Pattern

# Export in Node.js
module.exports = class MyClass
  constructor: ->
    # ...

# Or export object
module.exports = {
  method1: -> # ...
  method2: -> # ...
}

IIFE (Immediately Invoked Function Expression)

# CoffeeScript auto-wraps in IIFE
do ->
  privateVar = "secret"

  window.publicAPI =
    getData: -> privateVar

Callback Pattern

# Traditional callback
fs.readFile 'file.txt', (err, data) ->
  return console.error err if err
  console.log data

# Promise pattern
fetchData()
  .then (data) -> process data
  .catch (err) -> console.error err

Event Handlers

# jQuery style
$ ->
  $('#button').on 'click', (e) ->
    e.preventDefault()
    console.log 'Clicked!'

# DOM event
element.addEventListener 'click', (e) ->
  console.log e.target

JavaScript Comparison

Function Syntax

# CoffeeScript
square = (x) -> x * x
multiply = (a, b) -> a * b
// JavaScript ES6
const square = (x) => x * x;
const multiply = (a, b) => a * b;

Class Syntax

# CoffeeScript
class Person
  constructor: (@name, @age) ->

  greet: ->
    "Hi, I'm #{@name}"
// JavaScript ES6
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `Hi, I'm ${this.name}`;
  }
}

Array Methods

# CoffeeScript
squares = (x * x for x in [1..5])
evens = (x for x in list when x % 2 is 0)
// JavaScript ES6
const squares = [1, 2, 3, 4, 5].map((x) => x * x);
const evens = list.filter((x) => x % 2 === 0);

Destructuring

# CoffeeScript
{name, age} = person
[first, rest...] = array
// JavaScript ES6
const { name, age } = person;
const [first, ...rest] = array;

Gotchas

Implicit Returns

# CoffeeScript returns last expression
getValue = ->
  x = 10
  x + 5  # Returns 15

# Explicit return
getValue = ->
  x = 10
  return x + 5
  console.log "Never reached"

Every CoffeeScript function returns the last expression. Use explicit return or assign to variable to avoid unexpected returns.

Whitespace Sensitivity

# WRONG: Inconsistent indentation
if condition
  doThis()
   doThat()  # Error!

# RIGHT: Consistent indentation
if condition
  doThis()
  doThat()

CoffeeScript uses significant whitespace. Always use consistent indentation (2 spaces recommended).

Existential Operator

# ?= only assigns if null/undefined
x = false
x ?= true   # x is still false!

# Use || for falsy values
x = false
x = x || true  # x is now true

The ?= operator checks for null/undefined, not falsy values like false, 0, or "".

Global Variables

# Variables are local by default
func = ->
  x = 10  # Local to function

# Use global explicitly
func = ->
  global.x = 10  # Or window.x in browser

CoffeeScript makes all variables local by default. Use global or window for globals.

this/@ Context

class Widget
  constructor: ->
    @element = $('#widget')

    # WRONG: Loses context
    @element.on 'click', @handleClick

    # RIGHT: Fat arrow preserves context
    @element.on 'click', (e) => @handleClick(e)

  handleClick: (e) ->
    console.log @  # 'this' context

Use fat arrow => to preserve this context in callbacks.

CoffeeScript vs Modern JavaScript

What CoffeeScript Pioneered

CoffeeScript influenced many ES6+ features:

CoffeeScript (2009) JavaScript (ES6+/2015+)
-> arrow functions => arrow functions
class syntax class syntax
[a..b] ranges No direct equivalent
{a, b} destructuring {a, b} destructuring
`string #{expr}` interpolation `string ${expr}` template literals
for..in array iteration for..of array iteration
a ?= b existential a ?? b nullish coalescing (ES2020)
obj?.prop safe navigation obj?.prop optional chaining (ES2020)

When to Use CoffeeScript

Use CoffeeScript if:

  • Maintaining legacy CoffeeScript projects
  • You prefer significant whitespace syntax
  • Working with Ruby/Python developers who prefer similar syntax
  • Need ranges [1..10] feature

Use Modern JavaScript if:

  • Starting new projects
  • Working with modern frameworks (React, Vue, Angular)
  • Need broader ecosystem support
  • Want better IDE/tooling support
  • Working in teams unfamiliar with CoffeeScript

Also see