Sponsored by

  • Intel
  • HP

netmagHow to

Develop modern web apps with Dart

HTML5 and modern web browsers are evolving, and building large apps with more features can be difficult. Google’s Seth Ladd introduces Dart: a project that aims to address these concerns

This article first appeared in issue 234 of .net magazine – the world's best-selling magazine for web designers and developers.

Dart is a new, open source project designed to help developers from many different backgrounds build awesome modern web apps. More than a language, the Dart project also includes core libraries, an editor, a virtual machine, a package manager and a compiler to JavaScript. It’s a structured approach to web development, with support for classes, libraries, and (optional) static type annotations. Dart compiles to JavaScript to run across modern browsers, including desktop and mobile.

The Dart project is getting ready to launch its M1 release, the first public milestone since the initial unveiling. There will be more updates leading up to the 1.0 release, but we believe M1 will be ready for library and application authors. Thanks in large part to community feedback, Dart is getting more robust and adding critical features. This article reviews the state of the project, shows off some interesting features, and helps you get started with Dart Editor.

Philosophy and motivation

The web platform is evolving fast, with browser teams and standards committees launching numerous exciting features. The HTML5 family of technologies includes WebGL for hardware-accelerated 3D, FileSystem API for managing files and directories, Web Audio API for multi-channel low latency audio, getUserMedia for webcam and microphone access – and more. Modern JavaScript engines, faster with every release, can drive some impressive apps.

Users demand beautiful, full-featured, useful apps with an emphasis on functionality and aesthetics. What an app does and how it does it is as important as SEO, navigation and other traditional web development concerns. The Dart team believes it should be easier for devs from many backgrounds to use all the new features of HTML5 to meet and exceed these new user expectations. It should be easier to find the structure in the code. It should be easier to refactor and maintain code. It should be easier to work with larger code bases and with larger teams. Web apps should start much faster.

Dart Editor’s welcome screen helps you get started with Dart smoothly thanks to some sample apps and links

Using a new language to build web apps isn’t a new idea. For example, Haml is a new way to write the markup for the app, Sass is a new way to write the styles for the app, and Dart is a new way to write the logic and behaviour for the app. All three compile down to the web’s native HTML, CSS and JavaScript runtimes, and enhance developer productivity and happiness.

The Dart team is motivated to build a great programming experience for devs who demand structured code, full-featured editors and a familiar language. We want to see more awesome apps launching on the modern web.

Browser support

Dart compiles to modern JavaScript (ES5+) and thus targets modern browsers such as IE9+, Firefox, Chrome, Safari and Opera. Mobile browsers like Safari Mobile and Chrome for Android are also supported. The Dart virtual machine is not yet inside Chrome, nor have other browsers expressed explicit support for embedding it at the time of writing, but that is not a blocker to Dart’s adoption. We expect the Dart VM to eventually become integrated with Chrome, but it’s imperative Dart compiles to JavaScript so it can be used across the modern web.

We believe developers should have a choice of technologies for building web apps, as long as they work across the majority of the web. It’s important Dart remains an open source technology for the open web, and not locked to a single browser.

Dart project components

Dart is more than just a language: it’s an entire platform. Here’s a list of components from the Dart project:

  • The Dart language is a familiar, object-oriented class-based language.
  • Core Dart libraries include classes and functions for collections, core types, and functionality for server (files, directories, HTTP) and client (HTML5) apps.
  • Extended Dart libraries include internationalisation, unit testing, argument parsing – and much more.
  • Improved browser programming libraries make DOM programming feel like Dart programming.
  • Dart Editor is a rich editor that helps you write, run and debug Dart apps.
  • The Dart virtual machine, or VM, runs Dart code natively. It can run on the command line or can be embedded into a web browser.
  • dart2js compiles Dart code into JavaScript for all modern browsers.
  • dart_analyzer is the static analyser, able to parse Dart code, and report on potential errors and warnings.
  • Dartium is a custom build of Chromium with an embedded Dart VM. Dartium can run Dart apps natively, without being first compiled to JavaScript.
  • Pub is the Dart package manager. Use pub to install third-party Dart libraries.

