This post shares some highlights of the experience porting a non-trivial library from plain JavaScript to CoffeeScript in case other parties are considering a similar transition.
Yesterday’s announcement of QuickUI 0.9 mentioned that the framework source code has now been ported to CoffeeScript. The QuickUI framework is intended for plain JavaScript development as well; nothing in the change of source language changes that. But experimentation with the CoffeeScript language suggested there were enough advantages to the language that, going forward, it would be worth porting the runtime from plain JavaScript to CoffeeScript.
Overall, the port from plain to JavaScript to CoffeeScript went rather smoothly, and the bulk of it took about two days. The QuickUI runtime, quickui.js, is a reasonably complex JavaScript library, which is to say that it’s not a toy or trivial sample application. The last plain JavaScript version of the QuickUI runtime, quickui-0.8.9.js, was about 7700 lines of plain JavaScript (including comments), or about 60K, developed over the course of four and a half years.
Automatic translation with js2Coffee
The handy js2coffee conversion tool was used to kickstart the port. Kudos to Rico Sta. Cruz for this great tool.
- The automatically translated CoffeeScript immediately passed 97% of the QuickUI unit test suite. The remaining 4 broken tests were do to a single issue related to translation of the “instanceof” keyword, which was easy enough to work around.
- The one thing js2coffee doesn’t translate (yet) are comments, so these had to be copied over by hand. Tedious, but straightforward.
- Similarly, the js2coffee output sometimes produced long lines that needed to be hand-broken for legibility. Again, a bit tedious but straightforward.
- Once all unit tests passed, the unit tests themselves were ported to CoffeeScript by the same process.
After about a morning of work, a CoffeeScript-based quickui.js was functional. It passed all unit tests, and could actually be used to drive a non-trivial QuickUI-based body of code like the QuickUI Catalog.
Towards idiomatic CoffeeScript
After the mechanical port with js2coffee, various CoffeeScript idioms were applied incrementally to replace the standard JavaScript idioms with their more concise CoffeeScript versions. This took another day and half or so.
- There was occasion to use pretty much all of CoffeeScript’s syntactic sugar. References to Foo.prototype.bar() were replaced with the more concise Foo::bar(). Closure variables to hold “this” for use in an event handler were replaced with CoffeeScript’s “=>” syntax. Etc., etc.
- Because CoffeeScript can wrap a body of code in a single function closure, this no longer needed to be done by hand. A wrapping closure like that can complicate the management of a pile of plain JavaScript files. The closure will typically have to be created through a build process that includes a JavaScript fragment (to start the closure) before the real JavaScript files, and another fragment (to end the closure) afterwards. (The jQuery Makefile does this, for example.) CoffeeScript’s built-in support for a closure that spans multiple files finally made it easy enough to break up the quickui.js runtime from a single monolithic JavaScript file into a much saner and more manageable collection of CoffeeScript files. That is, while the same degree of manageability could have been achieved in plain JavaScript, CoffeeScript made it simple enough that it actually got done.
- The QuickUI runtime itself doesn’t create many classes, but in some cases (e.g., the unit test suite), classes could be created via CoffeeScript’s concise class syntax. This took advantages of QuickUI’s new support for creating web user interface controls using CoffeeScript class syntax.
- JavaScript “for” loops were replaced with CoffeeScript list comprehensions.
Idiomatic CoffeeScript iteration over jQuery objects
var $elements = $(".someClass"); $elements.each( function( index, element ) { $( element ).foo(); });
var $buttons = $(".BasicButton"); $buttons.eachControl( function( index, $button ) { $button.foo(); });
Control::segments = -> ( @constructor element for element in @ )
$button.foo() for $button in Control(".BasicButton").segments()