Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2008-454
$eventId
ID of event: wwdc2008
$eventContentId
ID of session without event part: 454
$eventShortId
Shortened ID of event: wwdc08
$year
Year of session: 2008
$extension
Extension of original filename: m4v
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2008] [Session 454] Improving R...

WWDC08 • Session 454

Improving Responsiveness in Websites and Web Applications

Essentials • 53:38

Great performance is an important component of any advanced website or web application, and is essential for content delivered to mobile devices like iPhone. Discover techniques and technologies for improving overall responsiveness. Learn how to minimize resource requests, streamline CSS, and use techniques such as image spriting to dramatically reduce page load time for your website or web application.

Speakers: Jeff Watkins, Ryan Orr

Unlisted on Apple Developer site

Downloads from Apple

SD Video (389.7 MB)

Transcript

This transcript was generated using Whisper, it may have transcription errors.

Good afternoon, everyone, and welcome to session 454 on optimizing websites. We've been-- We've been talking to a lot of you this week, and we've heard lots of stories about websites not loading as fast as you'd like, using more bandwidth than you probably want, and also that you're feeling a lot of pressure to add more interactive features to your websites and your web applications. And you really want to know how you can get the most performance out of the Safari browser and coincidentally every other browser out there.

Well, my name's Jeff Watkins, and I'm gonna be talking to you a little bit today about that, and with luck, maybe we'll answer all of your questions. If not, maybe we'll give you some food for thought. So, Today we're going to cover in depth the problems that are facing all of our websites. We've talked to a lot of you and you're facing the same problems that we are. We're also going to give you some really good strategies that you can use that you can take back to your offices and use in optimizing your websites as well.

And then we're going to run through a case study of what we're doing on the online store to put these strategies in place to really drive the performance of our site lower our load times, and decrease our bandwidth utilization. And then finally, we're gonna run through some interactivity issues, how you can improve the performance of your JavaScript so that you can add more and more rich, interactive features to your website.

So let's step into the Wayback Machine and zoom all the way back to 1999 and take a look at a web page that really would have required no optimization if we were loading it today. I'm speaking, of course, of the home page of the online store, circa 1999 or so, judging by the clamshell iBook. And it's no wonder this page would take almost no time for a browser to load. half dozen to a dozen images. There's no CSS. There's no JavaScript. It's really a pretty static, simple page. Load this in Safari today, and it loads in a heartbeat. Now, if we fast forward to today, this is the online store as of just prior to Monday, and it's a lot richer.

We're taking advantage of the separation of presentation and content and a lot more interactive features, some of which we'll actually see today. And the problem is that we have 13 CSS files 24 JavaScript files and a grand total of 80 images. Now that's a total assets of 117 assets on the page. Now you may be saying to yourself, so what? Who cares?

What really -- what penalty are we paying for having 117 assets on the page? And it turns out we're paying a pretty high penalty. If you were here yesterday and you got to see the debugging session for Safari, you're probably familiar with this already. This is the resource panel in the new Web Inspector. And what this is telling us, when we load the home page of the online store, is that it takes 12 seconds and 841K just to get the home page. I don't know about you, but I consider that unacceptable.

So let's dive into this a little bit and try to understand where those numbers are coming from. Why is it taking 12 seconds and good Lord, where's the 841K come from? So in order to do that, I'm going to ask you to bear with me a second and we're going to run through the anatomy of browser request. I know for a lot of you this is the sort of stuff you're already very familiar with. But there are two really important factors I want to talk about here. The first thing that the browser has to do is it has to open the socket to the server, obviously, otherwise it's not going to be able to get any data back and forth. Then it sends its request over and ultimately it gets some data from the server. Really, like I said, this is introductory stuff. But there are two things that often get overlooked here. The first is latency and the second is overhead.

Now, latency is is the time from when the browser makes its request and until it receives its first bite of data. That's really, really hard to reduce. You know, if you've got a data center here in Cupertino and your visitors are over in Oslo, the packet's got to get here somehow, and that takes time.

So on the store, we use an edge caching solution from Akamai, and that means that the cache is as close to you as we possibly can get it. So we see average latency of about 68 to 70 milliseconds. That's really great, but it's still 68 to 70 milliseconds. And for the home page, we see a total latency of 2.5 seconds.

Now, that's time that the browser, great browser that it is, is just sitting there idling. It's not doing anything. It's not loading fancy pings. It's not caching its JavaScript. It's doing nothing. So we We really want to drive that down a little bit more. And then the second big issue is overhead. And that's the number of bytes that the browser sends to the server and the server sends back to the browser that's used simply to negotiate the transmission, saying, hey, give me this file. Okay. Here's your file. Here's how big it is. Here's when it was last modified, so on and so forth. Now that varies by browser to browser and server to server, and we see that average somewhere between 500 and 1,000 bytes. if you have big cookies or other attributes that they're sending along, that could be even higher. On the home page of the store, we see a total of about 63K. Now, that doesn't improve our page at all. It doesn't make for a better experience for you when you come to the store. And I'd get rid of it if I could. So let's take a look at-- we're going to take a look at ways that we can do that.