Dart is developed mainly by Google, with a growing list of external contributors, as an open source project. You can find these projects, and more, here and at our GitHub repo. The issue tracker and commit logs are open, so you can file bugs and feature requests, submit patches, and track the status of the project.

Dart’s code completion facility lists available methods and fields: handy when you’re working with unfamiliar libraries and APIs

Language

Dart was designed with two guiding principles: it must be easy to learn (most developers pick it up in an hour or two) and compile to logical and performant JavaScript. At a glance, the language looks quite similar to other mainstream languages, which is the goal. But dig deeper and you’ll see some cool features that are new for a mainstream language – we can’t cover the entire language here, so we’ll take a look at a few interesting features here.

Classes

Dart is a class-based object-oriented language, with single inheritance. In other words, it works more or less as you’d expect. Everything is an object in Dart, even numbers, Booleans and null. Here’s a simple example of a Point class:

// Pull in the math library that ships with the Dart SDK .
import 'dart:math', prefix: 'Math';
class Point {
final num x, y;
// A constructor, with shorthand for setting instance fields.
Point(this.x, this.y);
// A named constructor.
Point.origin() : x = 0, y = 0;
// A method.
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}

Dart code is organised into libraries. The dart:math library is imported into the code above with a prefix.

The above example declares two constructors. The first shows off nice ‘syntax sugar’ for the repetitive task of setting constructor parameters to instance fields. Specifically, Point(this.x, this.y) is equal to Point(x, y) : this.x = x, this.y = y;. The second constructor is named, specifically Point.origin(), for conveying more information at the call site.

Both constructors initialise the two instance fields, x and y. Note x and y are marked as final, which means values can’t be changed once set. We’ve designed an immutable Point, which means it can’t be changed once it is created.

All Dart programs start with a main() function. Here’s how you create new instances of Point:

main() {
var p1 = new Point.origin();
var p2 = new Point(10, 10);
print("The distance is ${p2.distanceTo(p1)}");
}

The print() function outputs to stdout for command-line apps, or console.log for web apps. Notice the use of string interpolation, which embeds the result of an expression directly into the string.

Just with this short example, you’ve seen library imports, classes, named constructors, methods, variables, final, instance fields, print, and string interpolation. The code is easy to read and understand without surprises.

Optional static types

The use of var leads me to perhaps Dart’s most interesting feature: optional static type annotations. Dart is an optionally typed language and the use of type annotations does not affect the runtime semantics of the code. You can choose to use static type annotations depending on the situation. To help explain, let’s look at the pros and cons of static types.

When just starting a project, in the early R&D phase, it’s important for the developer to feel free to experiment and play. Often, the design isn’t locked down and thoughts are only emerging. Rigid type systems, requiring a completely analysed and provably correct world, interfere with this phase. Instead, the developer wants to write code, try it, and refactor. Now is not the time to be beholden to ceremonial type checkers. Early R&D and prototyping is the perfect time to code without static type annotations.

You can automatically change variable names across code in the Dart Editor with the Rename Refactor

Once the design solidifies or the code enters maintenance mode, it’s time to get better feedback from tools and help more developers use the code. The tools, like static analysers and editors, can also parse these static type annotations and provide helpful code completion, refactorings, and even warnings when they detect something has gone potentially wrong. Adding static type annotations helps developers avoid common questions such as “what do I pass into this function?” or “what does this method return?” Maintenance mode, sharing code with a larger team, or using advanced tool features are the perfect times to code with static type annotations.

For example, during initial development, you may need to build a function that combines things. You might initially write:

combine(a, b) {
var combination = a + b;
return combination;
}

The code above is good for initial experimentation, but fellow developers won’t know what to pass into combination() or what to expect in return. As the design locks down and you get more comfortable, adding type annotations helps everyone understand your intent. The signature now becomes:

num combine(num a, num b) {
var combination = a + b;
return combination;
}

The tools now offer more information. For this reason, Dart style encourages you to use type annotations for the ‘surface area’ of your methods and functions.

Why use var inside of methods? The tools, like the editor, can often perform local type inference, obviating the need to annotate the variable yourself. In the above example, the editor knows a and b are numbers, and that nums + operator returns a num, so the developer needn’t repeat that information.

