Features

Features > Web Apps Features > JavaScript

The next generation of web apps make heavy use of JavaScript and CSS. We’ll show you how to make those apps responsive and quick.

With our so-called "Web 2.0" applications and their rich content and interaction, we expect our applications to increasingly make use of CSS and JavaScript. To make sure these applications are nice and snappy to use, we need to optimize the size and nature of content required to render the page, making sure we’re delivering the optimum experience. In practice, this means a combination of making our content as small and fast to download as possible, while avoiding unnecessarily refetching unmodified resources.

This is complicated a little by the nature of CSS and JavaScript resources. In contrast to image assets, CSS and JavaScript source code is very likely to change many times as time goes by. When these resources change, we need our clients to download them all over again, invalidating the version in their local cache (and any versions stored in other caches along the way). In this article, we’ll look at ways we can make the whole experience as fast as possible for our users - the initial page load, subsequent page loads and ongoing resource loading as the application evolves and content changes.

I believe strongly in making things as simple as possible for developers, so we’ll also be looking at ways we can set up our systems to automatically take care of these optimization issues for us. With a little up front work, we can get the best of both worlds - an environment that makes development easy with great end-user performance - all without changing the way we work.

Monolith

The old school of thought was that we could achieve optimal performance by combining multiple CSS and JavaScript files into fewer, larger blocks. Rather than having ten 5k JavaScript files, we combine them into a single 50k file. While the total size of the code is still the same, we avoid having the overhead associated with multiple HTTP requests. Each request has a setup and teardown phase on both the client and server, incurs request and response header size overhead, and resource overhead on the server side in the form of more processes or threads (and perhaps more CPU time for on-the-fly gzipped content).

The parellization aspect is also important. By default, both Internet Explorer and Mozilla/Firefox will only download two resources from a single domain at once when using persistent connections (as suggested in the HTTP 1.1 spec, section 8.1.4). This means that while we’re waiting to download those JavaScript files, 2 at a time, we’re not loading image assets - the page our users see during the loading phase will be missing its images.

However, there are a couple of downsides to this approach. By bundling all of our resources together, we force the user to download everything up front. By chunking content into multiple files we can spread out the cost of loading across several pages, amortizing the speed hit across a session (or avoiding some of the cost completely, depending on the path the user chooses). If we make the first page slow to speed up subsequent pages, we might find that we have more users who never wait around to request a second page.

The big downside to the single file approach has not often, historically, been considered. In an environment where we will have to often change our resources, any changes to a single-file system will require the client to re-download a copy of the entire CSS or JavaScript working set. If our application has a single monolithic 100k JavaScript source file, any tiny change to our code will force all clients to suck down the 100k all over again.

A splintered approach

The alternative approach lies somewhere in the middle - we split our CSS and JavaScript resources into multiple sub-files, while at the same time keeping that number functionally low. This compromise comes at a cost - we need to be able to develop applications with our code split out into logical chunks to increase development efficiency, while delivering merged files for performance. With a few additions to our build system (the set of tools which turn your development code into production code, ready for deployment), this needn’t be a compromise we have to make.

For an application environment with distinct development and production environments, you can use a few simple techniques to keep your code manageable. In your development environment, code can be split into many logical components to make separation clear. In Smarty (A PHP templating language) we can create a simple function to manage the loading of our JavaScript:

SMARTY:
{insert_js files="foo.js,bar.js,baz.js"}

PHP:
function smarty_insert_js($args){
  foreach (explode(',', $args['files']) as $file){

   echo "<script type=\"text/javascript\" src=\"/javascript/$file\"></script>\n";
  }
}

OUTPUT:
<script type="text/javascript" src="/javascript/foo.js"></script>
<script type="text/javascript" src="/javascript/bar.js"></script>
<script type="text/javascript" src="/javascript/baz.js"></script>

So far, so easy. But then we instruct our build process to merge certain files together into single resources. In our example, imagine we merged foo.js and bar.js into foobar.js, since they are nearly always loaded together. We can then record this fact in our application configuration and modify our template function to use this information.

SMARTY:
{insert_js files="foo.js,bar.js,baz.js"}

PHP:
# map of where we can find .js source files after the build process
# has merged as necessary

$GLOBALS['config']['js_source_map'] = array(
  'foo.js'	=> 'foobar.js',
  'bar.js'	=> 'foobar.js',
  'baz.js'	=> 'baz.js',
);

function smarty_insert_js($args){

  if ($GLOBALS['config']['is_dev_site']){

    $files = explode(',', $args['files']);
  }else{

    $files = array();

    foreach (explode(',', $args['files']) as $file){

      $files[$GLOBALS['config']['js_source_map'][$file]]++;
    }

    $files = array_keys($files);
  }

  foreach ($files as $file){

   echo "<script type=\"text/javascript\" src=\"/javascript/$file\"></script>\n";
  }
}

OUTPUT:
<script type="text/javascript" src="/javascript/foobar.js"></script>
<script type="text/javascript" src="/javascript/baz.js"></script>

The source code in our templates doesn’t need to change between development and production, but allows us to keep files separated while developing and merged in production. For bonus points, we can write our merging process in PHP and use the same configuration block to perform the merge process, allowing us to keep a single configuration file and avoid having to keep anything in sync. For super-bonus points, we could analyze the occurrence of scripts and style sheets together on pages we serve, to determine which files would be best to merge (files that nearly always appear together are good candidates for merging).

For CSS, a useful model to start from is that of a master and subsection relationship. A single master style sheet controls style across your entire application, while multiple sub-sheets control various distinct feature areas. In this way, most pages will load only two sheets, one of which is cached the first time any page is requested (the master sheet).

For small CSS and JavaScript resource sets, this approach may be slower for the first request than a single large resource, but if you keep the number of components low then you’ll probably find it’s actually faster, since the data size per page is much lower. The painful loading costs are spread out around different application areas, so the number of parallel loads is kept to a minimum while also keeping the resources-per-page size low.

Compression

When talk about asset compression, most people think immediately of mod_gzip. Beware, however - mod_gzip is actually evil, or at the least, a resource hogging nightmare. The idea behind it is simple - browsers request resources and send along a header to show what kind of content encodings they accept. It looks something like this:

Accept-Encoding: gzip,deflate

When a server encounters this header, it can then gzip or deflate (compress) the content it’s sending to the client, where the client will then decompress it. This burns CPU time on both the client and server, while reducing the amount of data transferred. All well and good. The way mod_gzip works, however, is to create a temporary file on disk in which to compress the source data, serve that file out, then delete it. For high volume systems, you very quickly become bound by disk IO. We can avoid this by using mod_deflate instead (Apache 2 only), which does all the compression in memory - sensible. For Apache 1 users, you can instead create a RAM disk and have mod_gzip writes its temporary files there - not quite as fast as pure in-memory compression, but not nearly as slow as writing to disk.

Even so, we can avoid the compression overhead completely by pre-compressing the relevant static resources and using mod_gzip to serve people the compressed version where appropriate. If we add this compression into our build process, it all happens transparently to us. The number of files that need compressing is typically quite low - we don’t compress images since we don’t gain much, if any, size benefit (since they’re already compressed) so we only need to compress our JavaScript and CSS (and any other uncompressed static content). Configuration options tell mod_gzip where to look for pre-compressed files.

mod_gzip_can_negotiate	Yes
mod_gzip_static_suffix	.gz
AddEncoding	gzip	.gz

Newer versions of mod_gzip (starting with version 1.3.26.1a) can pre-compress files for you automatically by adding a single extra configuration option. You’ll need to make sure that Apache has the correct permissions to create and overwrite the gzipped files for this to work.

mod_gzip_update_static	Yes

However, it’s not that simple. Certain versions of Netscape 4 (specifically 4.06 to 4.08) identify themselves as being able to interpret gzipped content (they send a header saying they do), but they cannot correctly decompress it. Most other versions of Netscape 4 have issues with loading compressed JavaScript and CSS in different and exciting ways. We need to detect these agents on the server side and make sure they get served an uncompressed version. This is fairly easy to work around, but Internet Explorer (versions 4 through 6) has some more interesting issues. When loading gzipped JavaScript, Internet Explorer will sometimes incorrectly decompress the resource, or halt compression halfway through, presenting half a file to the client. If you rely on your JavaScript working, you need to avoid sending gzipped content to Internet Explorer. In the cases where Internet Explorer does receive gzipped JavaScript correctly, some older 5.x versions won’t cache the file, regardless of it’s e-tag headers.

Since gzip compression of content is so problematic, we can instead turn our attention to compressing content without changing its format. There are many JavaScript compression scripts available, most of which use a regular expression driven rule set to reduce the size of JavaScript source. There are several things which can be done to make the source smaller - removing comments, collapsing whitespace, shortening privately scoped variable names and removing optional syntax.

Unfortunately, most of these scripts either obtain a fairly low compression rate, or are destructive under certain circumstances (or both). Without understanding the full parse tree, it’s difficult for a compressor to distinguish between a comment and what looks like a comment inside a quoted string. Adding closures to the mix, it’s not easy to find which variables have a private lexical scope using regular expressions, so some variable name shortening techniques will break certain kinds of closure code.

One compressor does avoid this fate - the Dojo Compressor (there’s a ready-to-use version here) works by using Rhino (Mozilla’s JavaScript engine implemented in Java) to build a parse tree, which it then reduces before serializing it to a file. The Dojo Compressor can give pretty good savings for a low cost - a single compression at build time. By building this compression into our build process, it all happens transparently for us. We can add as much whitespace and as many comments as we like to our JavaScript in our development environment, without worrying about bloating our production code.

Compared to JavaScript, CSS is relatively simple to compress. Because of a general lack of quoted strings (typically paths and font names) we can mangle the whitespace using regular expressions. In the cases where we do have quoted strings, we can nearly always collapse a whitespace sequence into a single space (since we don’t tend to find multiple spaces or tabs in URL paths or font names). A simple Perl script should be all we need:

#!/usr/bin/perl

my $data = '';
open F, $ARGV[0] or die "Can't open source file: $!";
$data .= $_ while <F>;
close F;

$data =~ s!\/\*(.*?)\*\/!!g;  # remove comments
$data =~ s!\s+! !g;           # collapse space
$data =~ s!\} !}\n!g;         # add line breaks
$data =~ s!\n$!!;             # remove last break
$data =~ s! \{ ! {!g;         # trim inside brackets
$data =~ s!; \}!}!g;          # trim inside brackets

print $data;

We can then feed individual CSS files through the script to compress them like so:

perl compress.pl site.source.css > site.compress.css

With these simple plaintext optimizations we can reduce the amount of data sent over the wire by as much as 50% (depending upon your coding style - it might be much less), which can translate to a much faster experience for our users. But what we’d really like to do is avoid users having to even request files unless completely necessary - and that’s where an intimate knowledge of HTTP caching comes in handy.

Caching is your friend

When a user agent requests a resource from a server for the first time, it caches the response to avoid making the same request in the future. How long it stores this response for is influenced by two factors - the agent configuration and any cache control response headers from the server. All browsers have subtly different configuration options and behaviors, but most will cache a given resource for at least the length of a session, unless explicitly told otherwise.

It’s quite likely you already send out anti-caching headers for dynamic content pages to avoid the browser caching pages which constantly change. In PHP, you can achieve this with a pair of function calls:

<?php
  header("Cache-Control: private");
  header("Cache-Control: no-cache", false);
?>

Sounds too easy? It is - some agents will ignore this header under certain circumstances. To really convince a browser not to cache a document, you’ll need to be a little more forceful:

<?php
# 'Expires' in the past
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");

# Always modified
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");

# HTTP/1.1
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);

# HTTP/1.0
header("Pragma: no-cache");
?>

This is fine for content we don’t want to be cached, but for content that doesn’t change with every request we want to encourage the browser to cache it aggressively. The “If-Modified-Since” request header allows us to get part of the way there. If a client sends an “If-Modified-Since” header with its request, Apache (or your web server of choice) can respond with status code 304 (”Not Modified”), telling the browser that its cached copy of the file is already up to date. With this mechanism, we can avoid sending the contents of a file to the browser, but we still incur the overhead of an HTTP request. Hmmm.

