How we hash our Javascript for better caching and less breakage on updates
blog.greenfelt.net
blog.greenfelt.net
It seems like you will always have a script in there somewhere. Why not have it do the hash tag replacement at the same time and then the content is static?
I may just be a curmudgeon. I go to extremes to turn any runtime code into periodically-generated static code. I once started a mini project that was email-based blogging software. To post a comment, each page and existing comment had a mailto: link with an embedded ID string. Replies would hit procmail, which would pull out the ID and embed the comment in the HTML of the original page at the right place. No Javascript or CGI present, only procmail and static HTML. I wonder if Posterous does some of this?
Run-time works fine if you're loading all your JS and CSS from the same machine as your markup. But that's not an approach that scales quite as well.
Convenience. I'm sure the extra stat(2) has a measurable cost, but your js files are almost guaranteed to be in cache and linux should be able to stat 1M files a second in that case.
perl -MBenchmark -e 'timethese(3000000, { cached_stat => sub { stat("/etc/passwd") } })'
Mostly for development turn around. It's annoying to "make" between every save and reload. It also makes site updating convenient. We "darcs push" to our test site, make sure nothing is broken and then we "darcs push" to the main site and that's it.
It works fine for the site at its current scale (stats are fast!) and there is a clear path to making it create the files statically should it become a bottleneck.
script src="/whatever.js?x=09_02_2009_08_15_AM
Various versions of IE and Mozilla Firefox seem to treat this as a unique URL because of the query-string, even if whatever.js is in the cache. The caching proxies that I've dealt with seem to handle this, but I'm not sure that all of them would ... so the author's approach is much more reliable.
A) The last modified time of the file in milliseconds B) The current time milliseconds if in debug mode
Depending on if debug mode is turned on the minified file or original is served to the client. I minify on-demand since it's so fast and files rarely change, only the first request takes the hit and it is not a big hit, maybe 1 or 2 seconds.
I serve the pages with a 1 yr expiration time for these files.
I do the same for CSS.
It works very nicely :) NEVER have to clear a browser's cache when developing and garantee that all users always have the latest code.
file.js?11111 and file.js?22222 are considered separate files and would get redownloaded by the browser.
The URL parameters are significant in browser caching.
If the html is cached then it will still have the old reference to file.js?1111. If it goes and gets that from your site it will not get the old one it will get the new one. In which case you now have a mismatch and you can get errors.
With the hashing scheme this does not happen. The html references file-oldhash.js which still exists on the server in its original form. Therefore, your pages are atomic--either you will have all new scripts or all old scripts. Which is exactly what you want--it's the mismatch between new and old that causes problems.
Also can cache a number of js files into a single JS file for better performance.
I'm using quite the same thing here, but I'm doing the compilation step manually, but also combining all the JS files into one big minified one.
The templating engine always uses that combined file if it's available (one stat call). If not, it uses the non-combined versions (useful for development)
I'm curious what you mean by that. File dates are what "make" and all the other builders are built upon. If they weren't to be trusted then you could never compile programs correctly...
> not to mention the performance hit of doing stat() on all the related files.
The thing is stats are really really fast. Check onedognight's benchmark above (http://news.ycombinator.com/item?id=800778). On my server I get 1.6 million stats per second.
I can see this method has advantages but it is a band-aid over a non-functioning caching system in the first place.
Makes you wonder what caused the original problem!
This system has advantages, there is no doubt about that, but what caused the original issue (some browsers not fetching updated javascript files) remains unsolved.
A framework will often choose to default to setting some expires time, often because the framework authors don't really get caching either and don't fully understand why that's not really going to work. (It is natural that they end up here. E-Tags is a much safer default, but to make it efficient requires more work from the framework user; why that is is a bit more than I'd like to get into in this message.)
If content is going to change, you should be using E-Tags, not expiration times. This includes content that you may not think is going to change, like Javascript files, but in fact do sometimes. The best solution in this case is actually to keep creating new URLs and set Expires into the far future, so that if the browser needs "something_8ef38b.js" which had its expires set in 2030, it knows it doesn't even have to hit the server, and if you "change" the file, your new web pages will actually reference "something_f32190.js", a new file. The timestamp-on-the-end trick is the same. I wish more frameworks built this idea in better, it's generally useful.
(Drawing parallels with functional programming's idea of "value" is left as an exercise for the reader.)
This way you have a 'better' history because the numbers are in order and you can immediately tell which JS file was the previous one. While you're at it, why not just make this a commit/push hook rather than needing to generate it on the first page load? It would still be automatic for you.
http://blog.greenfelt.net/2009/09/01/caching-javascript-safe...
They are really insightful and expose the knowledge gained from a lot of work he's done at Yahoo! on this subject.