We think optional static types offer a more enjoyable, refreshing programming experience. JavaScript developers feel at home because methods can be untyped and they can use var. Developers familiar with more structured languages can use the type annotations more thoroughly.

Top-level functions

Not everything in Dart needs to be wrapped by a class. Functions are quite happy living up in the top level. This makes library design more pleasant, because you can put your utility functions directly inside a library without the baggage of a class full of static methods. For example:

makeLouder(String msg) => msg.toUpperCase();
main() {
print(makeLouder("hello, dart"));
}

In the above example, => expression; is shorthand for { return expression; } and is quite useful for callbacks and simple one-line functions.

Lexical scope

Dart has lexical scoping. The accessibility of variables and names is defined purely by the structure of the code. Here is a simple example:

class ShoutingButton {
final ButtonElement button;
ShoutingButton(this.button) {
button.on.click.add((e) => shout());
}
shout() {
print("YOU CLICKED THE BUTTON!!!!!");
}
}

Notice the click handler, set up in the constructor, is calling shout() from within the handler. Thanks to lexical scoping, shout() is called on this without the need to rebind callbacks.

Another cool example of lexical scope is lexical closures. The makeAdder function, below, returns a closure, wrapping the variable n.

makeAdder(num n) {
return (num i) => n + i;
}
main() {
var add2 = makeAdder(2);
print(add2(3)); // 5
}

Isolates

Even smartphones have multi-core CPUs, and Dart has a safer way to take advantage of all these cores. Instead of error-prone shared-state threads, Dart introduces isolates for shared-nothing isolated memory heaps that communicate by passing messages over ports. These isolates can run in separate threads or processes, thus achieving concurrency. (Note: Dart’s isolates are inspired in part by Erlang’s concurrency and actor model.)

Here is an example of creating a simple isolate that echoes messages.

import 'dart:isolate';
echo() {
port.receive((msg, SendPort replyTo) {
replyTo.send("I received: $msg");
});
}
main() {
// Spawn the echo function into a new isolate.
SendPort echoPort = spawnFunction(echo);
echoPort.call("Hello from main").then((replyMsg) {
print(replyMsg); // I received: Hello from main
});
}

Use spawnFunction() to create a new isolate from a function. The returned SendPort is used to send messages to the isolate. An isolate can listen for messages with port.receive(). Messages are copied before they are received by an isolate, ensuring that no state is shared between two isolates.

Debug browser apps from inside Dart Editor, which integrates with Dartium, the build of Chromium with the Dart VM

Factory constructors

The factory pattern is known by many developers. Often, the pattern must be expressed by frameworks; Dart builds it into the language itself as a factory constructor. Consider the case of a singleton class, where you want only one instance to exist. This is a detail you do not want clients to worry about.

class Singleton {
static Singleton _singleton;
factory Singleton() {
if (_singleton == null) {
_singleton = new Singleton._internal();
}
return _singleton;
}
Singleton._internal() {
// Initialize the object
}
}

Accessing the singleton is easy:

main() {
var s1 = new Singleton();
var s2 = new Singleton();
print(s1 === s2); // true, the objects are identical
}

The usage of factory constructors looks just like normal constructors, to hide implementation details. Consider the case of evolving a class into a singleton. You don’t want to force your clients to change their usage of your class because you changed an internal detail.

Method cascades

This feature is from Smalltalk, and is now available in Dart. For some APIs, like builders, it can be tedious to repeat variable names.

canvasContext.beginPath();
canvasContext.fillStyle = penColor;
canvasContext.arc(tx, ty, penWidth/2+2, 0, PI2, true);
canvasContext.fill();
canvasContext.moveTo(wx, wy);
canvasContext.strokeStyle = "black";
canvasContext.lineTo(tx, ty);
canvasContext.closePath();
canvasContext.stroke();

Use method cascades to reduce duplicate code. For example, the above code can be rewritten as:

canvasContext
..beginPath()
..fillStyle = penColor
..arc(tx, ty, penWidth/2+2, 0, PI2, true)
..fill()
..moveTo(wx, wy)
..strokeStyle = "black"
..lineTo(tx, ty)
..closePath()
..stroke();

