Wednesday, January 11, 2012

Converting Complex Dart Apps to Javascript

‹prev | My Chain | next›

Last night I had me a good old time making a Fibonacci page driven by Dart. At its heart, the page was pretty simple (calculate the Fibonacci number for a list of numbers, display the results on the page, time the whole thing in the console). Simple, but making use of two cool Dart features: #import() and #source() to load libaries.

Since Dart is not available in any major browsers, I had to run all of this in my Dartium build. Until Dart is supported in all major browsers, Dart applications will have to be compiled into Javascript. That got me wondering as to how the compiler handles libraries.

Unfortunately, I cannot simply point the compiler, oddly named frogc, at the web page containing the main() Dart code:
➜  timer git:(master) ✗ frogc index.html                  
unrecognized flag: "index.html"
fatal: no script provided to compile
Unhandled exception:
Compilation failed
 0. Function: '::main' url: '/home/cstrom/local/dart-sdk/lib/frog/minfrogc.dart' line:32 col:5
 1. Function: '::main' url: '/home/cstrom/local/dart-sdk/bin/frogc.dart' line:11 col:16
Bummer.

So first up, I factor the main() code block out of the web page:
<html>
  <body>
    <h1>Fibonacci!</h1>
    <div id="status"></div>
  </body>

  <script>{}</script>
  <script type="application/dart" src="main.dart"></script>
</html>
Next, I double-check that this still works in Dartium:


Yup, I even get a warning about an incorrect mime-type for my new main.dart resource.

So now I ought to be able to compile the dart script into Javascript:
➜  timer git:(master) ✗ frogc main.dart       
/home/cstrom/local/dart-sdk/lib/htmlimpl/htmlimpl.dart:23094:21: warning: a map literal takes one type argument specifying the value type
    _listenerMap = {};
                    ^^^^^^
Again, I get an error deep within htmlimpl, but this time I know that it can be safely ignored. And indeed, I do have a brand-new, admittedly very large, main.dart.js file:
➜  timer git:(master) ✗ ls -ltrh
total 240K
-rw-rw-r-- 1 cstrom cstrom  342 2012-01-10 23:03 pretty_stopwatch.dart
-rw-rw-r-- 1 cstrom cstrom  227 2012-01-10 23:56 fib_printer.dart
-rw-rw-r-- 1 cstrom cstrom   66 2012-01-11 21:26 fib_printer.dart.js
-rw-rw-r-- 1 cstrom cstrom  240 2012-01-11 21:32 main.dart
-rw-rw-r-- 1 cstrom cstrom  169 2012-01-11 21:32 index.html
-rw-rw-r-- 1 cstrom cstrom 218K 2012-01-11 21:39 main.dart.js
The resultant Javascript version of my Dart code (which starts on line 4,266!) is actually quite readable:
//  ********** Library A very pretty stop watch class **************
// ********** Code for PrettyStopwatch **************
function PrettyStopwatch() {}
PrettyStopwatch.start$ctor = function() {
  // Initializers done
  this.timer = new StopwatchImplementation.start$ctor();
}
PrettyStopwatch.start$ctor.prototype = PrettyStopwatch.prototype;
PrettyStopwatch.prototype.stop = function() {
  this.timer.stop();
  dart_core_print("Elapsed time: " + this.timer.elapsedInMs() + "ms");
}
PrettyStopwatch.prototype.stop$0 = PrettyStopwatch.prototype.stop;
// ********** Code for top level **************
//  ********** Library main **************
// ********** Code for top level **************
function main() {
  var list = [5, 40, 39, 32, 6, 41];
  var timer = new PrettyStopwatch.start$ctor();
  list.forEach$1(fib_printer);
  timer.stop$0();
}
function fib_printer(i) {
  var answer = fib(i), el = ElementWrappingImplementation.ElementWrappingImplementation$tag$factory('div');
  el.set$innerHTML(("fib(" + i + ") = ") + answer);
  html_get$document().query('#status').get$nodes().add(el);
}
function fib(i) {
  if (i < 2) return i;
  return fib(i - 2) + fib(i - 1);
}
The other 4,265 lines of core are the core Dart and dart:html Javascript implementations. As for my code, I am thrilled to see that both the #import() of my library code and the #source() of my mixins are represented in the compiled Javascript. Without resorting to require.js hackery there is no way to import/source files in Javascript, so bundling it all up into a single file is probably the way to go. I must confess that I am pleased to see it already working this early on in the life-cycle of Dart.

Anyhow, a couple of other things to note are the #libary() argument in my Dart code:
#library('A very pretty stop watch class');

class PrettyStopwatch {
  // ...
}
Gets translated into comments in the compiled Javascript:
//  ********** Library A very pretty stop watch class **************
// ********** Code for PrettyStopwatch **************
function PrettyStopwatch() {}
//... 
When I wrote that, I could not see an immediate purpose for the argument to #libary(). Now I know at least one: quality documentation in compiled Javascript.

The other thing to note is that, in my Dart code, I #source() the functions to be mixed in before the main() code block:
#import('dart:html');
#import('pretty_stopwatch.dart');
#source('fib_printer.dart');
void main() {
  // ...
}
But in the resulting Javascript, the included functions, fib_printer and fib, are defined after the main() function:
// ********** Code for top level **************
//  ********** Library main **************
// ********** Code for top level **************
function main() {
  // ...
}
function fib_printer(i) {
  // ...
}
function fib(i) {
  // ...
}
I cannot think of a reason that this might really matter, but thought it interesting nonetheless.

Last of note is the difference between the Dart Fibonacci function:
fib(i) {
  if (i < 2) return i;
  return fib(i-2) + fib(i-1);
}
And the Javascript equivalent:
function fib(i) {
  if (i < 2) return i;
  return fib(i - 2) + fib(i - 1);
}
As I noted in my presentation last night to B'more on Rails, Dart definitely feels familiar!

The only thing left at this point is to run the app in a "normal" browser, which works brilliantly:

(see for yourself on Dart for Hipsters)


Nice!

Having futzed with Dart for a couple of weeks, I can see myself growing to like it enough to build real apps in it and then compiling to Javascript. Once frogc improves to the point that the resultant Javascript is smaller (e.g. minified, core dart is hosted at google, etc), this could be very nice.


Day #262

No comments:

Post a Comment