This is all good. We've got 12 seconds. Fortunately, the resource inspector already includes latency. But it has no real way of -- or it doesn't today include overhead. So that boosts our total bandwidth usage up to 905K, which is just way too much. I mean, we're pushing a full megabyte here. But we've already seen that we're losing 2.5 seconds to latency. And if you're on a residential cable connection or T1 or anything like that, you should be able to download a full megabyte in four to five seconds or so. So I'm kind of curious where the other five seconds are going. And it turns out there are a number of roadblocks that stand between the browser and actually rendering your page, even if it's got all the data. Now, the first roadblock is that the browser won't render any elements until all the CSS is loaded. Now, that means that if you told it you need these CSS files to know what color and what shape and what background to use for the nodes, obviously, it wants to wait until that information is available so that it can render your page correctly the first time.

Secondly, elements may not render until they're fully closed. Now, depending on your browser, this can be either good or bad. And if you have like a highly position-dependent layout, so you've got floats with absolute positioning and all sorts of fun stuff, the browser may honestly not know where to put the node until it's all done laying out the node. Now, as we'll see, this gets exacerbated by the next problem, which is that the browser stops rendering while it's processing a script element. And the reason for this is really simple, and that's because in the old days, and to some degree still today, coders could call document.write and modify the file as it's being parsed. Now, naturally, if you're going to modify the file as it's parsing, the browser makes an optimization to say, hey, I'm not going to keep parsing because if you change the document, I'm going to have to rewind and reparse. Safari 4 is doing a great job of spinning off a secondary parser to keep parsing and keep loading your files as an optimization, but we really can't count on that from all browsers. And then finally, synchronous Ajax requests stop everything. I don't know if you've noticed on the online store, but as we're loading the page, you swap off to another tab because we brought your browser to a standstill while we're making all of our synchronous Ajax requests. Now, from our perspective, we could see that as an advantageous thing because maybe you've got Amazon open in your comparison shopping. Well, you can't buy it from Amazon while you're waiting for us to load. But similarly, you can't go to Gmail, you can't do pretty much anything while you're waiting for our page to load and ultimately we come to be seen as the culprit. So we don't want to be the culprit. Right? So let's take a look at how these things play on the store's home page because it turns out they're directly responsible for our total load time. We've got 13 CSS files, as I said. Well, every single one of them has to be fetched and parsed and the The entire CSS structure has to be rendered or put into memory before a single node in the body will be rendered. That's 13 files. Next, we've got two script elements with external sources. So we have to fetch those files, wait for them to load, parse them, execute them, and it turns out that those two script files fire off ten synchronous Ajax requests. So before we've even gotten to the body of the document, we've caused the browser to come to a complete and screeching halt 12 times.

I don't know about you, but I'm beginning to see where this 12 seconds is coming from. It doesn't get any better, sadly, once we get into the body. Because we have an additional four script elements with external sources. Okay. Again, four more files that have to be loaded, parsed, and executed.

Seven inline scripts. So this is great. We don't have to fetch anything. But we still have to parse it and we still have to execute it. And somewhere in those 11 scripts, we fire off seven more synchronous AJAX requests. So, before we've gotten to the end of the document, we've caused the browser to come to a halt another 18 times.

I'm actually surprised it only takes 12 seconds to load. And in fact, the truth is, I frequently see it at home take 30 to 40 seconds. So I'm a little unhappy about that. Well, what can we do about it? Turns out there's three things that we're gonna do about these problems. The first one is, is we're going to minimize the number of assets. We're going to take the JavaScript and the CSS and compact it down into single files.

Next, we're going to shrink the total asset size. So it's great that we have fewer assets going out. So we're paying less of a latency and overhead tax. But we're going to shrink that down so they download even faster. Then we're going to look at a couple of ways that we can reduce the script impact. So ways that we can either fool you into not noticing that it takes time for the scripts to execute, or do the work in the scripts at a later point so that the page renders faster. It turns out that minimizing the number of text assets is actually pretty easy. You know, we've all been there. We start off and we've got a couple of CSS files, maybe a couple of JavaScript files. files.

