Hi all,

Some of you have probably been wondering about my changes to the compiler
over the last year or more. I apologize again for occasionally breaking
things for short periods. It's been quite a challenge getting this stuff
working, but I'm excited to finally be able to report some real
improvements that pretty much anyone should be able to take advantage of
when building a Royale app.

First some background. A while back, Harbs asked me to look into ways of
reducing the file size of release builds. As you may know, we use Google's
Closure compiler to optimize our generated JavaScript. Closure can be very
aggressive in its optimizations, by renaming symbols (things like variable
and function names) and removing "dead code" that is detected as never
being called.

Closure's optimizations are good, but they also require developers to be
very careful about how they write their JavaScript code. When you enable
Closure's full optimizations, you are not allowed to use certain JavaScript
features because Closure cannot analyze them properly. For instance,
consider the following code:

var propName= "myProp";
var value = obj[propName];

When you dynamically access a property with a string, Closure cannot
reliably know that the property exists and will be accessed at runtime. It
may decide to rename or remove that property, which would break things at
runtime.

ActionScript supports many of the same restricted dynamic features too, so
if you want to support the entire AS3 language, we can't let Closure do its
full optimization. Luckily, Closure also provides a bit of a backdoor: it
allows you to "export" symbols, which means that they won't be renamed and
they won't be removed as dead code. Traditionally, we have made heavy use
of this exporting feature in Royale.

Harbs wanted to know if we absolutely needed to export everything that we
currently export, and if we could potentially allow developers to turn off
exporting entirely, as long as they follow the stricter rules required by
Closure.

I won't go into all of the details, but over the last several months, I've
been changing the compiler to give developers more control over release
builds. In particular, control over which symbols get exported, but also
the ability to block Closure from renaming symbols that haven't been
exported.

Now, for some of the results. I'm going to share the output file size of
the release build for several Royale projects with various different
compiler options.

For the example projects included with Royale, I built royale-asjs commit
94f12ed0e564b0b443834400dc2fc06d61b90a8a from October 26, 2020. If you want
to try building these examples yourself, the file sizes of release builds
may be slightly different, if you use a different commit.

SpectrumBrowser is a project developed by Harbs and his team. I used commit
d25a3def972b15ec029ae838f1a8a677d2d158bd from October 20 for the results
below. Repo: https://github.com/unhurdle/spectrum-royale/

To establish a baseline, I built all of these projects with the older
Royale 0.9.7 compiler first.

==========
Baseline: royale-compiler 0.9.7
==========

HelloWorld: 68 KB
ASDoc: 231 KB
TourDeJewel: 1074 KB
SpectrumBrowser: 900 KB

Again, I am building the same AS3/MXML code every time, but these first
numbers are from building with the older compiler. All apps build and run
successfully.

-----

The rest of the results are built with royale-compiler commit
df8bd9f686f1bbf89539e545377b2797c646172c from November 3.

All results below include the difference in KB and %. These values are
always in comparison to the baseline numbers above.

==========
Result 1: 0.9.8 default options
==========

HelloWorld: 84 KB (+10 KB / +24%)
ASDoc: 254 KB (+23 KB / +10%)
TourDeJewel: 1105 KB (+31 KB / +3%)
SpectrumBrowser: 936 KB (+36 KB / +4%)

These examples are slightly larger when built with the newer compiler.
That's expected. It's not ideal, but in the process of testing a multitude
of things to be sure that nothing had broken after my compiler changes, I
discovered some cases where exporting a symbol didn't actually work
correctly in 0.9.7! To properly fix the bug and export these symbols, there
was no choice but to make the file size a bit larger.

==========
Result 2: Disable export
==========

HelloWorld: 74 KB (+6 KB / +9%)
ASDoc: 227 KB (-4 KB / -2%)
TourDeJewel: 942 KB (-132 KB / -12%)
SpectrumBrowser: 882 KB (-18 KB / -2%)

In this round, I added the *-export-public-symbols=false* compiler option.
You may recall that I said earlier that I also modified the compiler to
allow a symbol not to be exported, but still prevent it from being renamed.
With that in mind, -export-public-symbols=false basically tells the
compiler that it still can't rename things, but it is allowed to remove
what it perceives as dead code.

HelloWorld is still slightly larger than 0.9.7, but the three other
examples are now slightly smaller than 0.9.7.

Most developers should be able to safely add -export-public-symbols=false
to their compiler options when building a Royale app. The only time that
you might still want this exporting is if you have external JavaScript in
your page that isn't part of your Royale app, but it needs to call
functions/classes in your Royale app.