Similar to the if-modified-since mechanism are entity tags. Under Apache, each response for a static resource is given an “ETag” header containing a checksum generated from the file’s modified-time, size and inode number. A browser can then perform a HEAD request to check the e-tag for a resource without downloading it. E-tags suffer from the same problem as the if-modified-since mechanism - the client still needs to perform an HTTP request to determine the validity of the locally cached copy.

In addition, you need to be careful with if-modified-since and e-tags if you serve content from multiple servers. With two load-balanced web servers, a single resource could be requested from either server by a single agent - and could be requested from each at different times. This is great - it’s why we load balance. However, if the two servers generate different e-tags or modified dates for the same files, then browsers won’t be able to properly cache content. By default, e-tags are generated using the inode number of the file, which will vary from server to server. You can turn this off using a single Apache configuration option:

FileETag MTime Size

With this option, Apache will use only the modification time and file size to determine the e-tag. This, unfortunately, leads us to the other problem with e-tags, which can affect if-modified-since too (though not nearly as badly). Since the e-tag relies on the modified time of the file, we need those times to be in sync. If we’re pushing files to multiple web servers, there’s always a chance that the time at which the files are pushed are subtly different by a second or two. In this case, the e-tags generated by two servers will still be different. We could change the configuration to generate e-tags only from the file size, but this means that we’ll generate the same e-tag if we change a file’s contents without changing its size. Not ideal.

Caching is your best friend

The problem here is that we are approaching the issue from the wrong direction. These possible caching strategies all revolve around the client asking the server if its cached copy is fresh. If we could notify the client when we change a file, it would know that its own cached copy was fresh, until we told it otherwise. But the web doesn’t work that way - the client makes requests to the server.

But that’s not quite true - before fetching any JavaScript or CSS files, the client makes a request to the server for the page which will be loading those files via <script> or <link> tags. We can use the response from the server to notify the client of any changes in those resources. This is all a little cryptic, so let’s spell it out - if we change the filenames of JavaScript and CSS files when we change their contents, we can tell the client to cache every URL forever, since the content of any given URL will never change.

If we are sure that a given resource will never change, then we can send out some seriously aggressive caching headers. In PHP, we just need a couple of lines:

<?php
header("Expires: ".gmdate("D, d M Y H:i:s", time()+315360000)." GMT");
header("Cache-Control: max-age=315360000");
?>

Here we tell the browser that the content will expire in 10 years (there are 315,360,000 seconds in 10 years, more or less) and that it can keep it around for 10 years. Of course, we’re probably not serving our JavaScript and CSS via PHP - we’ll address that in a few moments.

Mistakes abound

Manually changing the filenames of resources when the contents are modified is a dangerous task. What happens if you rename the file, but not the templates pointing to it? What happens if you change some templates but not others? What happens if you change the templates but don’t rename the file? Most likely of all, what happens if you modify a resource but forget to rename it or change any references to it. In the best of these cases, users will not see the new content and be stuck with the old versions. In the worst case, no valid resource is found and your site stops working. This sounds like a dumb idea.

Luckily computers are really good at this sort of thing - dull repetitive tasks which need to be done exactly right, over and over again, when some kind of change occurs.

The first step in making this process as painless as possible is to realize that we don’t need to rename files at all. URLs we serve content from and where the content is located on disk don’t have to have anything to do with each other. Using Apache’s mod_rewrite we can create a simple rule to redirect certain URLs to certain files.

RewriteEngine on
RewriteRule ^/(.*\.)v[0-9.]+\.(css|js|gif|png|jpg)$	/$1$2	[L]

This rule matches any URL with one of the specified extensions which also contains a ‘version’ nugget. The rule then rewrites these URLs to a path without the version nugget. Some examples:

URL			   Path
/images/foo.v2.gif	-> /images/foo.gif
/css/main.v1.27.css	-> /css/main.css
/javascript/md5.v6.js	-> /javascript/md5.js

With this rule in-place, we can change the URL (by changing the version number) without changing where the file lives on disk. Because the URL has changed, the browser treats it as a different resource. For bonus points, you can combine this with the script grouping function from earlier to produce a list of versioned <script> tags as needed.

At this point, you might ask why we don’t just add a query string to the end of the resource - /css/main.css?v=4. According the letter of the HTTP caching specification, user agents should never cache URLs with query strings. While Internet Explorer and Firefox ignore this, Opera and Safari don’t - to make sure all user agents can cache your resources, we need to keep query strings out of their URLs.

Now that we can change our URLs without moving the file, it would be nice to be able to have the URLs updated automatically. In a small production environment (or a development environment, for people with large production environments), we can do this really easily using a template function. This example is for Smarty, but applies equally well to other templating engines.

SMARTY:
<link href="{version src='/css/group.css'}" rel="stylesheet" type="text/css" />

PHP:
function smarty_version($args){

  $stat = stat($GLOBALS['config']['site_root'].$args['src']);
  $version = $stat['mtime'];

  echo preg_replace('!\.([a-z]+?)$!', ".v$version.\$1", $args['src']);
}

OUTPUT:
<link href="/css/group.v1234567890.css" rel="stylesheet" type="text/css" />

For each linked resource, we determine the file’s location on disk, check its mtime (the date and time the file was last modified on disk) and insert that into the URL as the version number. This works great for low traffic sites (where stat operations are cheap) and for development environments, but it doesn’t scale well to high volume deployments - each call to stat requires a disk read.

The solution is fairly simple. In a large system we already have a version number for each resource, in the form of the source control revision number (you’re already using source control, right?). At the point when we go to build our site for deployment, we simply check the revision numbers of all of our resource files and write them to a static configuration file.

<?php
$GLOBALS['config']['resource_versions'] = array(

  '/images/foo.gif'    => '2.1',
  '/css/main.css'      => '1.27',
  '/javascript/md5.js' => '6.1.4',
);
?>

We can then modify our templating function to use these version numbers when we’re operating in production.

<?php
function smarty_version($args){

  if ($GLOBALS['config']['is_dev_site']){

    $stat = stat($GLOBALS['config']['site_root'].$args['src']);
    $version = $stat['mtime'];
  }else{
    $version = $GLOBALS['config']['resource_versions'][$args['src']];
  }

  echo preg_replace('!\.([a-z]+?)$!', ".v$version.\$1", $args['src']);
}
?>

