Reply to comment

Brutalising the Flex Compiler: Tighter Control of Resource Files and CSS

Recently I've been reworking our in-house build process and dealing with a specific requirement; notably that graphical and text assets (and layouts) be separated out in such a way that they are combined - dynamically - at compile time or at runtime.

I went back and forth through a couple of different ways of doing it, and then threw out a lot of code, realising that I could hijack some of the Flex SDK's features to do it for me.

This relies on two built-in features that seem to have been pretty much hacked into the Flex compiler by the Flex team. Don't get me wrong, they're useful features and they're probably how I'd have done it. They've made it possible for me to hijack them - but it does feel like a hack. :-)

These are Flex's support for external resource bundles (compiled from .properties files, normally used for localisation), and for external CSS modules (compiled from .css files).

Resource Bundles

If you haven't come across Flex's build in resource files before then you're probably not dealing with a multilingual environment. They are simple text files containing key/value pairs, looking like so:

helpTitle=Help!
helpBody=Here is some help text.

You can even embed assets, just as if you were working directly in MXML or AS3, like so:
helpTitle=Help!
helpBody=Here is some help text.
helpImage=Embed(source="someImage.swf")

For each locale (for example the locale en_GB, which would represent British English, or cy_GB, which would represent Welsh) you provide a different .properties file.

Just as is, they're a very easy way to specify a collection of tagged assets, so that's what I decided to use them for - to specify assets for our code modules.

Support is pretty good out-of-the-box if you're just doing simple localisation, but I needed a few extra features.

  • I wanted to be able to load arbitrary resource bundles at runtime or to include arbitrary resource bundles at compile time. Flex provides for runtime loading of bundles, but only supports a fixed list of bundles at compile time i.e. whatever's embedded in the source code from [ResourceBundle()] metadata.
  • If you're doing a lot of loading/unloading, there are issues with the order that bundles are loaded in
  • Flex doesn't handle name-collisions between bundles terribly well.
  • I wanted to be able to decide arbitrarily which bundles to include for any given locale, rather than all-or-nothing. As a case in point, we often provide all the assets in one language except for the help assets, which we provide in several.

So - what was the trick?

I went away to look at how the Flex compiler actually compiles .properties files, and then how those compiled resource bundles are accessed from within the Flex SDK.

The short answer is that Flex 'cheats'. Flex doesn't compile .properties files, in the same way as it doesn't actually compile .mxml files. What it does is to read in the .properties file and translate it into an ActionScript file and then compiles that.

Usefully, you can look at these ActionScript files by passing a parameter to the mxmlc compiler:
-keep-generated-actionscript=true
This will create a folder called generated inside the root folder of the compilation and store the translated .as files there.

From reading through that, I realised that .properties files when compiled to .as files are actually very straightforward; they're just classes with an Object inside which contains the key/value pairs from the .properties file. Any Embeds are handled in the same way as in normal classes.

So the file from above becomes:

package 
{
 
import mx.resources.ResourceBundle;
 
[ExcludeClass]
 
public class en_GB$test_properties extends ResourceBundle
{
    [Embed(_resolvedSource="/Users/ian/Documents/Flex Builder 3/Test/src/someImage.swf", original="image.jpg", source="/Users/ian/Documents/Flex Builder 3/Test/src/someImage.swf", _file="/Users/ian/Documents/Flex Builder 3/Test/src/test.properties", _line="3")]
    private static var _embed_properties_someImage_swf_1576788060:Class;
 
    public function en_GB$test_properties()
    {
   super("en_GB", "test");
    }
 
    override protected function getContent():Object
    {
        var content:Object =
        {
            "helpImage": _embed_properties_someImage_swf_1576788060,
            "helpTitle": "Help!",
            "helpBody": "Here is some help text."
        };
        return content;
    }
}

It's a pretty simple class. (If you compile a .properties file as standalone, you get a bunch of other supporting stuff to turn it into a loadable Flex module, but for my purposes I can ignore it.)

So, to cut a long story short, I wrote an Ant task which allows us to more carefully control how .properties files are turned into .as files and which includes a published 'data dictionary' allowing our runtime code to decide which resource bundles are loaded and when. It's pretty simple stuff, and mostly uses the Flex SDK at runtime without our own extensions, which is nice!

It's let us get past the name collision issues; let us control what gets compiled in at compile-time and generally made resource bundles more useful for us. And because they're presented to Flash as normal Flex resource bundles, for the most part we can use the built-in Flex API to access and control them.

CSS Modules

CSS modules have similar problems to resource bundles:

  • Name collisions are frequent.
  • It's difficult to control dynamically including .css files at compile-time.
  • It's difficult to control loading/unloading sets of CSS files unless you're using an external CSS module.

Once again, back to the Flex compiler - and wouldn't you know it, the same thing is true - a .css file isn't compiled at all, it's turned into an .as file and then compiled.

The format of the CSS module is more annoying - I won't detail it here, you can check it out for yourself using -keep-generated-actionscript=true - and looks almost hand-edited. Rather than handroll it, I've written an Ant task that cheats in its own right, using the Flex compiler to create the .as class (in the generated folder). Then I can compile the generated class into our project at compile time.

This additional step also lets me control the name of the resulting .as class, which means I can avoid name collisions (or at least make a better job of it than Flex does natively).

Because I use the Flex compiler to create the class in a first pass, the compiler thinks that the .css file is destined to become a .swf file, which means it adds in a bunch of other supporting code to make it into a Flex module. In fact, it becomes an instance of an IStyleModule which when it's constructed applies all the styles inside it, and when it's unload() method is called, removes all the styles inside it.

This gives us an added bonus - it means that by including the .css file at runtime using my method rather than using the Flex standard method we can control when the styles get applied, and can remove them all when needed using unload(). You just don't get that with styles included via the <mx:Style/> tag.

Compiler Options

A couple of useful compiler options which have helped in all this:

  • -keep-generated-actionscript=true - obviously. Try it out. See that .mxml is built in exactly the same way - converted to an .as file, then compiled.
  • -includes Class - this is how we include the generated classes dynamically at runtime without having to put references to it in the .as source code.

Conclusion

I should probably go away and detail the exact methods used and maybe open-source some code; but firstly it's all part of our build system and is difficult to separate out, secondly there's a bunch of other stuff in there that's irrelevant to most people, and thirdly I'm short on time. :-) Maybe at some point.

The main message to take away is: .properties, .css and .mxml files are just translated into .as files by the Flex compiler. You can look at them, and doing so helps you understand what's going on under the hood. You can fake them, you can alter them. By doing all this as part of your compile/build process, you can stick to using the built-in Flex APIs without having to write any special client-side code.

Reply

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <h1> <h2> <h3> <h4> <h5> <i> <tt> <b> <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <image>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. Beside the tag style "<foo>" it is also possible to use "[foo]".

More information about formatting options