Dynamically Load Code with Dart

Rejoice! Dartium, a build of Chromium with the Dart VM, can now spawn a new isolate from a URI. This means your Dart apps have a new option for more modular code.

Isolates

In Dart, an isolate is an abstraction for an "isolated memory heap". Isolates do not share memory (variables, statics, etc), they are essentially self-contained applications. Isolates communicate by sending and receiving messages over ports.


Why Isolates Matter

Dart programs are static, in that their shape and structure do not change after they are compiled. A static program is great for optimizations like tree shaking (aka dead code elimination), type-inferencing compilers, and more.

However, modular apps often require a way to dynamically load code. Configurable and modular apps need a way to, at run time, pull in new functionality. If Dart apps have a static structure, how can they dynamically alter their behavior?

Isolates to the rescue! The structure inside of an isolate is static, but a Dart program is free to dynamically load other isolates. This means that libraries can be loaded into isolates at run time.

Your First Isolate

This simple reverser isolate listens on its port, receives messages, reverses them, and send the reversed text back to the sender.

// reverser.dart


import 'dart:isolate';

main() {
  port.receive((msg, SendPort replyTo) {
    var reverse = msg as String;
    var buffer = new StringBuffer();
    for (var i = reverse.length - 1; i >= 0; i--) {
      buffer.write(reverse[i]);
    }
    replyTo.send(buffer.toString());
  });
}

The main application (which itself is an isolate), is responsible for instantiating the reverser isolate. Use the spawnUri() function from dart:isolate to spawn a new isolate from some file.

// main.dart

SendPort reverser;

void main() {
  query("#sample_text_id")
    ..text = "Click me!"
    ..onClick.listen(reverseText);
  
  var serviceFile = 'reverser.dart';
  
  if (identical(1, 1.0)) {  // XXX: horrible hack to detect if we're in JS
    serviceFile = 'reverser.dart.js';
  }
  
  reverser = spawnUri(serviceFile);
}

Notice the horrible hack to detect if this code is running in the Dart VM or JavaScript. I don't know a better way to do it, but I don't recommend this.

When the text from #sample_text_id is clicked, the reverseText function is run:


void reverseText(MouseEvent event) {
  var text = query("#sample_text_id").text;
  reverser.call(text).then((reversed) {
    query("#sample_text_id").text = reversed;
  });
}

Notice how the reverser isolate is sent a message via call(), which returns a Future. When the Future completes, the callback registered with then() is run.

Support

Dartium supports spawnUri since at least version 28.0.1478.0 (194114). Dart2js compiles spawnUri() into a Web worker.

Note that each isolate is its own app, so dart2js must compile the same bootstrap code into each isolate file.

Popular posts from this blog

Lists and arrays in Dart

Converting Array to List in Scala

Null-aware operators in Dart