Convert legacy CoffeeScript to ES2015 Javascript easily (ish)

Posted on

Before projects like BabelJS, CoffeeScript made a lot of sense. However, especially in the EmberJS community, CoffeeScript has fallen out of favor and it’s recommended to convert code over to the new ES6/ES2015 style Javascript.



I’m not going to say that this is fun or (very) easy, but if you’re trying to kick the CoffeeScript, this process will help you take advantage of ES2015 syntax instead of directly converting CoffeeScript to ES5-style Javascript. If you’re trying to get over to Ember-CLI and have a legacy CoffeeScript app, check out this post for more info before you kick off a project like this.

Prerequisites

You’ll need a few tools to get started:

Decaffeinate

This is the main workhorse of the conversion process. It converts CoffeeScript (well, CoffeeScript Redux) to ES2015 style JavaScript. It’s not perfect, and you sometimes have to tweak the source CoffeeScript for it to work (more on this in a minute), but it’s better than doing all the conversion by hand.

CoffeeScript Redux

Since your source CoffeeScript probably won’t always match the rules of CoffeeScript Redux, it’s convenient to have the raw processor to identify where you need to tweak the file.

Converting Files

To convert a file, run it through decaffeinate

› decaffeinate my-file.coffee

If everything goes well, you’ll get no response. That’s it! Rinse and repeat.

When it Doesn’t Work

It would be great if it worked all the time. However, CoffeeScript Redux is pickier than standard CoffeeScript, so a lot of times it will bail out and leave you with an empty JS file. When it doesn’t work, you’ll see something like this

› cat my-file.coffee
foo = func('param',
  bar: 'baz'
  qux: 'im out of things'
)

› decaffeinate my-file.coffee
/Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/node_modules/coffee-script-redux/lib/module.js:61
      throw new Error(formatParserError(preprocessed, e));
            ^
Error: Syntax error on line 1, column 1: unexpected '\n' (\u000A)
1 : foo = func('param',
^ : ^
2 :   bar: 'baz'
3 :   qux: 'im out of things'
4 : )
    at CoffeeScript.parse (/Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/node_modules/coffee-script-redux/lib/module.js:61:13)
    at parse (/Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/lib/utils/parse.js:36:42)
    at convert (/Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/lib/index.js:177:43)
    at ReadStream.<anonymous> (/Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/lib/cli.js:106:35)
    at ReadStream.emit (events.js:129:20)
    at _stream_readable.js:908:16
    at process._tickCallback (node.js:355:11)

The file I ran through is totally valid CoffeeScript, but is not valid CoffeeScript Redux. There’s an open bug to make this error message better, but in the meantime, you’ll want to use the CoffeeScript Redux package we installed earlier.

Syntax error on line 1, column 20: unexpected '\n' (\u000A)
1 : foo = func('param',
^ :~~~~~~~~~~~~~~~~~~~~^
2 : bar: 'baz'
3 : qux: 'im out of things'
4 : )

This will help you to narrow down the problem. For instance, here the problem is that we need to wrap the object with curlies, like this:

foo = func('param', {
  bar: 'baz'
  qux: 'im out of things'
})

Now when we run it again, everything is happy!

› decaffeinate my-file.coffee

However, sometimes there are nodes that decaffeinate just doesn’t understand, like instanceof. You’ll see errors like this:

› cat my-file.coffee
foo = (bar, baz) ->
  bar instanceof baz

› decaffeinate my-file.coffee
/Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/lib/utils/traverse.js:150
    throw new Error('cannot traverse unknown node type: ' + node.type);
          ^
Error: cannot traverse unknown node type: InstanceofOp
    at childPropertyNames (/Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/lib/utils/traverse.js:150:11)
    at traverse (/Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/lib/utils/traverse.js:36:47)
    at /Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/lib/utils/traverse.js:27:11
    at Array.forEach (native)
    at /Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/lib/utils/traverse.js:25:15
    at Array.forEach (native)
    at descend (/Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/lib/utils/traverse.js:22:32)
    at traverse (/Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/lib/utils/traverse.js:39:5)
    at /Users/blimmer/.nvm/versions/node/v0.12.5/lib/node_modules/decaffeinate/lib/utils/traverse.js:31:9
    at Array.forEach (native)

To make this a bit easier, just comment out the code the parser doesn’t understand.

› cat my-file.coffee
foo = (bar, baz) ->
  # bar instanceof baz

Then the file will convert, and go manually clean up the resultant code.

Happy Converting!

Find an issue?
Open a pull request against my blog on GitHub.
Ben Limmer