This is the sort of fantastically elegant solution, to a tricky problem, that we have come to expect from 280North and Cappuccino.
The killer feature here is that "you won’t have to change a single line of code". I love how seamless this is to implement! Oh, and that the images are sent as encoded text - pure awesome.
I believe the GWT thing requires you to specifically create a bundle subclass, and put all the image files in that bundle (as methods with or without annotations). Please correct me if I'm wrong.
You only need to create a sub-interface, and the annotations are only necessary if the image filenames cannot be inferred from the method names in that sub-interface.
It wasn't clear from the original article how Cappuccino handles this...do they automatically throw any image that happens to be in a magic directory into the bundle?
There are other details that are different, so I guess I shouldn't have said "exactly". ImageBundle will make a real PNG for you instead of Base64, for example. Although in 2.0 the new generalized ClientBundle facility (same concept, but you can also bundle CSS, XML, a PDF...) promises to use data: URLs, JSON notation, or other representations when its calculated to be size-appropriate.
The default jakefile we will ship just automatically bundles any image resource in the project.
ImageBundle sounds pretty solid. I guess they are inferring the orientation of an image somehow? I'd like to see how that works. If you use an image in multiple orientations, or if you need to stretch in both directions, using a single .png file just isn't possible, unless there's something I'm missing.
In the current implementation (ImageBundle) you would need to have separate copies for each orientation & scale. That's a really nice thing that the Cappuccino implementation brings. The new ClientBundle implementation promises to, at least, have the compiler flip an image left-to-right when appropriate for a user's locale:
@ImageOptions(flipRtl = true)
...and there's another hint for images that are intended to be tiled:
@ImageOptions(repeatStyle=RepeatStyle.Vertical)
There's no scaling yet, but the tiling annotation covers some of the cases where you might want scaling (such as the edges of a corner-rounded/drop-shadowed box of variable size).
GWT 2.0 ClientBundle will use Base64 for images because GWT will bake those images straight to the compiled JS, along with the CSS styles.
The CSS names would be obfuscated (and also shortened).
All these features are there to support code-splitting (splitting the one big giant compiled and obfuscated Javascript file with everything into several "chunks" of JS files to be sent on-demand).
Yes, that's true. I think, generally speaking, base64 encoded images will result in more bytes than the original. I guess it's probably around 10%-20%.
I think, in the case of spriting, it would be fair to say that the overhead of loading many images/http requests (and their individual headers) pretty much nullifies the effects of bloating from the encoding.
Don't forget that since the resultant file is sent over the wire gzipped, a lot of that is undone by the fact that you are gzipping all the images together instead of separately.
Base64 will inflate a list of bytes by 33%. For every three bytes that go in, 4 bytes are returned. Wikipedia has a very good explanation of why this is:
While we're on the subject, Ascii85 is kind of a cool alternative. By increasing the number of characters used for the encoded string and breaking a 32 bit integer apart in an interesting way, it is able to reduce the expansion down to 25%. For every 4 characters in, 5 come out. Again, more info is available on Wikipedia:
My base64 codec turns out to be quite similar to the Cappucino one; oddly, most of the javascript base64 codecs with decent google juice are actually horrible implementations.
I don't see this as a problem since this is for user interface elements. Pictures and larger graphics probably wouldn't be a part of this and would be handled separately.
Typical cappuccino apps are cached all at once with far future expires. Simply changing the URL (in an automated way) is enough to break the cache for new versions.
The caching tradeoff is there for any spriting technique, it will always be a tradeoff between reducing HTTP requests and increasing caching granularity.
Most browsers can handle two concurrent connections.
I wonder if you could create a single diff file for minor revisions. Clients would get both files in parallel as most are configured to allow two connections per host.
E.g.:
- index.html with in-line minor diff + data loader.
- data file with resource that is patched before extraction.
The right time to blow away the cache and produce a new monolithic data file would depend on your visitor profile. The nice part is that your changes could touch multiple files and still have the same overhead.
.......quick example over 10 days........
A user visits every day, 1 change per day, 1% of code modified per change:
- file re-consolidated/cached on every change, user downloads 1000%
- with diffs consolidated every 5 days, user downloads 220%
- best case conventional update, user downloads 109% (but probably a lot more)
Interesting idea. It's worth mentioning that one file in Cappuccino means one file per bundle, plus the index.html file and Objective-J.js.
A typical app has the Cappuccino bundle, and one bundle for the app code, but using an external framework means adding an additional bundle. We're exploring techniques to further concatenate bundles together (we have one in the project, but it has some negative side effects).
I'm curious about your concatenation technique. Dynamically creating multiple script objects (with document.createElement) from a data file and injecting them has been reasonably robust for me but I'm not writing a framework so I have far fewer test cases! :-)
Cappuccino has a completely custom loader and concatenated file format that creates a file system representation, such that when you do something akin to XHR("something/blah") it knows that it already downloaded it and gives it to you without a request, that way whether you ship concatenated code or not the code that fetches it is the same.
Someone asked a similar question in the comments and this is the reply he got:
The code is all part of Objective-J and the tools, which don't require the framework itself. You could always use those in your project without using Cappuccino.
The killer feature here is that "you won’t have to change a single line of code". I love how seamless this is to implement! Oh, and that the images are sent as encoded text - pure awesome.