In this way, we don’t need to rename any files, or even remember when we modify resources - the URL will be automatically changed everywhere whenever we push out a new revision - lovely. We’re almost where we want to be.

Bringing it all together

When we talked about sending very-long-period cache headers with our static resources earlier, we noted that since this content isn’t usually served through PHP, we can’t easily add the cache headers. We have a couple of obvious choices for dealing with this; inserting PHP into the process or letting Apache do the work.

Getting PHP to do our work for us is fairly simple. All we need to do is change the rewrite rule for the static files to be routed through a PHP script, then have the PHP script output headers before outputting the content of the requested resource.

Apache:
RewriteRule ^/(.*\.)v[0-9.]+\.(css|js|gif|png|jpg)$  /redir.php?path=$1$2  [L]

PHP:
header("Expires: ".gmdate("D, d M Y H:i:s", time()+315360000)." GMT");
header("Cache-Control: max-age=315360000");

# ignore paths with a '..'
if (preg_match('!\.\.!', $_GET[path])){ go_404(); }

# make sure our path starts with a known directory
if (!preg_match('!^(javascript|css|images)!', $_GET[path])){ go_404(); }

# does the file exist?
if (!file_exists($_GET[path])){ go_404(); }

# output a mediatype header
$ext = array_pop(explode('.', $_GET[path]));
switch ($ext){
  case 'css':
    header("Content-type: text/css");
    break;
  case 'js' :
    header("Content-type: text/javascript");
    break;
  case 'gif':
    header("Content-type: image/gif");
    break;
  case 'jpg':
    header("Content-type: image/jpeg");
    break;
  case 'png':
    header("Content-type: image/png");
    break;
  default:
    header("Content-type: text/plain");
}

# echo the file's contents
echo implode('', file($_GET[path]));

function go_404(){
  header("HTTP/1.0 404 File not found");
  exit;
}

While this works, it’s not a great solution. PHP demands more memory and execution time than if we did everything in Apache. In addition, we have to be careful to protect against exploits made possible by sending us doctored values for the path query parameter. To avoid all this headache, we can have Apache add the headers directly. The RewriteRule directive allows us to set environment variables when a rule is matched, while the Header directive lets us add headers only when a given environment variable is set. Combining these two directives, we can easily chain the rewrite rule together with the header settings.

RewriteEngine on
RewriteRule ^/(.*\.)v[0-9.]+\.(css|js|gif|png|jpg)$ /$1$2 [L,E=VERSIONED_FILE:1]

Header add "Expires" "Mon, 28 Jul 2014 23:30:00 GMT" env=VERSIONED_FILE
Header add "Cache-Control" "max-age=315360000" env=VERSIONED_FILE

Because of Apache’s order of execution, we need to add the RewriteRule line to the main configuration file (httpd.conf) and not a per-directory (.htaccess) configuration file, otherwise the Header lines get run first, before the environment variable gets set. The Header lines can either go in the main configuration file or in an .htaccess file - it makes no difference.

Skinning rabbits

By combining the above techniques, we can build a flexible development environment and a fast and performant production environment. Of course, this is far from the last word on speed. There are further techniques we could look at (separate serving of static content, multiple domain names for increased concurrency) and different ways of approaching the ones we’ve talked about (building an Apache filter to modify outgoing URLs in HTML source to add versioning information on the fly). Tell us about techniques and approaches that have worked well for you by leaving a comment.

Read more

Cal’s new book, Building Scalable Web Sites, contains more tips and tricks to help you develop and manage the next generation of web applications.

digg.com logo Like this article? Digg it!

Future of Web Apps Expo is back in London, 8-10 Oct, bringing you our fresh blend of amazing speakers, great advice and tons of networking potential. Use our special code FOWA-VIT to get a 15% discount!

