Rigel Group

They shoot Yaks, don't they?

Using React.js With CoffeeScript

If you haven’t heard of the latest front-end hotness, head on over to React.js and prepare to have your mind blown. Brought to us by the fine folks over at Facebook, it presents a new take on building browser apps. Once you get your head around it, it really makes a lot of sense, and especially if you need something lightweight to add JavaScript components to your existing site. It doesnt have things like routers or data models. It just concerns itself with building interactive view components in a highly composeable (and performant!) way.

But, the examples are all in POS (Plain Ole JavaScript), which is a problem for me. I much prefer CoffeeScript. And due to the weirdness that is JSX, it is not easy to get React to work with CoffeeScript out-of-the-box.

The first thing to note is that you need to get your workflow pipeline set up correctly. Because, you will need to compile your CoffeeScript to JSX-style JavaScript, and then compile your JSX-style JavaScript to regular old JavaScript. (I know, it sounds crazy and I wouldn’t blame you if you bounced right now. But if you stick with me, enlightenment will come.)

I set up a Grunt workflow that does this, and the relevant parts of the Gruntfile.coffee look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
module.exports = (grunt) ->
  grunt.initConfig
    pkg: grunt.file.readJSON("package.json")

    srcDir: "./src"
    testDir: "./test"
    outputDir: "./dist"

    # Compile to JS first, then we will compile the JSX in another task and move to /dist
    coffee:
      options:
        # This is IMPORTANT, because the first line has to be a JSX comment
        bare: true
      all:
        files: [
          expand: true
          cwd: 'src/'
          src: ['**/*.coffee']
          dest: 'src/'
          ext: '.js'
        ]

    react:
      all:
        files:
          "<%= outputDir %>": "<%= srcDir %>"

    regarde:
      coffee:
        files: "<%= srcDir %>/**/*.coffee"
        tasks: ["coffee", "spawn_react"]

    # Set up a static file server
    connect:
      server:
        options:
          hostname: "0.0.0.0"
          port: 9292
          base: "."
          keepalive: true

    # Clean up artifacts
    clean:
      output: "<%= outputDir %>"

    # Execute server script
    exec:
      server:
        cmd: "./server.js"

  grunt.loadNpmTasks "grunt-contrib-coffee"
  grunt.loadNpmTasks "grunt-regarde"
  grunt.loadNpmTasks "grunt-contrib-connect"
  grunt.loadNpmTasks "grunt-contrib-clean"
  grunt.loadNpmTasks "grunt-exec"
  grunt.loadNpmTasks 'grunt-react'

  # Make sure we get an error on compilation instead of a hang
  grunt.registerTask 'spawn_react', 'Run React in a subprocess', () ->
    done = this.async()
    grunt.util.spawn grunt: true, args: ['react'], opts: {stdio: 'inherit'}, (err) ->
      if err
        grunt.log.writeln(">> Error compiling React JSX file!")
      done()

  grunt.registerTask "server", ["exec:server"]
  grunt.registerTask "build", ["coffee", "spawn_react"]

You will also need the server.js file, which is here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/env node

var spawn = require("child_process").spawn,
    watcher = spawn("grunt", ["regarde", "--force"]),
    server = spawn("grunt", ["build", "connect:server"]);

watcher.stdout.on("data", function(data) {
  var importantOutput = data.toString().split("\r?\n").filter(function(str) {
    return />>|Done|Warning|Running/.test(str);
  });

  process.stdout.write(importantOutput.join("\n"));
  // process.stdout.write(data);
});

server.stdout.on("data", function(data) {
  process.stdout.write(data);
});

watcher.on("exit", function(code, signal) {
  server.kill();
  process.exit();
});

server.on("exit", function(code, signal) {
  watcher.kill();
  process.exit();
});

process.on("exit", function() {
  watcher.kill();
  server.kill();
});

Now you can do a grunt server and start writing React code.

Here are some (contrived) code snippets that might help you out if you are struggling with how to reconcile React with CoffeeScript syntax. The secret is to shell out to JavaScript with the ` operator when necessary, so the code is intact when JSX transpiles it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
`/** @jsx React.DOM */`
# The above line HAS to be the first line in the file for JSX to know to process it.
MySimpleComponent = React.createClass
  render: ->  `<pre>{this.props.mytext}</pre>pre>`

MyComponent = React.createClass
  render: ->
    `(
      <ul>
        {this.props.items.map(
          function(item){
            return (
              <li><a href="#" onClick={_this.props.handleClick}>{item}</a></li>
            )
          }, this)
        }
      </ul>
    )`

A big thanks to Facebook and everyone who worked to bring this project to life. I look forword to using it in my projects.

[UPDATE] Vjeux has blog post about how to actually use CS instead of shelling out to JS.