Skip to content

Latest commit

 

History

History
293 lines (212 loc) · 8.12 KB

coffeescript-to-javascript-guidelines.md

File metadata and controls

293 lines (212 loc) · 8.12 KB

CoffeeScript to JavaScript Guidelines

Steps

  1. Rename your file extension from foo.coffee to foo.js
  2. Open http://js2.coffee/ & paste the entire contents foo.js into the CoffeeScript side
  3. Paste the entire contents of the output back into your foo.js
  4. Rebuild the app, functionality should be unchanged & there should be no JS errors in the console
  5. If necessary, convert AMD (requirejs) style modules to Browserify (commonjs) style, as described below.
  6. Refactor the “CoffeeScript magic”, using examples below
  7. If this conversion is being done at the same time as a feature, submit a PR using the interstitial PR process

Converting from AMD

An AMD style module looks like this:

define(function(require, exports, module) {
  var foo = require('foo');

  var MyModel = Backbone.Model.extend(_.extend({
    /* ... */
  }, foo);

  return MyModel;
});

To convert to commonjs style, remove the surrounding define and assign the export to module.exports instead of returning it. It should end up looking about the same as modules in serverside projects.

Be aware that some of our AMD style modules are currently getting away with using certain dependencies (like jquery or underscore) without requiring them. This will not work once the modules are converted, so require statements may need to be added in.

var _ = require('underscore');
var foo = require('foo');

var MyModel = Backbone.Model.extend(_.extend({
  /* ... */
}, foo);

module.exports = MyModel;

Once everything is commonjs style, we can remove more magic from our build process so it's closer to using vanilla browserify.

Refactoring CoffeeScript magic

Disclaimer: This file is not a JavaScript style guide. Reference the following files for coding style & principles:

Classes & super

CoffeeScript

class CoreProduct extends Product

  initialize: ->
    super

Generated JavaScript

This is what would be output from js2coffee

var CoreProduct,
  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
  hasProp = {}.hasOwnProperty;

CoreProduct = (function(superClass) {
  extend(CoreProduct, superClass);

  function CoreProduct() {
    return CoreProduct.__super__.constructor.apply(this, arguments);
  }

  CoreProduct.prototype.initialize = function() {
    return CoreProduct.__super__.initialize.apply(this, arguments);
  };

  return CoreProduct;

})(Product);

// ---
// generated by coffee-script 1.9.2

Refactored JavaScript

We can change from CoffeeScript's classes to Backbone's classes, like so

var CoreProduct = Product.extend({
  initialize: function() {
    // we also change from using from `CoreProduct.__super__` to `Product.prototype`
    return Product.prototype.initialize.apply(this, arguments);
  }
});

Variables

CoffeeScript

_ = require('underscore')
Product = require('models/product')
TerminalMixins = require('presenters/mixins/terminal')

Generated JavaScript

This is what would be output from js2coffee

var Product, TerminalMixins, _;

_ = require('underscore');

Product = require('models/product');

TerminalMixins = require('presenters/mixins/terminal');

// ---
// generated by coffee-script 1.9.2

Refactored JavaScript

We would move the variable declarations to the same line that they are initialised, like so

var _ = require('underscore');
var Product = require('models/product');
var TerminalMixins = require('presenters/mixins/terminal');

Existence checks

CoffeeScript

_hasFlightCode: ->
  @get('flights')?.out?.flight?.code

Generated JavaScript

This is what would be output from js2coffee

_hasFlightCode: function() {
  var ref, ref1, ref2;
  return (ref = this.get('flights')) != null ? (ref1 = ref.out) != null ? (ref2 = ref1.flight) != null ? ref2.code : void 0 : void 0 : void 0;
}

// ---
// generated by coffee-script 1.9.2

Refactored JavaScript

This is fairly unreadable, we would remove the temporary ref variables and do the following.

_hasFlightCode: function() {
  var flights = this.get('flights');
  if(!flights || !flights.out || !flights.out.flight || !flights.out.flight.code)) {
    return;
  }
  return flights.out.flight.code;
}

Note: this particular example is excessively complicated. Ideally, if we're having to check a property that's nested this deeply then the checks should be pushed further upstream, such as a flight model in this case.

Forcing context

CoffeeScript

parent: ->
  @someMethod (err, result) =>
    @foo() # Executed in context of parent

Generated JavaScript

This is what would be output after the conversion:

parent: function() {
  return this.someMethod((function(_this) {
    return function(err, result) {
      return _this.foo(); // Executed in context of parent
    };
  })(this));
}

Desirable JavaScript

parent: function() {
  var self = this;
  this.someMethod(function(err, result) {
      self.foo(); // Executed in context of parent
  });
}

Return values

CoffeeScript

func = ->
  doSomething()

Generated JavaScript

This is what would be output after the conversion:

var func;

func = function() {
  return doSomething();
};

Refactoring process

With coffeescript the last statement of a function is always returned even if not required.

The first thing to do here is to check how this function is called. It could be called from many places so it's best to perform a search of the code base for the method name.

If the callers expect a return value from the function please keep the return keyword, if not please remove it to avoid confusion.

Static methods

CoffeeScript

There are occasions when you will come across static methods or properties that are attached directly to the class itself.

class Foo extends Bar
  @staticBaz = ->
    ...

Generated JavaScript

This is what would be output after the conversion:

Foo = (function(superClass) {
  ...
  Foo.staticBaz = function() {};
  ...
})(Bar);

// ---
// generated by coffee-script 1.9.2
};

Desirable JavaScript

We can use the optional classProperties parameter in Backbone's extend function to achieve the same thing.

var Foo = Bar.extend({
  ...
}, {
  staticBaz: function() {
  ...
});

Comments

Please check all comments in the source coffeescript as converters such as http://js2.coffee/ will not convert comments and they will be removed in the Javascript output.

Tripapp does not have many comments but those that are there normally indicate particular problem areas or complex logic which may reference JIRAs etc.

If the comment is still relevant after the conversion please make sure the comment is kept on the appropriate place.

Full conversion example of models/carpark.coffee

https://gist.github.com/jackdcrawford/a8e5dacf6cecdb9b6bfe

Interstitial PR process

The process for converting CoffeeScript to JavaScript at the same time as writing features

  1. Create a new “interstitial” branch, based on the project’s master branch
  2. Create a “code conversion” branch, based on the “interstitial” branch
  3. Create a “feature” branch, based on the “code conversion branch”
  4. Both “code conversion” and “feature” can then be merged into “interstitial”, removing the need for two pull requests to be tested

Interstitial PR process