==========
Result 3: Allow non-public things to be renamed
==========

HelloWorld: 72 KB (+4 KB / +6%)
ASDoc: 221 KB (-10 KB / -4%)
TourDeJewel: 918 KB (-156 KB / -15%)
SpectrumBrowser: 861 KB (-39 KB / -4%)

In this round, I used the following compiler options:

-export-public-symbols=false

*-prevent-rename-protected-symbols=false-prevent-rename-internal-symbols=false*

The two new options allow Closure compiler to rename protected and internal
symbols. Once again, HelloWorld is still slightly larger than 0.9.7, but
the other three examples have gotten smaller again.

While -prevent-rename-public-symbols=false exists too, we cannot use it.
The examples would not work correctly at runtime. This option would
probably work in a pure AS3 app, but our implementation of MXML in Royale
uses dynamic language features that Closure restricts. Unless that is
fixed, we need to avoid renaming certain public symbols.

Again, most developers should be able to add
-prevent-rename-protected-symbols=false
and -prevent-rename-internal-symbols=false to their Royale app's compiler
options. You might need to prevent renaming of protected/internal symbols
if you access them dynamically. However, in my experience, people are much
more likely to access public symbols dynamically.

-----

==========
Result 4: Allow public methods to be renamed
==========

HelloWorld: 64 KB (-4 KB / -6%)
ASDoc: 206 KB (-25 KB / -11%)
TourDeJewel: 881 KB (-193 KB / -18%)
SpectrumBrowser: 828 KB (-72 KB / -8%)

In this round, I used the following compiler options:

-export-public-symbols=false
-prevent-rename-protected-symbols=false
-prevent-rename-internal-symbols=false

*-prevent-rename-public-static-methods=false-prevent-rename-public-instance-methods=false
*

The two new options allow Closure to rename methods that are public. Now,
all four examples are smaller than 0.9.7, and the file size difference is
getting even more dramatic.

Once again, -prevent-rename-public-static-methods=false and
-prevent-rename-public-instance-methods=false should be safe for most
developers to enable when compiling their Royale app. In my experience,
calling methods dynamically is rare.

==========
More new compiler options
==========

There are some additional new compiler options available, but using them is
likely to break most Royale apps.

-prevent-rename-public-static-variables=false
-prevent-rename-public-instance-variables=false
-prevent-rename-public-static-accessors=false
-prevent-rename-public-instance-accessors=false

These options control whether Closure allows variables or accessors
(getters and setters) to be renamed. There are also similarly-named options
for protected and internal symbols, if you want more control over those
too, instead of using -prevent-rename-protected-symbols=false and
-prevent-rename-internal-symbols=false.

Unfortunately, renaming public variables/accessors is usually not possible
without breaking the app at runtime. In some apps, you might be able to
allow public static members to be renamed. However, in my experience,
binding to static constants is pretty common, and renaming breaks those
bindings.

==========
Next Steps
==========

Ideally, I'd like to make it possible for developers to be able to tell
Closure that it's allowed to rename all symbols, including public ones. I
believe that we could see even more file size savings in release builds if
Closure works with full optimizations for all symbols. Obviously,
ActionScript developers would be required to strictly follow Closure's
rules, if they opt into renaming of public symbols, but that's a choice
that they should be allowed to make.

As I mentioned above, our implementation of MXML and binding uses dynamic
access, which is not compatible with Closure's full optimizations. To
support those optimizations, I will need to explore changes to how we
generate JS for MXML, and how it gets parsed at runtime.

We previously discussed this subject a bit in this older thread from
January 2020:

https://lists.apache.org/thread.html/r843e55252e37967b71b1430a2a904719791d698f3e5e2a79de74e493%40%3Cdev.royale.apache.org%3E

At the time, I tried out some ideas that we came up with while
brainstorming, but all had various downsides that didn't make for an
obvious winner. In the end, I decided to set further investigation aside
and first focus on exporting/renaming stuff. Now, I'm ready to take a
second look with a fresh perspective, and maybe we'll have some new ideas
to try.

-----

That was really long, so thank you for reading, if you made it to the end!

TL;DR: By enabling certain, new compiler options, most Royale developers
can make their app release builds smaller. Additionally, I plan to keep
investigating, and I expect to find more ways to reduce file size in the
future.

--
Josh Tynjala
Bowler Hat LLC <https://bowlerhat.dev>

Reply via email to