You hire a couple of guys to help out and work on the team. Suddenly you've got 10 CSS files because they're spread out over different parts of the site, maybe compartmentalized for different portions of the pages. Your JavaScript gets more interactive. Suddenly ten years goes by and you've got 13 CSS files and 24 JavaScript files. And you're thinking to yourself, oh, my God, this is just killing me. We're not saying to you that you should You take all of those files, put them back together in one file, and now you're going to work on like a 384k JavaScript file and everybody's going to have to tiptoe around in there.

No, really what you can do is you put together a script, and we'll see the details of that a little bit later, that at either build time or at page request time dynamically concatenates those files together. You vend out a single file, and as a result, you pay less of a latency tax, and you pay less of a total overhead tax. So the result is you're paying those taxes only once for JavaScript and once for CSS.

So that's a huge win right there. Turns out that images are not so easy, though. In fact, they're not so easy enough that we chose not to do them. But if you throw your mind back to the old school gaming days of perhaps, say, Donkey Kong, they didn't really do 3D animation or any of those sorts of things. In fact, they really didn't do animation per se. What they had was a big canvas of images with lots of little images within it, and they just simply rotated through those images, and it gave the appearance of animation. But we're going to take a page out of that playbook and use what's called image sprites to construct assets on the page. So we're going to start off with a single canvas, and we're going to have multiple images that we use on the page in that canvas. And the result is, because we're only serving one canvas image, we're going to lower our and we're gonna lower our overhead. So let's take a look at how that would work if we were to build just the breadcrumb bra, breadcrum-bra, on the online store's homepage.

Well, we're going to start off with our canvas, right? And we're going to pull in the left-hand edge, followed by the little home icon thingy, and then that weird triangle thing. slide all the way down to the end, and we're gonna pick up the divider bar, the shopping cart, and then the final right-hand edge. So we're good, we've got all the image assets that we're gonna need for the breadcrumb bar, except one, which is the actual background. Well, it turns out, because we use a horizontally repeating background for the background of the bar, it needs to take up an entire row within our canvas. There can't be anything to the left or the right of it, because that would just get repeated as it fills out the bar.

Now, image sprites are not a cure-all. They're really excellent if you're designing a brand new site or you're going through a full remodel where you're taking everything down to the bare metal, redesigning it, and starting again. They can be a little bit troublesome to work into an existing site. The problem is that, first of all, they have to be an exact fit. So if you have a background image, really all you're using here is a viewport onto that canvas. And you're moving the canvas such that the image that you're interested in appears within your node. Well, if your node is very big and the image that you're interested in is very small, well, the browser, except for Safari coming up, doesn't have any notion of clipping of the background, which I believe is a CSS3 property.

And as a result, all of the images next to your sprite will also appear in your node. So the solution for that is you have to leave white space in your canvas, which, depending on the size of your node, may kind of defeat the purpose of putting this image in the sprite. So that's why this may not be the sort of solution that you can do automatically, say, with like a build script. This is the sort of thing that almost has to be hand tuned. And finally, we already saw that repeating images pose a special problem. Fortunately, this is actually an easy one to solve. Basically what you do is you have a separate canvas for horizontally repeating images and another canvas for vertically repeating images. And those wind up being, in the case of horizontal, tall, thin images, obviously because they just repeat horizontally. And for vertically repeating, it's a nice wide but short canvas. So now we've got fewer images on the page. And what we really need to do next is make those images, fewer images, fewer assets on the page. What we need to do is now take those assets and just shrink them down. Make them take less time to actually load. Well, there's a good thing here. The assets don't need to be humanly readable. You and I all work, and I know you're better than this than I am, we comment our code.

We format it nicely. Well, it turns out that this CSS is exactly the same to the browser as that. Now, we don't want to work with this, but I'm perfectly happy serving this out to the browser. Similarly, this JavaScript is exactly the same to the browser as this JavaScript.

Could be a problem if you need to debug it, but that's an easy trick. You just swap out the file. So this will take our actual textual assets, strip out all the extraneous stuff, like comments and white space, and in the case of JavaScript, you know, some meaningful identifiers, and really reduce the size of the assets so they download a lot faster. Because they download faster, obviously the browser is going to be waiting less time and your page will render faster.

Now, I promise you we're going to take a look at two ways that we're going to reduce the impact of script execution. Now, in order to do that, we have to understand what the browser is doing with each script. The first step it has to do, obviously, if it's an external script, it has to fetch it. Well, we're good here, right? Because we've reduced the number of scripts that we're including on our pages to possibly one, possibly two, if you have to send out a separate script for IE. So this should be not so much of a problem.

Well, then the browser has to parse it. Now, short of sending a brand-new Mac Pro to everyone who visits your site, and I promise I'll come visit your site if you send me one, there's really not much you can do to accelerate parsing of JavaScript. You pretty much just have to deal with it. So fortunately, Safari is really fast at this, so we can kind of ignore this as well.

