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.

In my test app (uuid, html, mirrors), the generated JavaScript size dropped to 400k unminified (from 4MB unminified) once I added @MirrorsUsed.

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

Popular posts from this blog

Lists and arrays in Dart

Converting Array to List in Scala

Null-aware operators in Dart