An API needn’t be designed specifically for this feature, so it works in many cases. Method cascades resolve to expressions, so they compose better, too.

There are many more features of the Dart language, including, but not limited to, getters and setters, implicit interfaces, abstract methods, named constructors, operator overloading, optional parameters and libraries. We encourage you to learn more by reading the Dart Language Tour.

Programming the browser

Web developers know the DOM, because it is a language-neutral set of interfaces that is ubiquitous across web browsers. However, programming the DOM never feels very natural in any language. (The rise of jQuery is partly explained because it made the DOM feel like JavaScript.) The Dart project is building a Dart-friendly browser library called dart:html in order to help developers be more productive when building browser apps.

The code below is an example of common browser tasks, as programmed with dart:html.

import 'dart:html';
main() {
// Find the element with ID of 'button'.
var button = query('#button');
// Add a click handler.
button.on.click.add((e) => print('You clicked ${button.id}'));
// Create a new DIV.
var message = new DivElement();
// Set an ID.
message.id = 'confirmation';
// Add a CSS class.
message.classes.add('important');
// Set the contents.
message.text = 'Thanks for trying Dart!';
// Attach the message to the body.
document.body.elements.add(message);
}

Dart takes a fresh approach to browser programming by revisiting some old assumptions. For example, when was the last time you sent XML over XMLHttpRequest? We thought so. In an effort to simplify, we renamed XMLHttpRequest to HttpRequest.

Here is an example of how to use HttpRequest to fetch and parse JSON data:

import 'dart:html';
import 'dart:json';
void main() {
new HttpRequest.get("data.json", (req) {
Map data = JSON.parse(req.response);
});
}

You can read more about the dart:html library, or study more samples.

One of Dart’s sample apps is Solar – see the body of the article below for more details of how to run this simulated solar system

Dart Editor

Central to Dart’s promise is a great tooling experience. Dart Editor is built to give developers a familiar editing and debugging experience, complete with features like code completion, refactoring and more. (If you are a fan of IntelliJ or WebStorm, you can try JetBrains’ Dart plug-in. If you like Eclipse, you can try the Eclipse plug-in.)
 

Code completion

With code completion, the editor can provide a list of options for the developer. This is helpful when you’re working with an unfamiliar API or library.

Refactoring

The editor supports refactoring, which is the art of changing the structure of your code without changing the behaviour. Refactoring is a valuable technique when adapting code to new requirements or designs.

Debugging

A great debugging experience separates text editors from IDEs. Dart Editor integrates its debugger with Dartium, the build of Chromium with the Dart VM. You can set breakpoints, step through code, inspect variables and more.

Getting started

  • Download Dart Editor. It includes the Dart SDK and Dartium. The welcome screen has sample apps and links to get started.
  • Click the Solar sample app and a copy of Solar is added to your editor.
  • Click the green Run button to run the app. The editor launches Dartium, and you can see a simulated solar system!
  • Run Solar in a browser without a Dart VM. Right-click on solar.dart in the Files view, and select Run as JavaScript. This will compile the Dart app into JavaScript, and launch your default browser.
  • You’ve just run your first Dart app! Play around with the code for Solar; the editor makes a copy for you. Try changing the colours of the planets or the gravity factors.
Select Run as JavaScript when running a Dart app in a browser without a Dart VM: the app will compile into JS and launch in your default browser

Conclusion

Dart is a new open source effort to build a comprehensive developer experience for structured modern web apps. The language is familiar to devs from many backgrounds, yet has new, exciting features such as optional static types and isolates. The tools, like the editor, are meant to help scale up from simple scripts to complex apps. Perhaps most importantly, Dart compiles to JavaScript to run across the modern web.

Dart isn’t yet at a 1.0 release, but we’ve seen many cool libraries and apps from the community and think the M1 release will be ready for developers to start building libraries and apps. This article scratched the surface of the project; there’s a lot more to learn and explore, and we hope you check us out. We’re interested in feedback, so tell us what you think!

Seth Ladd is a web engineer and conference organiser.

Advert

Log in to Creative Bloq with your preferred social network to comment

OR

Log in with your Creative Bloq account

site stat collection