The last thing that we can affect is execution time. And there's two things we want to look at. The first is really kind of a trick. It's going to take advantage of the fact that it takes a moment for you to recognize that the content's even arrived. So we're recommending that you put your script content at the end of your pages. Seems like a really obvious thing, but by doing that, the browser gets to go entirely through your document.

parse it, render it, and then it gets to the end and it can hook up your script, your event handlers, maybe do some additional fetches, things like that. Now, many of you may be familiar with the DOM content loaded event. And if you are, you're probably also familiar with the fact that it has some challenges, especially regarding IE, which doesn't support it at all, and older versions of Safari, Even though there are ways to get around this in your libraries, they don't always work 100%. This on the other hand does work 100%. By the time you get here, your page is ready. All of your nodes are available.

So that's the trick. The other one is a different sort of thing. Imagine you're out last night boozing it up, having a good time. comes lunchtime today and you have no money. Well, fortunately you've got friends, right? And your buddy offers to pick up lunch. Excellent. So if he never asks you to pay him back, you're up a cool 20 bucks. So let's take that approach and use that with our JavaScript here. And I know that maybe, Okay. I guarantee you, you probably don't need Fibonacci numbers on your website, but it really is a good introductory sample sort of thing.

And it allows us to highlight this notion of IOU data. So what we're going to set up here is a cache of our Fibonacci numbers. And when we first call get Fibonacci at index, we're going to notice, hey, the cache of underscore first 100 Fibonacci's is null. So the JavaScript will then call calculate first 100 Fibonaccis, which does some math, and then returns the cache. We then vend out the Fibonacci that you're interested in from that cache.

Now, the great thing about this is if you never need a Fibonacci number, you never pay this penalty of actually calculating them. Obviously, this is a fictitious example, but if you need to calculate, say, a CRC or some other computationally intensive table, this would be an excellent thing to apply to it, because if it turns out that you don't need it, you never pay the cost.

So at this point, I'd like to bring up Ryan Orr, who's a UI engineer on the store, and I'd like to ask him to run us through in detail some of the things we're doing on the store to take these strategies, put them in place, and more importantly, give you some sense that A, you can do it too, and B, what sort of performance improvements that you'll see from this.

Hi, my name's Ryan Orr, and I'm a UI engineer on the online store. And Jeff spent the first part of this talk kind of going over some of the problems you all might be seeing on your various sites. And this talk was really bred out of the problems that we were seeing on the online store.

A few months ago, Jeff came to me and he said, look, we need to get the page weight under control. We need to reduce the asset size. We need to reduce the time it takes to load the site. So I want you to go back with the team and kind of figure out some solutions to how we can kind of manage this problem.

And so, you know, the team and I, we sat around and we said, well, you know, where do we start? There's so many areas that we could look at. Where do we begin? And so we decided that we would probably start looking at the source code. And immediately we saw a page that looked like this. Link element after link element after link element. You know, and I'm sure you all have pages that are like this. And as Jeff mentioned earlier, you know, we pay a cost for each one of these assets that we request, right? And so if we can just get these under control and kind of vend out a single asset, we might be well on our way to furthering this.

And so we decided, well, we should probably concatenate this stuff. How should we do this? Do we want to event this on the fly? What do we want to do? And so ultimately, we decided that we should probably do a prepackager, some sort of beginning phase where we could grab all this stuff up, push it together, and then do some other processing on the files. And so that's something that we did. This could be written in any sort of language. It could be written as a bash script. If you're a Ruby Python dev, you can use those languages. I chose Ruby because it's something that I'm very comfortable with, but anything really works.

So basically we have a simple YAML configuration file, very standard Ruby. There are YAML parsers for all various languages. But we have three headers in this YAML file. We have global, IE6, and IE7. And underneath each one of those headers, we have a list of CSS files, in the same order that you would have the link elements on the page that we just saw, so that you get rule overriding and inheritance and whatnot. And so we loop over each of the headers, and we grab all the CSS files. We grab the contents from the CSS file and stick it out in the root as one single file, global CSS, IE6, IE7. And we went and rendered the page, and immediately we noticed a big problem on the site. None of the images showed up.

