Friday, December 30, 2011

10 lessons from porting JavaScript to Dart

I wrote a simple HTML5 game for my Google IO 2011 Introduction to HTML5 Game Development talk. The original game was written in JavaScript, and as an exercise I just ported the game to Dart. This post contains some lessons learned from the process.

Background: The original JavaScript code for the game is open source, as is the new Dart code.

Note: The Dart version doesn't have sound working, as the Web Audio API isn't currently working in Dart.

Straight forward port

The executive summary of the porting process is: it's pretty dang easy. Dart had two primary design constraints: it must be familiar to JavaScript developers, and it must compile to JavaScript. Having just ported over 574 lines of JavaScript to 554 lines of Dart, I can say that those two design constraints are being satisfied quite well.

To be fair, I wrote the first JavaScript version with structured classes in mind (using JavaScript's prototype system) so it was easy to port to Dart's native classes.  For example, compare the following JavaScript class:

// old JavaScript code
function Timer() {
    this.gameTime = 0;
    this.maxStep = 0.05;
    this.wallLastTimestamp = 0;
}

Timer.prototype.tick = function() {
    var wallCurrent = Date.now();
    var wallDelta = (wallCurrent - this.wallLastTimestamp) / 1000;
    this.wallLastTimestamp = wallCurrent;
    
    var gameDelta = Math.min(wallDelta, this.maxStep);
    this.gameTime += gameDelta;
    return gameDelta;
}

to the new equivalent Dart class:

// new Dart code
class Timer {

  num gameTime = 0;
  static final num MAX_STEP = 0.05;
  num wallLastTimestamp = 0;
  
  num tick() {
    num wallCurrent = new Date.now().value;
    num wallDelta = (wallCurrent - wallLastTimestamp) / 1000;
    wallLastTimestamp = wallCurrent;
    
    num gameDelta = Math.min(wallDelta, MAX_STEP);
    gameTime += gameDelta;
    return gameDelta;
  }
}

I think the Dart class syntax is easier to read, and it's a welcome relief to be able to omit all of those this qualifiers that are required in JavaScript. Regardless, I think it's safe to say that a developer versed in one language can grok the other.

So what did I learn?

1) Types rule. I know there are a bunch of JavaScript developers that have never seen a static type, and don't want to touch them. To that I say, don't be too quick to judge. Types made the porting process much quicker, because my editor and compiler caught errors much earlier for me. Did I use types for everything? Nope, but I will be using types in Dart for most things.

2) The Dart editor is very handy. With it's ability to warn me of real and potential errors as I code, I'm able to code faster. I think web developers are going to have an epiphany when they realize what they've been missing: real-time analysis of their code as they write it is a real time saver and will result in better code.

3) Frog compiler generates very logical JavaScript. Dart code will run on modern browsers via JavaScript, and Frog is the Dart to JavaScript compiler that is currently recommended due to its terse and human readable output. The Frog compiler turned my Dart code back into what was essentially the same JavaScript code that I originally wrote.  For example, check out the following JavaScript code that came from Frog:

// this JavaScript code came from Frog
function Timer() {
  this.gameTime = 0
  this.wallLastTimestamp = 0
  // Initializers done
}
Timer.prototype.tick = function() {
  var wallCurrent = new DateImplementation.now$ctor().value;
  var wallDelta = (wallCurrent - this.wallLastTimestamp) / 1000;
  this.wallLastTimestamp = wallCurrent;
  var gameDelta = Math.min(wallDelta, 0.05/*Timer.MAX_STEP*/);
  this.gameTime += gameDelta;
  return gameDelta;
}

4) Dart doesn't let me get sloppy. Thanks to types, real classes, inheritance, modifiers like static and final, and real lexical scoping, my Dart code feels more robust. I'm not allowed to slack.

5) The api.dartlang.org docs are helpful. Thanks to the new API docs, and the Dart Editor, it was easy to find my way around the core libraries.

6) Math.abs() is now num.abs(). This makes more sense to me, I'm glad they did it. For example, instead of Math.abs(-4) you will write -4.abs().

7) Grab the number of milliseconds since the epoch with new Date.now().value. Slightly more verbose than JavaScript, but this might change as the whole core libraries are going to get some major love in the beginning of 2012.

8) Dart has one falsy value: false. This is such a blessing compared to JavaScript's six falsy values. This means I had to rewrite:

// JavaScript
if (something) {
  // do stuff
}

to:

// Dart
if (something != null) {
  // do stuff
}

9) Real lexical scoping means I can drop this from instance variables. I no longer needed to write this.foo in my objects, I could just reference foo.

10) console.log() is now print(). The new print() is more terse, and works both on the server via the VM and in the browser. I'll allow it!

Summary

If your JavaScript is half way logical, you'll probably find it very straight forward to convert to Dart. There are differences, but most of the work can be handled by some regular expressions and some search and replaces. The resulting code felt more stable, as types and the tools like the Dart Editor didn't allow me to slack.

Check out the resulting Dart code for a simple HTML5 game.
Post a Comment

Disclaimer

I'm probably required to say that the views expressed in this blog are my own, and do not necessarily reflect those of my employer. Also, except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the BSD License.