223 Responses to “Serving JavaScript Fast”

  1. alexander says

    I have noticed several sites using something like:

    What technique is recommended for adding the unix timestamp, is it server-side reading the modification date of the file or something else?

  2. meneame.net says

    Cómo servir Javascript rápido…

    Las nuevas aplicaciones web 2.0 tienden a incrementar el uso de Javascript considerablemente. Uno de los ingenieros principales que desarrollan Flickr presenta en este artículo las claves para servir Javascript rápido….

  3. Carnet Web de Pascal says

    Servir du javascript rapidement…

    Un intéressant article de Cal Henderson (développeur de Flickr) :

    Serving JavaScript Fast

    Il y explique tout un tas de techniques côté serveur pour améliorer la rapidité d’un site dépendant beaucoup de javascript/css, rien de révolutionnaire…

  4. Robertas Aganauskas says

    Thanks Cal,
    that really helped a lot (especially the idea of elegant versioning files).

  5. Max says

    While these are great recommendations for an Apache/PHP environment, I would’ve liked to see more cross platform solutions, to include .NET/IIS as well. Great nevertheless.

  6. Stefan Hayden says

    According the letter of the HTTP caching specification, user agents should never cache URLs with query strings. While Internet Explorer and Firefox ignore this, Opera and Safari don’t

    I’m confused about which browsers you say do cache files with query strings? All the tests I’ve done show that in both IE and Firefox adding a query string does infact force the browser to re-download the css file. And that if the current query string matches the previous query string then the file stays cached.

    you are saying it’s Opera and Safari this does not work on?

  7. picture of Cal Henderson Cal Henderson says

    Stefan:

    No browser will treat URLs with different query strings as the same. “foo.css?v=1″ and “foo.css?v=2″ will always cause a refetch.

    However, Safari and Opera (most, if not all versions) will not cache any URL with a query string - it will treat two URLs with the same query string as if they were different URLs. If a first page requests “foo.css?v=1″ and a second page also requests “foo.css?v=1″, the first request will not be cached.

  8. Mandy Singh says

    Nice Article!

    Just want to know more about the gzip related problem. Is it seen mostly with .js & .css file on Internet Explorer?

    Would it be any better if gzip for .js & .css files was turned off (I know half the code is going to be in them) but at least the html pages would be served faster (never mind the includes which are anyway going to be cached).

    Let me know your thoughts.

    Thanks,
    Mandy.

  9. picture of Cal Henderson Cal Henderson says

    Mandy:

    It’s mostly seen with JavaScript and CSS, but can happen with HTML too. In this case it’s a lot more obvious to the user that something’s gone wrong so there’s a greater change they’ll reload and clear the issue themselves. With JavaScript, if a bad version of a file gets cached then the user may never realize it’s not working as expected.

  10. Pig Pen - Web Standards Compliant Web Design Blog » Blog Archive » Serving JavaScript Fast says

    […] Serving JavaScript Fast by Cal Henderson over on Vitamin. […]

  11. Keith says

    Thank you Cal, Great Article.

  12. AJAX道 » web应用加速技巧 says

    […] http://www.thinkvitamin.com/features/webapps/serving-javascript-fast 归类于: Javascript, 编程例子, PHP, Perl by — gaoliang @ 8:45 am […]

  13. Nicolai says

    - Why not use mod_negotiation for compression? That will allow you to use pre-compressed files and avoid per-request on-the-fly compression. You can automate this and use the most expensive/effective compression level (gzip -9 foo).

    - Separate static content, dynamic content, and database onto separate machines.

    - Serve static content from a machine with lots of RAM to allow OS- or server-level file caching. Check out a built-for-speed server like lighttpd.

    - Compile a stripped-down version of your Web server using only the modules you need.

    - For frequently-called, brain-dead utility work like “Bringing it all together”, avoid PHP like the plague. Write it in C using FastCGI.

    - For big PHP, use the Zend Optimizer. Check out the PHP5 function benchmarks at http://byster.net/?page_id=48

    - All the usual stuff: don’t follow symlinks, don’t use .htaccess files, don’t resolve client hostnames (move the logs to another machine and batch it later), etc..

    - If you use SSL, buy a dedicated hardware SSL device that offloads crypto processing from the servers to it.

    - When linking to directories, add the trailing slash. i.e. http://foo.com/bar/ rather than http://foo.com/bar. This avoids an HTTP redirect, which results in an unncessary HTTP transaction.

  14. Emergent Properties » Blog Archive » How To Speed Up Your Web App says

    […] read more | digg story […]

  15. Bizarro says

    No offense dude… I know that you work on Flickr but this is the most convoluted, bizarre set of recommendations I have ever seen. Is this really workable in real life? It seems like a complicated process that could break at any stage. Thanks for sharing the insanity though…

  16. Andy Kant says

    Great article, I will make sure to use your recommendations in the future. Thanks!

  17. ajaxDNA says

    […] A very good article from Cal Henderson of Flickr/Yahoo. […]

  18. Sam Minnee says

    Bizarro: The recommendations look like they’re very tightly bound to the author’s development workflow. So, while it may be byzantine to have to manually keep this stuff in check, if it fits into the way you normally go about building applications, then it’s all good.

    On the flip side, the author’s done a good job of explaining his motivations for all the pieces, so we can all go and make use of the same ideas in our own applications, though maybe in different ways.

  19. All Dugg » Blog Archive » How To Speed Up Your Web App says

    […] The next generation of web apps make heavy use of JavaScript and CSS. This article, by the lead developer of Flickr, shows you how to make those apps responsive and quick.read more | digg story Share and Enjoy:These icons link to social bookmarking sites where readers can share and discover new web pages. […]

  20. [[ the sirens of titan ]] » Blog Archive » links for 2006-05-23 says

    […] Vitamin Features » Serving JavaScript Fast js/css caching tips (tags: javascript optimization development webdev web2.0 ajax) Posted by geekfreak Filed in Bookmarks […]

  21. Kim says

    Using mod_rewrite to hide version numbers is a bad thing. mod_rewrite requires a lot of ressources.

    A better solution is using symlinks on the web server.

  22. Richard says

    I was looking at a simliar article yesterday, I guess this guy has copied a lot of content from this page.

    http://vivekjishtu.blogspot.com/2006/04/speed-up-your-ajax-based-webapps.html

  23. 26 Miles » Serving JavaScript Fast says

    […] Read more at Thinkvitamin.com. Posted by Matt |  May 23, 2006 | […]

  24. picture of Cal Henderson Cal Henderson says

    Nicolai:
    Why use mod_negotiation (which gets measureably slower with Multiviews turned on) when the latest version of mod_gzip does exactly that, but with the added ability to freshen pre-compressed files as needed? As for recommending writing the braindead part in C, if you read that section carefully I suggest you use mod_rewrite and mod_headers which are already written in C and well optimized. The rest of your recommendations, while great, are all server performance, which we’re not talking about here and is already very well covered elsewhere.

    Bizarro:
    Can you suggest any part of it that would actually break? It’s neither fragile nor complicated - we change URLs pointing to files. By buiilding it in at the template level it doesn’t require us to think about it at all once it’s been set up. It’s working every day for hundreds of millions of pages - not just Flickr, but many large applications. Akamai’s Edge Platform uses a very similar technique.

    Kim:
    It’s a myth that mod_rewrite is a really poor performer, but that’s besides the point - there’s a much greater issue with SymLinks. Ignoring the case of a development environment, imagine you had 10 JavaScript files and each of them (after some time) had been through one hundred revisions. Each time we deploy the app, we need to create 1000 symlinks. Depending on our the file system we’re using, seek time can increase linearly with the number of files per directory. The time to create all of these symlinks is non-trivial too. We could rely on only symlinking the current revision and letting a 404 handler clean up the rest, but that seems messy to me, compared to the mod_rewrite option.

  25. Alan says

    Very excellent article, particularly the attention paid to the realities of mod_gzip/mod_deflate and browser caching behavior when attaching query strings. Recently I’ve been fighting both battles at work, and it’ll be nice to have a clear, well written resource to point to.

  26. Nicolai says

    Sorry, but isn’t this whole thing about server performance?

    Why use mod_negotiation (which gets measureably slower with Multiviews turned on) when the latest version of mod_gzip does exactly that, but with the added ability to freshen pre-compressed files as needed?

    Sorry, I guess I didn’t read that carefully enough. That’s excellent news! Since there’s no need to use MultiViews in mod_negotiation to accomplish this, though, it may still be faster. Auto-freshen is a really nice feature from a developer standpoint, but it may slow it down a bit by doing a stat-and-compare for each request. I don’t know for sure, though, I’ll look at the source and see exactly what it’s doing.

    As for recommending writing the braindead part in C, if you read that section carefully I suggest you use mod_rewrite and mod_headers which are already written in C and well optimized.

    I agree, that’s the way to go! But if you can’t run Apache or an httpd that can do this natively in-process, C+FastCGI is a much more efficient alternative than PHP.

    The Header lines can either go in the main configuration file or in an .htaccess file - it makes no difference.

    While this is true in terms of the resulting functionality, it’s misleading in terms of performance. Using .htaccess files requires Apache to look for, open, read, close, parse, and compile it for every single request, which results in a significant speed penalty on a loaded server. (As you said, “This works great for low traffic sites (where stat operations are cheap) and for development environments, but it doesn’t scale well to high volume deployments - each call to stat requires a disk read.”–then add a bunch of work on top of that.)

    It’s a myth that mod_rewrite is a really poor performer…

    It doesn’t have to be, but it certainly can. Any use of regular expressions is going to cause a bit of a hit–pattern matching is not exactly a speedy operation. But you can make it much, much worse by writing your regular expressions badly. Use greedy operators (*) or specify more of the pattern than you need to and watch the performance plummet!

  27. picture of Cal Henderson Cal Henderson says

    Nicolai:
    All excellent points. When I said this wasn’t about server performance, I meant that we’re looking at client performance as the main goal. Adding 10ms on the server isn’t noticeable, but avoiding entire requests makes a big impact for the client. Unless you’re serving a lot of pages, server optimization can be a secondary activity. Even for small traffic applications, making some of the changes I talked about will make the user experience faster.

    But yes, avoiding .htaccess files can give you a good speed boost, especially for deeply-nested webroots.

  28. Slashe says

    Great article, I’ve just implemented some of the ideas and I’m already seeing a difference.

    Note: I think your perl script is missing some slashes.

  29. jon says

    how about setting a cache expiry date for the file, and then on that date sending/updating the file (most probably the .js) ?

    if this is workable….wouldn’t this be the ideal solution ?

  30. links for 2006-05-24 at leron’s crib says

    […] Vitamin Features » Serving JavaScript Fast via digg, tips for js & css performance optimization (tags: ajax compression css development performance php programming web2.0) […]

  31. Mike D. says

    Pardon me if I’m missing something (I probably am), but I’ve tested query string caching in Safari plenty of times and for me, it appears to cache just fine. Tell me if there’s a flaw in the test here… there very well may be:

    1. Open Activity Window.
    2. Load fresh page with query-stringed JS file included in it. JS file comes from server so its size is reported as 5k (indication that it is gzipped).
    3. Jump to other pages on the site which include that same JS file. JS file size is reported as 20k (indication that it is *not* gzipped).

    Doesn’t this mean the file is now coming from the cache and not the server? Any hitting of the Reload button pulls a new file and reports the size as 5k. Is this just a peculiarity with the Activity Window or is it indeed indicating caching?

  32. Mike D. says

    Some further investigation seems to reveal that indeed Safari and Opera have no problem caching URLs with query strings:

    “All of them will cache responses from URIs that contain question marks, as long as there’s freshness information present.”

    http://www.mnot.net/blog/2006/05/11/browser_caching

    Am I misreading the problem here?

  33. Lynred says

    Would be great to see actual benchmark tests - to see the difference in performance

  34. Perrin Harkins says

    I’m skeptical about the value of jamming several JavaScript files together. If anyone has some benchmark numbers, I’d like to see them.

    This presentation by Michael Radwin of Yahoo covers similar ground with caching and never expiring static content, although he thinks If-modified-since is fine for JavaScript and CSS:
    http://public.yahoo.com/~radwin/talks/http-caching.htm

  35. Jonathan Feinberg says

    You misunderstand the “private” cache-control value. “Private” does not request that the browser not cache; it forces proxy caches not to cache. In fact, browsers should cache documents with a private cache-control directive, in the absence of other factors (such as a modified etag, modified date, or expiration).

  36. picture of Cal Henderson Cal Henderson says

    Jonathan:
    Yes, there was a missing line there - ‘no-cache’ is needed to tell browsers not to cache, while ‘private’ is needed to prod some older proxy-caches not to cache the content in-flight.

    Mike D:
    Dependant on version number. Our internal testing was on older versions of both Safari and Opera.

    Perrin:
    While an I-M-S works fine and is faster than serving content, it still inccurs request overhead. That presentation is two years old and alot has happened in the last two years - such as applications with megabytes of JavaScript, spread across many files, becoming more common.

  37. zean.no-ip.info » Serving JavaScript Fast says

    […] http://www.thinkvitamin.com/features/webapps/serving-javascript-fast   […]

  38. BarelyBlogging » Blog Archive » links for 2006-05-25 says

    […] Serving JavaScript Fast Some interesting ideas for speeding up your javascript. (tags: javascript) […]

  39. thund3rbox » Blog Archive » links for 2006-05-25 says

    […] Vitamin Features » Serving JavaScript Fast (tags: javascript ajax) […]

  40. DHTML Guy says

    Found this from Reddit - not Digg - This is definitely a Bookmarker - will show this to many other developers

  41. nonsmokingarea.com says

    dev: compressing javascript & css…

    thinkvitamin.com features a very detailed article by Cal Henderson (flickr.com) on how-to serve large javascript- and css-libraries in performance-critical environments. Cal describes best practices for splitting, compressing and caching of these code-…

  42. Richard@Home » Blog Archive » links for 2006-05-24 says

    […] Vitamin Features » Serving JavaScript Fast Some useful tips for speeding up Javascript and CSS delivery (tags: javascript css optimization) […]

  43. Moe Aboulkheir says

    I don’t think the regex “^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$” does what you expect it to. It’ll rewrite “foo.v2.jpg” as “foo.jpg”, which is fine, but will also turn “gorbachev2.jpg” into “gorbachejpg”. If you turn the first capture group into “(.*\.)”, then it should work.

  44. picture of Cal Henderson Cal Henderson says

    Moe:
    It turns out several back-slashes are missing in the code snippets - It seems that wordpress has removed them for some reason (they’re there when i edit the article). I’ll see if we can get that fixed asap :)

  45. Lynred says

    what about mod_deflate for apache 2.0?

  46. grumpY! says

    we know that serving is a very small part of the process of displaying a page on the user’s browser, like 2% of the total time. so i would argue against using lighttp, or obsessing too much over common sense compression. better to open your wallet and get you content into an edge network, put your servers behind a hardware load balancer, and keep your pages simple. i am not sure why people think lighttpd will buy them much, i see little value in sacrificing useful apache features to get a microsecond off the server load. as for the suggestion to wirte C code for certain processes, i strongly disagree. your pages will take much longer to load when your webserver is coredumping, and it will.

  47. Who Cares? » How To Speed Up Your Web App says

    […] The next generation of web apps make heavy use of JavaScript and CSS. This article, by the lead developer of Flickr, shows you how to make those apps responsive and quick. Link […]

  48. 94smart’s Blog » 接着讲Flickr的八卦 says

    […] 前两天,顺着DBA的Flickr 的开发者的 Web 应用优化技巧,找到了Cal Henderson写的Serving JavaScript Fast,又让我窥探到Flickr的些些技术内幕,Flickr用的模板居然是SMARTY,这个之前普遍被人诟病的模板系统。 […]

  49. Weekly Highlights at mattwalters.net says

    […] Vitamin Features » Serving JavaScript Fast - Nice write-up from a Flickr developer on speeding up web applications that rely on JavaScript/CSS […]

  50. Pig Pen - Web Standards Compliant Web Design Blog » Blog Archive » Serving JavaScript Fast says

    […] Serving JavaScript Fast by Cal Henderson. […]

  51. David Levy (( Creativity Matters » Blog Archive » Serving JavaScript Fast says

    […] Vitamin Features » Serving JavaScript Fast […]

  52. SitePoint Blogs » Reducing HTTP Requests: An idea for a plugin says

    […] Cal Henderson from Flickr recently posted Serving Javascript Fast, where he talks about the approach they use at Flickr to reduce the number of HTTP requests per page and efficiently propagating new changes to assets. […]

  53. SitePoint Blogs says

    Reducing HTTP Requests: An idea for a plugin…

    The other problem Cal addressed, reducing the number of HTTP requests, is not something we get for free in Rails. In fact, the default setup for a Rails application that uses script.aculo.us has 4 javascript files. These four files don’t often change (…

  54. Fiftyfoureleven.com Web Development Weblog says

    Reducing HTTP Requests…

    The idea of reducing HTTP requests to optimize page download speed is something that I have discussed a few times on Fiftyfoureleven. Recenlty a couple of articles have been published on the topic, and I thought I’d revisit some of the ideas presented…

  55. penk - Keep on rockin’ in the free world » Blog Archive » 本日書籤 says

    […] http://www.thinkvitamin.com/features/webapps/serving-javascript-fast […]

  56. Massage Therapy Products says

    Thanks for the article. It is incredibly informative.

    Our company is nearing completion on a new Zen-Cart (PHP/Apache) site, and I know I’m going to have to go in and hack on it a bit, so I’ll keep these tips in mind (and probably reference some of them) when I finally start digging around in there.

  57. Ajaxian » Serving JavaScript Fast says

    […] Cal Henderson brings us this new (lengthy) article on ThinkVitamin.com, a look at speeding up the one thing that’s really growing large on Web 2.0-type applications - the Javascript running behind the scenes. With our so-called “Web 2.0″ applications and their rich content and interaction, we expect our applications to increasingly make use of CSS and JavaScript. To make sure these applications are nice and snappy to use, we need to optimize the size and nature of content required to render the page, making sure we’re delivering the optimum experience. In practice, this means a combination of making our content as small and fast to download as possible, while avoiding unnecessarily refetching unmodified resources. […]

  58. Ajaxian » Service JavaScript Fast says

    […] Cal Henderson has written a piece on Servicing JavaScript Fast. Cal takes a look at the practical issues of compression (where you learn how evil mod_gzip is!) and caching. […]

  59. Jonathan Arkell says

    Great article, but one nitpick, doing:

    echo implode('’, file($_GET[path]));

    seems a bit wonky, why not just :

    readfile($_GET[’path’]);

    Which would be faster, as you are not reading a file into an array, only to collapse it and spit it out again.

    I also wonder how vulnerable the header solution is to XSS attacks.

  60. picture of Cal Henderson Cal Henderson says

    Ah, right, yes. The code we previously doing something with the contents before outputting them, but you’re right - it’s a sub-optimal way of outputting a files contents directly.

    As for XSS attacks, i’d be interested to know which piece of code you think might have problems.

  61. James Mc Parlane says

    I have a screen recording of the three different ways I deal with packaging JavaScript.

    1) MwUse - Intelligent dependancy based system
    2) MwPackage - From MwUse can build a package of files
    3) MwMonolithic - From a package can build a monolithic file and strip out comments and superfluous whitespace.

    Its near the end of this screen recording.

    http://blog.metawrap.com/blog/TwentyMinuteScreenRecordingOfJavaScriptMacroRecorder.aspx

  62. Quentin says

    great article !!

    What about JSMIN to who can also help you to shrink your JS files.

  63. Dominic Mitchell says

    Cal: In regards to wordpress and backslashes, I never figured out whether it was PHP, MySQL or wordpress which was eating them. In the end, I’ve ended up switching to numerical entities to get them properly fixed. \ is ugly, but works.

  64. benstraw.com » links for 2006-06-02 says

    […] Vitamin Features » Serving JavaScript Fast (tags: javascript ajax css development fast) […]

  65. Scott Becker says

    After reading this article I was immediately inspired to create a plugin to easily facilitate this in Ruby on Rails. I whipped up most of it right then. It also compresses the files with JSMin.

    Seeing a couple other people’s posts thinking about the same thing kicked it into high gear, and I finally got around to polishing it for release today.

    Told myself I wasn’t allowed to eat today until I finished. How’s that for inspiration?

    Here you go!

    MergeJS - E

    - Scott

  66. Scott Becker says

    Sorry, that link should be:

    Easily merge, compress, cache, and version your javascript with Ruby on Rails!

  67. Dan Century » A post strictly for my web developers says

    […] JavaScript: Serving JavaScript Fast. Help speed up your Ajax apps. […]

  68. popnutten :: le blog plus cool :: Webdesigner Lesefutter says

    […] Einen fundierten Artikel über die Möglichkeiten JavaScript- & CSS-Dateien einer Schrumpfkur zu unterziehen hat Cal Henderson im immer lesenswerteren vitamin-blog veröffentlicht. Trotz DSL-Schwemme; in Zeiten aufgeblähter JavaScript-Liberies/-Frameworks/-Whatever ein sehr sinnvoller Gedanke. […]

  69. Serving JavaScript Fast says

    […] Serving JavaScript Fast: ” Cal Henderson brings us this new (lengthy) article on ThinkVitamin.com, a look at speeding up the one thing that’s really growing large on Web 2.0-type applications - the Javascript running behind the scenes. With our so-called ‘Web 2.0′ applications and their rich content and interaction, we expect our applications to increasingly make use of CSS and JavaScript. To make sure these applications are nice and snappy to use, we need to optimize the size and nature of content required to render the page, making sure we’re delivering the optimum experience. In practice, this means a combination of making our content as small and fast to download as possible, while avoiding unnecessarily refetching unmodified resources. […]

  70. Gen-X-Design | Ian Selby » What to do About Huge JavaScript Files says

    […] Read this article […]

  71. Заметки на полях » Blog Archive » says

    […] Оригинал статьи c обсуждением можно найти на сайте Vitamin. […]

  72. design4speed says

    Hi cal,

    this is good advises worth spreading. This is not specially related to javascript, it is more about what is the best strategy to optimize download on the browser side, which is worth whatever the context, Web 2.0 or 1.0 …

    You will find in my blog some entries about caching or impact of Ajax on server side performance requirement.

    What would be more interesting would be to explain what should be done on client side to guarantee good performance: what can you or cannot you do in javascript, what are the pitfalls, limitations, etc. That would be very interesting to get some view from a Flick’r developer, which is a very great site I’am using.

  73. » links for 2006-06-07 « marksdigital says

    […] Vitamin Features » Serving JavaScript Fast (tags: optimization javascript ajax css webdev) […]

  74. » JavaScript » HTML Blog says

    […] : http://www.thinkvitamin.com/features/webapps/serving-javascript-fast […]

  75. D Jones says

    I have used some of these techniques and found reducing scripts to single files to be the easiest way besides mod_gzip to make a difference. Also I have use source optimization before both by hand and I used a product called w3compiler.com which actually does a pretty good job suprisingly enough though I do have a few files I still hand optimize or do skip directives on. The effort made on compression doesn’t always pay in time either, sometime my interest is some of this more on keeping people from just stealing my code in 1 second … make them work for it I say :-)

  76. COLD CASE » Blog Archive » How To Speed Up Your Web App says

    […] read more | digg story Explore posts in the same categories: coldcase […]

  77. Yoann Boukredine says

    Have a look at that great utilities :

    http://hometown.aol.de/_ht_a/memtronic/

    http://www.syntropy.se/?ct=downloads&target=jcefree

    http://svn.dojotoolkit.org/dojo/trunk/buildscripts/lib/

    Real solution : obfuscate and pre-compression (small name in variable etc…) :)

    Yoann!

  78. dandyna says

    This is a real useful article to me who’s beginning with javascript

  79. selva says

    hai henderson,
    Your tips for the java script loading is very good.
    but i need code exaple for jsp based examples to merge files,etc..
    You have descriped everything in PHP.. Try to give in JSP also.. I realy need that..

  80. Danny says

    Email to a Friend is not validating

  81. webmasternewsblog.com » Blog Archive » says

    […] Speed up your JavascriptVitamin has a good article about how to optimize your Javascript for speed and efficiency, as old techniques won’t fly in a “Web 2.0″ world. Cal Henderson’s best practices include compression, caching, and letting PHP or other server-side technologies do some of the work. […]

  82. » De cómo comprimir ficheros javascript - Scriptia says

    […] En Serving JavaScript Fast, Carl Herlson, de Flickr, habla largo y tendido sobre diferentes métodos para acelerar la carga de scripts y hojas de estilo (combinación, compresión, cacheado). […]

  83. Suave’s Blog » Web Cache Tutorial says

    […] Serving Javascript Fast […]

  84. Programming » How To Speed Up Your Web App says

    […] The next generation of web apps make heavy use of JavaScript and CSS. This article, by the lead developer of Flickr, shows you how to make those apps responsive and quick.read more | digg story […]

  85. in new jersey says

    compassionate!inclusion,curl tirelessness?eloquently incloses latent initiating

  86. geoffrey says

    Thanks for this great piece of reading, both in the article and in the many interesting comments.

  87. toadward » Blog Archive » Webdeveloper Links says

    […] Zum einen sollte man die Größe der verschiedenen Dateien optimieren, welche auf einer Webseite sind. Dies wären HTML, CSS, JavaScript sowie Bilder. Je geringer das Datenvolumen, desto weniger Datenanfragen an den Server müssen abgesetzt werdeb, desto weniger Datenpakete werden übertragen (Reduce HTTP Requests). Hierbei finde ich vor allem die Optimierung von mehreren JavaScripten (Serving JavaScript fast) sowie von Cascading Stylesheets (CSS Compressor) bemerkenswert. […]

  88. bueroeinsnull|blog » Blog Archive » ajax says

    […] Ajax Optimization: Serving JavaScript Fast […]

  89. bueroeinsnull|blog » Blog Archive » //ajax says

    […] AjaxOptimization: Serving JavaScript Fast […]

  90. Go-test.net » Blog Archive » script.aculo.us - the other side of fame? says

    […] So, what is bad about script.aculo.us? People who use it where it’s not needed. If you need something really simple it is much better to do some custom work on it than to use heavy library (especially if you don’t use its full functionality). Here you can read more about proper use of JavaScript in your applications. Share and Enjoy:These icons link to social bookmarking sites where readers can share and discover new web pages. […]

  91. Tech Links says

    […] Make javascript run fast - Cal Henderson of Flickr talks about his experiences in speeding up javascript. […]

  92. Bob says