And so we said, oh, what happened here? What went wrong? And to explain this, let me explain a little bit of back history. The online store is 10 years old. And that's 10 years of people coming in through the store and having their touch with it, deciding the directory structure and whatnot. And because of that, there are files all over the place. And people wrote the background image URL paths relative to the location of the CSS file. So when we looped over all the files, pulled all the CSS out and stuck it in the root, we never changed any of the paths. And so it referred to images that weren't there. And so we decided that we had to go through and rewrite these URLs. And so there was a fair bit of work to do that, but we were able to do that. And that solved our problem. And so the team and I, we took a step back and we said, how do we prevent ourselves from being bit by this again? What do we do? And well, it turns out we'd already done the work to grab the image URL. So we thought, why don't we just verify that these images actually exist? So we do that. We go out and we rewrite the URL, and we verify that that image is there. Maybe somebody forgot to check it in. And if that happens, it throws us an error. And we know to go look at it. Maybe the path was written wrong. And so we're able to verify that the asset actually exists. And so that worked out great for us. And we said, well, what else can we do here? This is a great phase to kind of push any of this processing into. And we say, well, it makes sense that we want to validate our CSS here, you know, look for syntax errors and things like that. And there's a widely accepted validator by the W3C called Jigsaw. It's at [email protected]. But it's got two big problems for us. The first is that it's a Web app. And so it doesn't work with our command line flow -- workflow that we were kind of going with. And the second is because it's a Web app, we have to send our code out to it. And I don't know about you guys, but there's no way that we could possibly send our code out to a web app, you know, that's proprietary Apple code. It would be a matter of, you know, an hour before the blogs are trying to interpret what the various class in the CSS is. So that really wasn't an option for us. And so we opted to write our own validator.

You know, it was a pretty simple process. It took a couple hours over a week to kind of get it done. And that's not really the important thing that I want to talk about, what writing a validator allowed us to do was to chain load in other development rules. And, you know, to kind of explain this and kind of give you an idea of something that you might be able to do with a step like this, we have a lot of people at the online store that, you know, are fixing bugs or fixing issues and they may not have a global view of all the CSS. And, you know, to their credit, they're trying to get their work done. And ULLI background colon red. And then that fixes their issue, right? But when you concatenate all the CSS and you pull it out and then that out site wide, it may turn all the ULLIs red on the site and cause more problems. And so what we did is in the validator, we wrote like dev rules. And essentially it's a starting comment that says, all rules from here to a termination comment should begin with a class rule namespace. And so we go through and when it hits that block, it verifies that all the rules below are actually namespaced. And if it fails that test, we throw in error and it lets us know that we should probably go look at this before we push out to production. And hopefully we won't push out something that will cause havoc on the site or cause havoc on a small piece of the site that you rarely go to.

So on the JavaScript side, we did something very similar. We checked the dependencies, and this allowed us to dynamically generate the list of JavaScript files that we concatenate out into the root. And then we validated with JavaScript Lint. And, you know, this saved us from, you know, small syntax bugs and making sure the local variables were initialized. Kind of the things I like to call dumb bugs, right?

These are the bugs that, you know, it's midnight and right before a code push and you're staring at it and you can't figure out why it won't work and it turns out that an E and an A was transposed or you forgot a semicolon, you know? And so these sort of things really help when it comes to those kind of small, dumb bugs. We noticed that our dumb bug count kind of dropped by 80% after kind of putting these practices in place. And so it was really good for us. And so, you know, this made up our phase one, right? We thought this is a great place to kind of do a baseline and then benchmark and see what kind of reduction we got. And so, you know, when we started, we started with 314K of JavaScript, 153K of CSS, And our request time latency was about 2,500 milliseconds.

And so after we put phase one into place, we saw a sharp drop in the latency. We went from 2,500 milliseconds down to 136 milliseconds. This was fantastic. This is a step in the right direction. This is the way we wanted to go. We basically lopped 2 and 1/2 seconds off of the request time. This is time where the browser's not really doing anything. The user can't see any data on the screen. And it doesn't really help the experience at all. And this is great. But it wasn't quite enough. We knew that the assets were still way too high, and we needed to get that under control. And so we started looking at the source. We said, we probably need to reduce this a bit. We need to compress the source.

And so Jeff mentioned earlier that the code doesn't necessarily need the white space and comments to be readable by the browser. It doesn't need to be human readable to be readable by the browser. We put that in for us. I certainly wouldn't want to work on source files that aren't commented or don't have proper formatting. And I know none of my coworkers would either. But during this packaging phase, we can strip all that stuff out. And so we elected to use the YUI Compressor by the Yahoo! UI team. It's a great utility. The guys have put a lot of work into it. It's very robust. It works with both CSS and JavaScript. And it works on the command line. So it works with our workflow.

And so it's a great tool. Just strips all the comments and whitespace out and puts everything on a single line. It's the minimal amount of code that the browser needs to render a page. Another thing that you can do is to rewrite your rules. There's this notion of-- or some CSS properties have a top, right, bottom, left variant to it. Things like margin and padding and border have these top, right, bottom, left components. But you can also write a shorthand version of that rule that looks something like this, where you have margin and then you pass in the value starting at the top and going clockwise. And the difference between the two rules is some number of bytes. And obviously, the shorthand version is smaller. Ultimately, we decided not to go this route because we weren't able to accurately QA our code before it was pushed out to production.

