CoffeeScript to ES6 migrations

Goal: Converting a major project which used CoffeeScript into ES6.

Why would you consider doing this:

  • You need to make updates to your project. And CoffeeScript is hindering with that process.
  • CoffeeScript is slowly dying within the developer community, and you want your codebase to be more relevant.
  • It's easier to hire/find more JS developers than CoffeeScript hipsters.
  • No reason. You just want to get rid of CoffeeScript.

The good part is you don't even have to know CoffeeScript to convert things to ES6. I didn't. But if you're even a bit familiar with Ruby and/or JavaScript there isn't a steep learning curve, and can be picked up in a single setting relatively quickly.

Set Up

Text Editor
Getting your text ready for the project, will save you a lot of time and pain.
I typically use SublimeText3 for work, but for whatever reason I could not get the plugins I needed installed for the project. With that as a sufficient excuse, I decided to get it over with and finally try out the Atom editor. It was very useful for JS linting purposes, and I would definitely recommend it for these type of projects.

Useful Plugins/Packages:
https://github.com/roadhump/SublimeLinter-eslint
Not working with ST3 at the time of publishing this post.

The requisite atom community packages: linter, linter-eslint, beautify

Decaffeinate
Get it. You need to be on node version 6 to use decaffeinate.
https://github.com/decaffeinate/decaffeinate
$ npm install -g decaffeinate

Once installed, cd into your directory and run:
$ decaffeinate <filename>.coffee

If the CoffeeScript is kosher, you should flawlessly get a JavaScript file generated. This is not the end of the job, rather the beginning. The new .js file needs to be combed through and the ES6 styles you prefer manually written. I will cover some common use cases below.

Most of the time however, you will be prompted with a decaf error which means you need to edit your CoffeeScript file before it can be parsed into modern JavaScript.

Guidelines & Gotchas

~ String Interpolations
Backticks are the way to go with ES6. Get rid of the double quotes that CoffeeScript uses, especially with string interpolations. URLs were where we were getting errors the most. You can get your files decaffeinated with single quotes, but you can still fail tests without backticks.

Note regarding urls. While CoffeeScript uses double-quotes, those should have limited business in ES6. In interpolating, then make sure dollar signs, ${}, are being used instead of #{}, and of course remove the superfluous plus signs. But even single quotes can fail tests, so it's best to replace those with backticks.

~ JSON parse body
A surprise error that prevented decaf from converting CoffeeScript to JS, were because of a bunch of errors related to JSON. Once I found that out, it was easy to do a quick find and replace on it.
Find: (JSON.parse body) or JSON.parse body
Replace: JSON.parse(body)

Those 2 were the major errors I came across in trying to decaf our files. Once the requisite CoffeeScript was edited and successfully converted, phase 2 is to change your new JS code into ES6. Some typical cases to keep in mind regarding editing JS to ES6:

~ Imports
Where there is import, change to require.
Example:
import Boom from ‘boom’;
Should change into:
const Boom = require(‘boom’);

Here's some useful RegEx to help speed up the conversion.

Atom replace:
Find: ^import (.*?) from (.*?);
Replace: const $1 = require($2);

Sublime find-and-replace RegEx
Find: ^import (.*?) from (.*?);
Replace: const \1 = require(\2);

Remember to tell your text editor that you are finding a RegEx and not text, otherwise the above commands won't work.

~ Guard Functions
The was something more unique to the repo we were refactoring. Our guard function looked something like this:

function __guard__(value, transform) { return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; }

The solution for us was using the lodash library.
const _ = require('lodash');

Where ever we had a guard function, changed it to lodash using the .get() method.

~ Exports
Replace something like
export default exports = mongoose;
to
module.exports = mongoose;

Here's a helpful Atom Regex find-and-replace for that. (Should work in Sublime too, but haven't tested):

Find: ^export default( exports =)?
Replace: module.exports =

~ let exports
On transpiling, if let exports is on top- remove it. You don't need it.

Note:

  • There will be a lot of suggestions your text editor will make for replacing var with the new ES6 syntax of let/const. While the rules are pretty straightforward, it really depends on the context of your code. Don't have to follow all of ESLint's suggestions.

  • Good to know you can click on Atom’s ES Lint errors and it takes you to a site to explain the error in more detail.
    Also I learned to just ignore the ES Lint errors for the Chai Test syntax: describe, it, expect.

  • Where stuck in transpiling from CoffeeScript to JavaScript just use the Decaf Repl to play with your code, and see where it's breaking:
    http://decaffeinate-project.org/repl/
    Good way to debug in little pieces of code.

Enjoy deleting unused variables, beautifying your code, getting rid of a transpiler and adopting new community standards.

Updates:
July 18, 2017: http://eng.datafox.com/javascript/2017/07/18/decaffeinating-large-coffeescript-codebase/