UPDATE: I guess most people are not getting what this technique does in the first place. It sets the expiry of the JavaScript to years and not days. Once the JavaScript file is downloaded it is never downloaded again, ofcourse unless you force it by removing the file in the cache. If you visit the site often the JavaScript will not be removed from the cache. If you make any changes to the JavaScript you only need to change the version of the file and the new file will be downloaded. The older file is automatically removed from the cache when it is no longer requested. And just to add one more point this can be done on the WebServer itself without using this technique, but that has its own drawbacks. To further speed up the download you can gzip the JavaScript.
If you have developed an AJAX based web application you would know how many JavaScript files are required per webpage. If you use the prototype or dojo toolkit library you would know how big those JavaScript files can turn out to be.
I am currently developing a website fefoo.com, and I learned a few things about caching and how you can speed up your website for users who visit your site often. A website like digg takes up more than a minute to load on my dialup connection even though the main page is no more than a 27-32 KB. The real time is taken up by the JavaScript files. The solution for this problem is to cache the JavaScript files. Though caching improves the speed but it causes a problem when you have to update the JavaScript files, since the browser will not look for updated files if they have been cached.
Since your files are cached they will not be requested by the browser and you will not be able to send out updated JavaScript files. The solution to this problem is that you use a different name for your JavaScript file every time, or you can version control your directory. So for version 0.1 of your project http://testserver.com/javascript/0.1/test.js, and for 0.2 http://testserver.com/javascript/0.2/test.js
Thought this solution is good but it's still difficult to implement, and soon you will have multiple directories to take care of, and you will face problems when only one file needs to be changed.
After facing the trouble of a slow server and the JavaScript file being downloaded every time, I came up with this solution for PHP and .net based web application. You need to download getjs.php or getjs.aspx depending on your server. To load a script file use
<script src="getjs.php?file=test1&version=0.1" />
<script src="getjs.aspx?file=test2&version=0.2" />
In this example we load two files test1.js and test2.js. This solves two problems firstly it will work even if you have virtual hosting and secondly it solves the problem of multiple files. So if you wish to change only one file change the version from 0.1 to 0.2 and the new file will be downloaded and cached. In the next article I will try to tell you how this has been implemented, though if you know PHP or VB.net and have some idea about HTTP you won't face too many problems.
You can view a demo to see how you can improve your own web applications using prontoCache.
<?phpThe PHP Code
/*
* Author: Vivek Jishtu
* Copyright (c) 2006 Viamatic Softwares
*/
/**************************************************
This acts like a security measure also. So no
other extension except JavaScript can be downloaded.
**************************************************/
$filename = $_GET["file"]. ".js";
header("Content-Type: text/javascript");
if(!file_exists($filename)) {
echo "alert('The file [" .htmlspecialchars($_GET["file"], ENT_QUOTES). "] does not exist. Please inform webmaster.');";
exit;
}
$if_modified_since = preg_replace('/;.*$/', '', $HTTP_IF_MODIFIED_SINCE);
/**************************************************
The javascript never expires so if we get any
Request for a modified page we send back that
javascript has not been modified.
**************************************************/
if ($if_modified_since != "") {
header("HTTP/1.0 304 Not Modified");
exit;
}
/**************************************************
Set the cache such that it does not expire. In
this example we set it till 22nd Feb 2011.
You can change the date to whatever year you want,
any year in the future.
**************************************************/
header("Last-Modified: " . gmdate('D, d M Y H:i:s', time()) . ' GMT');
header("Expires: Tue, 22 Feb 2011 05:00:00 GMT");
header("Cache-Control: public");
echo "/* prontoCached on ". gmdate('D, d M Y H:i:s', time()) . " */\r\n";
require_once($filename);
?>
<%@ Page Language="VB" %>The VB.net Code
<script runat="server">
'
' Author: Vivek Jishtu
' Copyright (c) Viamatic Softwares
'
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
'**************************************************
' This acts like a security measure also. So no
' other extension except JavaScript can be downloaded.
'**************************************************
Dim FileName As String = Me.MapPath(Request.QueryString("file") & ".js")
Response.ContentType = "text/javascript"
If Not My.Computer.FileSystem.FileExists(FileName) Then
Response.Write("alert('The file does not exist. Please inform webmaster.');")
Response.End()
Return
End If
'**************************************************
' The javascript never expires so if we get any
' Request for a modified page we send back that
' javascript has not been modified.
'**************************************************
If Request.Headers("If-Modified-Since") <> "" Then
Response.StatusCode = "304"
Response.StatusDescription = "Not Modified"
Response.End()
End If
'**************************************************
' Set the cache such that it does not expire. In
' this example we set it till 22nd Feb 2011.
' You can change the date to whatever year you want,
' any year in the future.
'**************************************************
Response.AddHeader("Last-Modified", DateToHTTPDate(Date.Now))
Response.AddHeader("Expires", "Tue, 22 Feb 2011 05:00:00 GMT")
Response.AddHeader("Cache-Control", "public")
Response.Write("/* prontoCached on " & Date.Now & " */" & vbCrLf)
Response.Write(My.Computer.FileSystem.ReadAllText(FileName))
Response.End()
End Sub
'Source for DateToHTTPDate from http://www.motobit.com/tips/detpg_net-last-modified/
Function DateToHTTPDate(ByVal OleDATE As Date) As String
On Error Resume Next
OleDATE = OleDATE.ToUniversalTime
Return engWeekDayName(OleDATE) & _
", " & Right("0" & Day(OleDATE), 2) & " " & engMonthName(OleDATE) & _
" " & Year(OleDATE) & " " & Right("0" & Hour(OleDATE), 2) & _
":" & Right("0" & Minute(OleDATE), 2) & ":" & Right("0" & Second(OleDATE), 2) & " GMT"
End Function
Function engWeekDayName(ByVal dt As Date) As String
Dim Out As String = ""
Select Case Weekday(dt, 1)
Case 1 : Out = "Sun"
Case 2 : Out = "Mon"
Case 3 : Out = "Tue"
Case 4 : Out = "Wed"
Case 5 : Out = "Thu"
Case 6 : Out = "Fri"
Case 7 : Out = "Sat"
End Select
Return Out
End Function
Function engMonthName(ByVal dt As Date) As String
Dim Out As String = ""
Select Case Month(dt)
Case 1 : Out = "Jan"
Case 2 : Out = "Feb"
Case 3 : Out = "Mar"
Case 4 : Out = "Apr"
Case 5 : Out = "May"
Case 6 : Out = "Jun"
Case 7 : Out = "Jul"
Case 8 : Out = "Aug"
Case 9 : Out = "Sep"
Case 10 : Out = "Oct"
Case 11 : Out = "Nov"
Case 12 : Out = "Dec"
End Select
Return Out
End Function
Public Function DateFromHTTP(ByVal HTTPDate As String) As Date
Dim Swd As String, d As String, Sm As String, y As String, h As String
Dim m As String, s As String, g As String, Out As Date
HTTPDate = LCase$(HTTPDate)
If Mid$(HTTPDate, 27, 3) = "gmt" Then
Swd = Left$(HTTPDate, 3)
d = Mid$(HTTPDate, 6, 2)
Sm = Mid$(HTTPDate, 9, 3)
y = Mid$(HTTPDate, 13, 4)
h = Mid$(HTTPDate, 18, 2)
m = Mid$(HTTPDate, 21, 2)
s = Mid$(HTTPDate, 24, 2)
Out = New Date(y, mFromSm(Sm), d, h, m, s)
Out = Out.ToLocalTime
End If
Return Out
End Function
Function wdFromSwd(ByVal Swd As String) As Integer
Dim Out As Integer
Select Case LCase$(Swd)
Case "sun" : Out = 1
Case "mon" : Out = 2
Case "tue" : Out = 3
Case "wed" : Out = 4
Case "thu" : Out = 5
Case "fri" : Out = 6
Case "sat" : Out = 7
End Select
Return Out
End Function
Function mFromSm(ByVal Sm As String) As Integer
Dim Out As Integer
Select Case LCase$(Sm)
Case "jan" : Out = 1 : Case "feb" : Out = 2
Case "mar" : Out = 3 : Case "apr" : Out = 4
Case "may" : Out = 5 : Case "jun" : Out = 6
Case "jul" : Out = 7 : Case "aug" : Out = 8
Case "sep" : Out = 9 : Case "oct" : Out = 10
Case "nov" : Out = 11 : Case "dec" : Out = 12
End Select
Return Out
End Function
</script>
Incase you have the rights to change the expiry of JavaScript on the webserver itself, you can still use the version technique to send out new versions of files using <script src="test1.js?version=0.1" />. The version parameter is there to make it clear, you can also use any random value if you want.
After looking at suggestions from people I guess the best option is to use <script src="test1.js?timestamp={timestamp('test1.js');}" />. This is a simplest way of doing it. To get the timestamp of a file is language/platform dependent. But anytime the file is modified the timestamp would be changed. Using this method you would not have to make changes in the version either.