And so we didn't feel comfortable pushing out code that we weren't able to QA. But if you can fit this into your workflow and you're able to QA it and you feel comfortable and you need to eek a few more bytes out, this is definitely something to look at.

On the JavaScript side, we did something very similar. Again, like I said, we used the YUI compressor to remove the whitespace in the comments. And so we kind of compressed the JavaScript up real nice. But we do something a little bit different with the JavaScript. We also shorten the local variables. Now, you may have heard of this being called obfuscation, right? So there's a notion that, you know, variables can be somewhat long at times, right? You could have, "This is the name of a div." That would be, you know, the variable. That could very easily be the letter A.

And A is obviously a lot shorter than this is the name of a div. So we found that by using obfuscation, which is traditionally used to make your code a lot harder to read, we found that by repurposing the tool, we were able to get compression results out of it. And so we employed it in this case.

So that kind of made up our phase two. And we went back to the baseline, and we tested our phase two against what we'd already done. And we saw that the JavaScript was basically cut in half. We went from 314k of JavaScript down to 157k. And CSS went from 153k down to 128k. And this is great. It's a step in the right direction, but it's still not quite enough. We thought the sweet spot is really 100K for the CSS and JavaScript combined. That's what we were targeting. We thought that it would really -- it wouldn't cost the user a lot of time if they only had to download 100K. So that's what we shot for. And we had to look at other avenues. What do we look at next?

We've already spent a bunch of time on getting rid of all the requests and we already compressed the code down as much as we could. Where else can we eke a bit of performance? And so we said we should probably start looking at the vending mechanism. We should probably start looking at Apache. And modern browsers, they support gzip compression, right? Very efficient algorithm to kind of compress, take compressed data, decompress it on the client side so you can transfer this compressed data. And so we started taking a look at Apache and how we could use Apache to do this.

And it turns out Apache ships with two modules, moddeflate and modgzip. And the way I like to distinguish the two, moddeflate is really for assets that you want to compress and compress on the fly and then bend, and modgzip are for assets you want to pre-compress and stick up on the server. Now, at the online store, we have an edge caching solution, as Jeff mentioned. We use Akamai. An open source, a very similar open source caching solution would be, like, Squid. So it's, you know, something to take a look at. But because we have that caching solution in front of us, it periodically hits our web servers, and then we use ModDeflate to compress the assets, vend it up to Akamai, and then Akamai handles the job of vending that to the millions of users that hit the online store. So we don't really need to worry about server utilization, and that's why we use ModDeflate. If you didn't have a caching solution in front of your web servers, you might want to take a look at something like ModJSYS. And this would allow you to pre-compress all these assets, you know, probably during your packaging phase, and then stick them up on the server as part of your deployment. And then you could use mod rewrite to kind of redirect all the requests for static assets over to these gzip-compressed versions, and then vend those out.

So this was our phase three. So we put this into place, and we pushed this out to the QA server, and we tested it. And we saw that the sizes went through the floor. This had a huge impact for us. And so we went from 314k of JavaScript all the way down to 38k. And on the CSS, we went from 153k at the beginning down to 22k.

And if you remember in phase one, you know, our request time was 2,500 milliseconds. We had gotten that down to 136 milliseconds. You know, this is fantastic. We couldn't have been happier. We overshot our goal. Our goal was 100K. We were able to get the static assets down to 60K. And so what this allowed us to do now is we have room to kind of grow and to add interactivity and to add features and not have to worry about, you know, going over our target file size. And so to kind of underline the amount of reduction that we saw from beginning to end, I kind of want to take a look and give you some numbers here.

So from the start, from our start file size of the CSS to the end, we saw an 86% reduction with these techniques that I just outlined. With the JavaScript, we saw an 88% reduction. And the request time latency, we saw a 97% reduction. You know, we couldn't have been happier about this. And really what I want you guys to take away from this is that these are all really easy techniques, right?

None of this is rocket science. None of this is too hard to comprehend. And it's things that can be piecemealed together. You don't have to follow the way we did it. You can take small pieces and put it into your workflow as you see fit. And hopefully, you'll notice these large gains like we were seeing. And with that, I'd like to hand it back to Jeff Watkins.

course, the real challenge now is going to be how we top this. Because you know you never get to stand still, right? Now, I see a lot of you have your laptops open. And if you're looking at the source of the store, I have to confess you won't see this yet. But just keep looking. Takes us a little while to roll these things out of the QA and into production. So how many of you are responsible for adding animation and interactivity to your sites?

So we're going to talk about that a little bit. Once upon a time, you know, animation was sort of the province of marquee tags and blink tags and -- don't you miss those? Oddly enough, we just had someone use a marquee tag just recently, purely as an attention-getting thing. Hey, there's content missing here, but boy, did it get my attention.

