How to shrink the size of your Dart app when compiled to JavaScript

So you caught the Dart bug and you're enjoying using the structured language, comprehensive libraries, and productive tools. Great! Except, when you compiled your Dart app to JavaScript, the output was bigger than expected. Read this post to learn how to reduce the size of your JavaScript output. Small apps are fast apps!

Make sure you are minifying

The dart2js compiler can minify your JavaScript. However, minification is not the default. Make sure you opt-into minification before you deploy to production.

How can you tell if you minified? Take a look at the generated JavaScript and you'll know. Minified JavaScript replaces variables names, function names, and more with shorter names. It also moves code around to use less lines.

If you compile your app from the command line, use --minify:

dart2js --minify -o=app.dart.js app.dart

If you use pub build (a streamlined build process for pub package and apps), it defaults to minifying. Yay!

If you use Dart Editor, it's a bit more tricky. Follow the instructions I posted on StackOverflow for screenshots and a walk through. You need to add --minify to the Run config in Manage Launches.

You will see a significant reduction in size after you minify.

Make sure all dart:mirrors imports use @MirrorsUsed

Dart's reflection capabilities come from the mirrors library. Mirrors are really powerful, but they introduce challenges to dart2js's tree shaking abilities. Normally, dart2js can look at the entire app and deduce what code is required to run the app. However, as of the time of this writing, mirrors (due to their API design and highly dynamic nature) more-or-less disable dart2js's tree shaking.

Enter @MirrorsUsed, a metadata annotation, which explicitly states what parts of the program are reflected. Tools like dart2js can use @MirrorsUsed to re-enable tree shaking.

Note: by the time you read this article, @MirrorsUsed might be deprecated or removed. The engineers are working on better ways to deduce how mirrors are used in the code. As of the time of this writing, @MirrorsUsed is the tool we have to gain control over the output size when mirrors are used.

If your app imports dart:mirrors and you compile it with dart2js, you might see a warning like this:

sethladd:~/dart/test/web$ dart2js test.dart

test.dart:1:1: Hint: 6379 methods retained for use by dart:mirrors out of 7873 total methods (81%).

import 'dart:html';

mylibrary.dart:3:1: Info: This import is not annotated with @MirrorsUsed, which may lead to unnecessarily large generated code.

Try adding '@MirrorsUsed(...)' as described at https://goo.gl/Akrrog.

import 'dart:mirrors';

^^^^^^^^^^^^^^^^^^^^^^

If you see "This import is not annotated with @MirrorsUsed", then the generated JavaScript is probably too big. My sample app, which uses UUID, HTML, and mirrors, compiles out to > 4MB. Yikes. But we can fix it!

At the import of dart:mirrors, add a @MirrorsUsed and specify what the app reflects on. You can specify:

targets: one or more names of libraries or classes

metaTargets: one or more classes used as metadata to indicate reflectability

For example:

library mylib;

@MirrorsUsed(targets: 'mylib')

import'dart:mirrors';

All names and things (classes, functions, etc) inside of mylib will be retained for reflection.

From what I can tell, once dart2js sees a @MirrorsUsed, it then tree shakes and drops all targets that are not specified. So be sure to include a complete list of targets (libraries, classes, etc) that will be reflected.

If the import of dart:mirrors is not under your control (e.g. you use a package that imports a package that uses dart:mirrors), you can introduce your own @MirrorsUsed annotation to override. Just introduce your own import of dart:mirrors in code that you control (e.g. in your entry point file), add @MirrorsUsed, specify targets and metaTargets, and set override: '*'.

As mentioned above, Dart engineers are working on making mirrors + reflection + dart2js much friendlier. We know @MirrorsUsed is hard to get right, but it's the only tool we have right now at the time of this writing.

Make sure you are gzipping

Of course, your web server should perform real-time compression. Be sure to account for gzip when calculating how many bits are traveling across the wire.

Summary

To calculate the real size over the wire, minify your app, add @MirrorsUsed if you use mirrors, and enable HTTP compression like Gzip. If you have any questions, please ask on StackOverflow or file a bug at dartbug.com/new.

In which I port a snazzy little JavaScript audio web app to Dart, discover a bug, and high-five type annotations. Here's what I learned.

[As it says in the header of this blog, I'm a seasoned Dart developer. However, I certainly don't write Dart every day (I wish!). Don't interpret this post as "Hi, I'm new to Dart". Instead, interpret this post as "I'm applying what I've been documenting."]

This post analyzes two versions of the same app, both the original (JavaScript) version and the Dart version. The original version is a proxy for any small JavaScript app, there's nothing particularly special about the original version, which is why it made for a good example.

Warning: We expect the Dart libraries to undergo potentially sweeping changes before Dart goes to alpha. This document is relevant as of 2011-12-22.

Intro

Dart is a "batteries included" effort to help app developers build modern web apps. An important "battery" is the bundled core Dart libraries, providing common and rich functionality. Dart is building a solution for large, complex web apps, and providing well tested, integrated, and common libraries is key to helping a web app developer be more productive out of the box.

The Collection libraries are a crucial set of APIs that Dart developers get for free. Much more than simple arrays and maps, the Collection library includes standard ways to filter, iterate, inspect, compose, and sort your data. This post specifically looks at List<E>, Dart's ordered, indexable collection of objects.