So we all want to be adding more and more animation to our sites. And the trick is now it's not -- it's really not just a gimmicky thing. Now we're doing it as much to add context and feedback to our visitors. It's really important for us to provide a sense of how do I get back to that previous state?

Well, we recently rolled out a brand-new feature on the store, which maybe some of you have encountered already, which was an enhancement to our photo galleries on the product details pages. What we wanted to give our visitors was this rich, immersive experience of being able to zoom in, pan around, explore their products so that, you know, maybe they would be able to make the buying decision right now. Because the more we sell, the better off we all are, right? So we built this new feature, and as you can see here, kind of the standard product details page, the visitor's hovering his cursor over one of the thumbnails for the Nike iPod Sport integration kit. But when he goes ahead and clicks on that thumbnail, the page smoothly transforms into a photo gallery. We didn't want to have light boxes pop up, we didn't want to have pop-up windows, because we didn't want to get in the way of the experience. And we always wanted to have the option of you just exploring the page, scrolling down, reading the details, seeing how this product -- basically, we wanted to be all about the product rather than our fancy animation. So let's take a look at that again. And I want to point out some things that you should be looking at. Specifically, the product text just goes away. Right? Because we're going looking at the photographs for this product rather than reading the text about it. So bye-bye text. Next, it's always important for the visitors to know, here's -- is this a good product or is this maybe not so good a product? So we take the customer rating information, we move that over to the side so it's always visible so you never get lost and figure, hey, you know, this is not really a great product, but I'll buy it anyway.

And finally, to provide that context, to provide feedback to, here's how you get back to the descriptive portion of the site, we bring in a close box. So that the actual button for you to click to close the gallery just sort of fades in. So if you watch carefully, you'll see all of those things happen really smoothly.

And that's not just because this is a quick time animation of it. But what we're striving for here is 50 frames a second. Now, to put that in perspective, traditional film runs at about 24 to 25 frames a second. Television runs at about 30 frames per second. And new high-definition content is targeting about 60 frames a second. And the reason for that is it turns out as you add more pixels, Your eye gets better and better at detecting motion artifacts. So the more frames that you have per second, the less noticeable those artifacts are.

Well, we want to take advantage of that. So we needed to build a really robust way of doing this animation. But at the same time, we had to have it be really performant so that we didn't lag down any other animations that might be occurring on the site at the same time.

And we came up with two optimization techniques that we're going to talk to you about today. There are tons of optimization techniques for JavaScript. You can probably fill an entire session just with JavaScript optimization tricks. But we're going to talk about two. The first being sometimes it's necessary to leave the library. Now, JavaScript libraries are essential tools. I don't know really anybody who develops a sophisticated website without a library of any sort. And I'm certainly not up here telling you that libraries are terrible. No, they're actually really critical. They allow us to build much better sites in far less time. But they're developed for everyone. They're not specifically developed for you.

And as a result, there may be more in that library than you actually need, and that might actually be slowing down your code. We'll take a look at a really good example of that in a moment. And sometimes it helps to be really, really laser-focused and specific. And we'll see that that provides us with dramatic speed improvement.

Let's get to the code. So this is actually the core of that animation. This is the setup function. What we do-- and this is a really common technique-- is we set up our CSS as a inline, open, and zoomed mode. So we have separate class names for each one of those. And that way, as the CSS developer's working, it just sets the class. Everything pops into place. Everything's good. We can make sure everything's located correctly.

But what we do is before we apply the class in the animation, we find out all the properties of those nodes. Then we apply the class, find out their new properties, and then smoothly animate between them. Okay. Well, when we did this the first time, we encountered 50 nodes. Okay, not too bad. a little bit more nodes than we'd like. You know, we can't always use multiple background images like we do with Safari.

But we execute this twice. And that made our runtime 42 milliseconds. So to put this in perspective, if we're shooting for 50 frames a second, that means each frame is only 20 milliseconds. So we've dropped two frames on the floor. Now, realistically speaking, if we are getting 50 frames a second, you won't notice. But not everybody out there is running Safari on the latest Mac Pro. There are people out there running it on MacBooks. There are people running Safari on cubes. And there's even that poor guy out there running it on IE. So it's really, really critical that we do everything we can to speed this up because that IE guy, he's going to be seeing 200 milliseconds or more.

So he's going to drop way more than two frames. You know, he'll probably just notice it pop into place. So we want to do whatever we can to make that better. But it turns out there's not a lot of code here. Essentially, it's a tight loop with a call to element getStyle. So let's take a look at element getStyle and see what we can see. Now I don't mean to be picking on the prototype library because this is the source code for their getStyle function. I chose prototype because it is one of the most popular libraries out there, and I figured a lot of you would be very familiar with it.

It turns out, though, that as I mentioned earlier, this is written for everyone, including people who pass in IDs. We're not going to ever pass in an ID. We're always going to pass in an actual node. People who pass in CSS properties rather than the JavaScript equivalent of them. We're only ever going to pass in the JavaScript equivalent. So we can throw away a lot of this code. It's good code. But for our uses, we don't need it. So we're only gonna consider just the code that we're interested in.

So we see that we have one access to an inline style, and we do that for performance reasons. If the inline style is there, excellent. We return in right quick. If the value is null, then we call getComputedStyle to find out what the node's actual styles are, and then we access that computed style and return it for a grand total of three operations. Really good. I mean, if the prototype guys could optimize this even more, I guarantee you they already would have. But keep in mind, this is generic code. It's meant for everyone to use. So if we put this in place in our getStyles function, what we see is not so good performance. In addition to the call to element getStyle, which carries along with it three additional operations, we're doing that for every single property. Well, we animate 30 to 40 properties. So you can easily see that we're doing a tremendous number of operations per node, per loop through those nodes. So it's no wonder to me that it's taking a little bit longer than it should. So this brings us to our first point. Sometimes it's necessary to leave the library. So what we're gonna do is we're gonna rewrite this entirely from scratch to do the exact same thing, but do it a lot faster.

So here's the code. Not that much more complicated. But the first thing you'll notice is that I've hoisted getComputedStyle out of the loop. So we're not, I mean, the styles aren't gonna change within the execution of this function, especially since I'm not changing them. So I only need to call this function once. Then we still have our same call to inlineStyle and the computedStyle. No change there. But because I'm only doing two operations per property, I've got a 50% savings, right? That's gonna make a big difference and it surely does. So we see the execution time, just with this tweak alone, drop in half. Well, that's great. But remember, one frame is still 20 seconds. So we're still dropping a frame and that's not working out so well for the folks using IE. They're possibly even dropping more than one frame.

So there's more that we can do here. And it turns out that we need to be more specific. If you noticed, our animation is actually pretty simple. We're only moving elements. We're either bringing them in or fading them out. So we're not actually inspecting things like line height or background color or border width or any of those other properties. They might be totally appropriate for a different animation, but for this one, we don't need them.

So what we're going to do is we're going to rewrite this code just a little bit so that we can pass in an array of properties that we're truly interested in. And in this case, since we're only passing in six to eight properties, this function is going to run a lot faster. And it sure does. 50 nodes, two times, in only six milliseconds. So even on IE, we're now getting great performance.

Sure, we may not ever get 50 frames per second on IE, but at least we're not dropping any frames while we're setting everything up. So to recap, we started here at 42 milliseconds. And through just a little bit of hand tweaking, we arrived here at 6 milliseconds. Now my task would have been a whole lot easier if I were using the profiler that's in the new Safari. because that would have clearly identified right away element get style is part of the root of our problem. And half my effort would have been taken right away from me. But, We arrived at the same result nonetheless. And this really, like I'm telling you, this is not hard stuff, but the end result is a seven times speed improvement. Now, I'll do a fair amount of work to improve anything by seven times. And when it's a critical feature of the site, like the product gallery is, that's pretty awesome. So to recap, We talked about some of the problems that we're all seeing. You know, I'm sure nobody's site loads as quickly as you want it to.

Mine could never possibly load as fast as I wanted to. And we use more bandwidth than we'd like. You know, bandwidth is only going to get cheaper. That's true. But you still got to pay the bill. So the less you use, obviously, the lower that bill is going to be. We looked at the strategies that you can all use.

They're really simple. They're the sorts of things that all you got to do is find time in your schedules to apply them. And then we looked at how we're applying them on the online store. And finally, hey, we looked at ways to add even more interactivity to our websites and hopefully provide a richer, more immersive experience to our visitors.

So if you'd like some more information, please feel free to contact Vicki Murley or Mark Merlone. They're the evangelists for Safari and internet technologies, respectively. And do take a chance to look at the JavaScript Lint tool. It's really tremendous. I can't tell you the number of times it's caused, caught dumb bugs of mine, which would have caused tremendous problems. Probably the best thing it ever will do for you is tell you that you never declared a variable and you're promoting it then to global scope. So also the YUI compressor, say that 10 times fast. And this is a great tool. They've done an enormous amount of work on it as Ryan mentioned, and it'll really take your code, smoosh it down and reduce it quite a bit. If you didn't catch it yesterday, I'd really encourage you to go onto iTunes in a week or so, or maybe more, and download the debugging session. Tim and Adam did an awesome job of walking you through the new debugging features in Safari.