Skip to main content

3 posts tagged with "requirejs"

View All Tags

Caching and cache-busting with RequireJS

Having put together a demo of using TypeScript with RequireJS my attention turned quickly to caching. Or rather, IE forced me to think about caching.

Everyone has their own workflow, their own tools. The things they like to use as they put things together. And for me I’m a Visual Studio man – it’s not everyone’s bag but I really like it. I find the JavaScript tooling is now really solid combined with IE and it (generally) makes me more productive. I want to use it. But, as you know, nothing is perfect...

So there I was, delighted with the TypeScript / RequireJS demo. It was working just lovely. I started investigating the debugging story. What would happen if I change a script file on the fly? When I refresh IE does it pick up the tweaks?

Let’s find out. I'll open up alerter.ts and change this:

var name = "John";

to this:

var name = "Bobby";

And *boom*! Nothing. I’ve refreshed IE and I’m expecting to see “Welcome to Code Camp, Bobby”. But I’m still reading “Welcome to Code Camp, John”... I bet Chrome wouldn’t do this to me... And I’m right! It doesn’t. I don’t want to get too much into the details of this but it looks like it comes down to Chrome sending an "If-Modified-Since" request header where IE does not. I’m pretty sure that IE could be configured to behave likewise but I’d rather not have to remember that. (And furthermore I don’t want to have to remind every person that works on the app to do that as well.)

This raises a number of issues but essentially it gets me to think about the sort of caching I want. Like most of you I have 2 significant use cases:

  1. Development
  2. Production

For Development I want any changes to JavaScript files to be picked up – I do *not* want caching. For Production I want caching in order that users have better performance / faster loading. If I ship a new version of the app to Production I also want users to pick up the new versions of a file and cache those.

Research#

I did a little digging. The most useful information I found was a StackOverflow post on RequireJS and caching. Actually I’d recommend anyone reading this to head over and read that from top to bottom. Read the question and all of the answers as well – pretty much everything will add to your understanding of RequireJS.

As with any set of answers there are different and conflicting views. Phil McCull’s (accepted) answer was for my money the most useful. It pointed back to the RequireJS documentation.

*"urlArgs: Extra query string arguments appended to URLs that RequireJS uses to fetch resources. Most useful to cache bust when the browser or server is not configured correctly. Example cache bust setting for urlArgs:

urlArgs: "bust=" + (new Date()).getTime()

During development it can be useful to use this, however be sure to remove it before deploying your code."

Phil’s answer suggests using urlArgs *both* for Production and for Development in 2 different ways. Using what amounts to a random number in the Development environment (as in the official docs) for cache-busting. For the Production environment he suggests using a specific version number which allows for client-side caching between different build versions.

Full disclosure, this is not the approach favoured by James Burke (author of RequireJS). He doesn’t go into why in the RequireJS docs but has elsewhere expounded on this:

For deployed assets, I prefer to put the version or hash for the whole build as a build directory, then just modify the baseUrl config used for the project to use that versioned directory as the baseUrl. Then no other files change, and it helps avoid some proxy issues where they may not cache an URL with a query string on it.

I’m not so worried about the proxy caching issue. My users tend to be people who use the application repeatedly and so the caching I most care about is their local machine caching. From what I understand urlArgs will work fine in this scenario. Yes the downside of this approach is that some proxy servers may not cache these assets. That’s a shame but it’s not a dealbreaker for me. As I said, I still have client side caching.

If you want to go a little deeper I recommend reading Steve Souders post on the topic (in case you’re not aware Steve is Google’s Mr Performance). Interestingly, looking at the comments on the post it sounds like the lack of support for proxy caching with querystrings may that may be starting to change.

But either way, I’m happy with this approach. As I always say, if it’s good enough for Stack Overflow then it’s good enough for me:

Implementation#

I’m going to start off using the demo from my last blog post as a basis. Let’s take that and evolve it. As a result my solution is going to work with TypeScript and RequireJS (since the previous demo was about that) but the implementation I’m going to come up with would work as well with vanilla JS as it would with TypeScript compiled JS.

Let’s take a look at our index.html. First we’ll drop our usage of main.ts / main.js (our bootstrapper file that defines config and kicks off the "app"). We’ll pull out the use of data-main and instead, just after the reference to require we’ll add the contents of main.js much in the style of the RequireJS docs. We’ll also include a urlArgs that as a cache-buster that uses the approach outlined in the RequireJS docs:

<script src="/scripts/require.js"></script>
<script>
require.config({
baseUrl: "/scripts",
paths: {
"jquery": "jquery-2.1.0"
},
urlArgs: "v=" + (new Date()).getTime()
});
require(["alerter"], function (alerter) {
alerter.showMessage();
});
</script>

Spinning up the site all runs as you would expect. The question is: does this work as a cache-buster? Let’s tweak alerter.ts / alerter.js. And:

Oh yeah! We’re cache-busting like gangbusters!

So now let’s comment out our existing urlArgs (which represents the Development solution from Phil’s answer) and replace it with a fixed value like this:

//urlArgs: "v=" + (new Date()).getTime()
urlArgs: "v=1"

This represents the Production solution from Phil’s answer. Now let’s run, refresh a couple of times and ensure that our fixed querystring value results in a 304 status code (indicating “Not Modified” and cached item used):

It does! Now let’s increment the value:

urlArgs: "v=2"

When we refresh the browser this should result in 200 status codes (indicating the cached version has not been used and the client has picked up a new version from the server).

Success! That’s our premise tested – both Development and Production scenarios. Now we want to turn this into a slightly more sophisticated reusable solution like this:

<script src="/scripts/require.js"></script>
<script>
var inDevelopment = true,
version = "1";
require.config({
baseUrl: "/scripts",
paths: {
"jquery": "jquery-2.1.0"
},
urlArgs: "v=" + ((inDevelopment)
? (new Date()).getTime()
: version)
});
require(["alerter"], function (alerter) {
alerter.showMessage();
});
</script>

In the tweaked script above 2 variables are defined. The first is inDevelopment which models whether you are in the Development scenario (true) or the Production scenario (false). The second is version which represents the application version number. With this in place I can simply flip between the Development and Production scenario by changing the value of inDevelopment. And when a new version ships I can change the version number to force a cache refresh on the users.

What drives the values of inDevelopment / version is down to you. You could load the inDevelopment / version values from some application endpoint. You could hardcode them in your screen. The choices are yours. I’m going to finish off with a simple approach that I've found useful.

Let’s get the server involved!#

I want the server to drive my urlArgs value. Why? Well this project happens to be an ASP.NET project which handily has the concept of Development / Production scenarios nicely modelled by the web.config’s compilation debug flag.

<configuration>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
</configuration>

If debug is true then that reflects the Development scenario. If debug is false then that reflects the Production scenario.

So bearing that in mind I want to use the value of debug to drive my urlArgs. If I have my debug flag set to true I want to cache-bust all the way. Likewise, if debug is set to false then I want to serve up the version number so that caching is used until the version number changes. Time to break out the C#:

namespace RequireJSandCaching
{
public static class RequireJSHelpers
{
private static readonly bool _inDebug;
private static readonly string _version;
static RequireJSHelpers()
{
_inDebug = System.Web.HttpContext.Current.IsDebuggingEnabled;
_version = (_inDebug)
? "InDebug"
: System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
public static string Version
{
get
{
return (_inDebug)
? System.DateTime.Now.Ticks.ToString()
: _version;
}
}
}
}

This is a static helper class called RequireJSHelpers. It has a static constructor which initialises 2 fields. _inDebug is taken from System.Web.HttpContext.Current.IsDebuggingEnabled which exposes the compilation debug value. _version is initialised, when debug is false, to the version number of the dll (driven by this AssemblyInfo.cs [assembly: AssemblyVersion("1.0.*")] attribute)

There’s 1 property on this helper class called version. Depending on whether the app is in debug mode or not this attribute either exposes the application version or effectively the C# equivalent to JavaScript’s (new Date()).getTime(). (Well strictly speaking they have a different starting point in history but that’s by-the-by... Both are of equal value as cache-busters.)

You probably see where this is all going.

Let’s clone our index.html page and call it serverUrlArgs.cshtml (note the suffix). Let’s replace the script section with this:

<script>
require.config({
baseUrl: "/scripts",
paths: {
"jquery": "jquery-2.1.0"
},
urlArgs: "[email protected]"
});
require(["alerter"], function (alerter) {
alerter.showMessage();
});
</script>

Which drives urlArgs from the RequireJSHelpers.Version property. If we fire it up now (with debug set to true in our web.config) then we see requests like this:

And if we set debug to false in our web.config then (after the initial requests have been cached) we see requests like this:

This leaves us with a simple mechanism to drive our RequireJS caching. If debug is set to true in our web.config then Require will perform cache-busting. If debug is set to false then RequireJS will perform only version-changing cache-busting and will, whilst the version remains constant, support client-side caching.

Finished. In case it helps I’ve put the code for this up on GitHub.

TypeScript and RequireJS (Keep It Simple)

I'm not the first to take a look at mixing TypeScript and RequireJS but I wanted to get it clear in my head. Also, I've always felt the best way to learn is to do. So here we go. I'm going to create a TypeScript and RequireJS demo based on John Papa's "Keep It Simple RequireJS Demo".

So let's fire up Visual Studio 2013 and create a new ASP.NET Web Application called “RequireJSandTypeScript” (the empty project template is fine).

Add a new HTML file to the root called “index.html” and base it on “index3.html” from John Papa’s demo:

<!DOCTYPE html>
<html>
<head>
<title>TypeScript with RequireJS</title>
</head>
<body>
<div>
<h1>TypeScript with RequireJS loading jQuery in Visual Studio land</h1>
</div>
<!-- use jquery to load this message-->
<p id="message"></p>
<!-- Shortcut to load require and then load main-->
<script src="/scripts/require.js"
data-main="/scripts/main"
type="text/javascript"></script>
</body>
</html>

John’s demo depends on jQuery and RequireJS (not too surprisingly) so let’s fire up Nuget and get them:

Install-Package RequireJS
Install-Package jQuery

Whilst we’re at it, let’s get the Definitely Typed typings as well:

Install-Package jQuery.TypeScript.DefinitelyTyped

To my surprise this popped up the following dialog:

By "Your project has been configured to support TypeScript." it means that the csproj file has had the following entries added:

<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props')" />
...
<PropertyGroup>
...
<TypeScriptToolsVersion>0.9</TypeScriptToolsVersion>
</PropertyGroup>
...
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets')" />
...
</Project>

I’m not sure when this tweak to the Visual Studio tooling was added was added. Perhaps it's part of the TypeScript 1.0 RC release; either way it’s pretty nice. Let's press on.

Whilst we’re at it let’s make sure that we’re compiling to AMD (to be RequireJS friendly) by adding in the following csproj tweaks just before the Microsoft.TypeScript.targets Project import statement:

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<TypeScriptModuleKind>amd</TypeScriptModuleKind>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<TypeScriptModuleKind>amd</TypeScriptModuleKind>
</PropertyGroup>

Where was I? Oh yes, typings. So let’s get the RequireJS typings too:

Install-Package requirejs.TypeScript.DefinitelyTyped

Right – looking at index.html we can see from the data-main tag that the first file loaded by RequireJS, our bootstrapper if you will, is main.js. So let’s add ourselves a main.ts based on John's example (which will in turn generate a main.js):

(function () {
requirejs.config(
{
baseUrl: "scripts",
paths: {
"jquery": "jquery-2.1.0"
}
}
);
require(["alerter"],
(alerter) => {
alerter.showMessage();
});
})();

main.ts depends upon alerter so let’s add ourselves an alerter.ts as well:

define('alerter',
['jquery', 'dataservice'],
function ($, dataservice) {
var
name = 'John',
showMessage = function () {
var msg = dataservice.getMessage();
$('#message').text(msg + ', ' + name);
};
return {
showMessage: showMessage
};
});

And a dataservice.ts:

define('dataservice', [],
function () {
var
msg = 'Welcome to Code Camp',
getMessage = function () {
return msg;
};
return {
getMessage: getMessage
};
});

That all compiles fine. But we’re missing a trick. We’re supposed to be using TypeScripts AMD support so let’s change the code to do just that. First dataservice.ts:

var msg = "Welcome to Code Camp";
export function getMessage() {
return msg;
};

Then alerter.ts:

import $ = require("jquery");
import dataservice = require("dataservice");
var name = "John";
export function showMessage() {
var msg = dataservice.getMessage();
$("#message").text(msg + ", " + name);
}

I know both of the above look slightly different but if you look close you'll see it's really only boilerplate changes. The actual application code is unaffected. Finally, main.ts remains as it is and that's us done; we have ourselves a working demo... Yay!

Thanks to John Papa for creating such a simple demo I could use as the basis for my own demo.

Closing Thoughts#

Unfortunately there is no typing on the alerter reference within main.ts. To my knowledge there is no way to implicitly import the typings here – the only thing you can do is specify them manually. (By the way, if I'm wrong about this then please do set me straight!) That said, this is not so bad really since this main.ts file is essentially just a bootstrapper that kicks things off. All the other files contain the real application code and they have have typings a-go-go. So we're happy.

Finally for bonus points....#

I’ve included the js and js.map files in the project file as they don't seem to be added into the project by Visual Studio when the TS file is created or when it is compiled for the first time. I've also ensured that these files are dependent upon the typescript files they were generated from.

<TypeScriptCompile Include="Scripts\alerter.ts" />
<Content Include="Scripts\alerter.js">
<DependentUpon>alerter.ts</DependentUpon>
</Content>
<Content Include="Scripts\alerter.js.map">
<DependentUpon>alerter.ts</DependentUpon>
</Content>
<TypeScriptCompile Include="Scripts\dataservice.ts" />
<Content Include="Scripts\dataservice.js">
<DependentUpon>dataservice.ts</DependentUpon>
</Content>
<Content Include="Scripts\dataservice.js.map">
<DependentUpon>dataservice.ts</DependentUpon>
</Content>
<TypeScriptCompile Include="Scripts\main.ts" />
<Content Include="Scripts\main.js">
<DependentUpon>main.ts</DependentUpon>
</Content>
<Content Include="Scripts\main.js.map">
<DependentUpon>main.ts</DependentUpon>
</Content>

Want the code for your very own?#

Well you can grab it from GitHub.

How I'm Using Cassette part 2:Get Cassette to Serve Scripts in Dependency Order

Last time I wrote about Cassette I was talking about how to generally get up and running. How to use Cassette within an ASP.Net MVC project. What I want to write about now is (in my eyes) the most useful feature of Cassette by a country mile. This is Cassettes ability to ensure scripts are served in dependency order.

Why does this matter?#

You might well ask. If we go back 10 years or so then really this wasn't a problem. No-one was doing a great deal with JavaScript. And if they did anything it tended to be code snippets in amongst the HTML; nothing adventurous. But unless you've had your head in the sand for the last 3 years then you will have clearly noticed that JavaScript is in rude health and being used for all kinds of things you'd never have imagined. In fact some would have it that it's the assembly language of the web.

For my part, I've been doing more and more with JavaScript. And as I do more and more with it I seek to modularise my code; (like any good developer would) breaking it up into discrete areas of functionality. I aim to only serve up the JavaScript that I need on a given page. And that would be all well and good but for one of the languages shortcomings. Modules. JavaScript doesn't yet have a good module loading story to tell. (Apparently one's coming in EcmaScript 6). (I don't want to get diverted into this topic as it's a big area. But if you're interested then you can read up a little on different approaches being used here. The ongoing contest between RequireJS and CommonJS frankly makes me want to keep my distance for now.)

It Depends#

Back to my point, JavaScripts native handling of script dependencies is non-existent. It's real "here be dragons" territory. If you serve up, for example, Slave.js that depends on things set up in Master.js before you've actually served up Master.js, well it's not a delightful debugging experience. The errors tend be obscure and it's not always obvious what the correct ordering should be.

Naturally this creates something of a headache around my own JavaScript modules. A certain amount of jiggery-pokery is required to ensure that scripts are served in the correct order so that they run as expected. And as your application becomes more complicated / modular, the number of problems around this area increase exponentially. It's really tedious. I don't want to be thinking about managing that as I'm developing - I want to be focused on solving the problem at hand.

In short, what I want to do is reference a script file somewhere in my server-side pipeline. I could be in a view, a layout, a controller, a partial view, a HTML helper... - I just want to know that that script is going to turn up at the client in the right place in the HTML so it works. Always. And I don't want to have to think about it any further than that.

Enter Cassette, riding a white horse#

And this is where Cassette takes the pain away. To quote the documentation:

"Some assets must be included in a page before others. For example, your code may use jQuery, so the jQuery script must be included first. Cassette will sort all assets based on references they declare."

Just the ticket!

Declaring References Server-Side#

What does this look like in reality? Let's build on what I did last time to demonstrate how I make use of Asset References to ensure my scripts turn up in the order I require.

In my _Layout.cshtml file I'm going to remove the following reference from the head of the file:

Bundles.Reference("~/bundles/core");

I'm pulling this out of my layout page because it's presence means that every page MVC serves up is also serving up jQuery and jQuery UI (which is what ~/bundles/core is). If a page doesn't actually make use of jQuery and / or jQuery UI then there's no point in doing this.

"But wait!", I hear you cry, "Haven't you just caused a bug with your reckless action? I distinctly recall that the Login.cshtml page has the following code in place:"

Bundles.Reference("~/bundles/validate");

"And now with your foolhardy, nay, reckless attitude to the ~/bundles/core bundle you've broken your Login screen. How can jQuery Validation be expected to work if there's no jQuery there to extend?"

Well, I understand your concerns but really you needn't worry - Cassette's got my back. Look closely at the code below:

See it? The ~/bundles/validate bundle declares a reference to the ~/bundles/core bundle. The upshot of this is, that if you tell Cassette to reference ~/bundles/validate it will ensure that before it renders that bundle it first renders any bundles that bundle depends on (in this case the ~/bundles/core bundle).

This is a very simple demonstration of the feature but I can't underplay just how useful I find this.

Declaring References in your JavaScript itself#

And the good news doesn't stop there. Let's say you don't want to maintain your references in a separate file. You'd rather declare references inside your JavaScript files themselves. Well - you can. Cassette caters for this through the usage of Asset References.

Let's demo this. First of all add the following file at this location in the project: ~/Scripts/Views/Home/Index.js

The eagle-eyed amongst you will have noticed

  1. I'm mirroring the MVC folder structure inside the Scripts directory. (There's nothing special about that by the way - it's just a file structure I've come to find useful. It's very easy to find the script associated with a View if the scripts share the same organisational approach as the Views.).
  2. The purpose of the script is very simple, it fades out the main body of the screen, re-writes the HTML in that tag and then fades back in. It's purpose is just to do something that is obvious to the user - so they can see the evidence of JavaScript executing.
  3. Lastly and most importantly, do you notice that // @reference ~/bundles/core is the first line of the file? This is our script reference. It's this that Cassette will be reading to pick up references.

To make sure Cassette is picking up our brand new file let's take a look at CassetteConfiguration.cs and uncomment the line of code below:

bundles.AddPerIndividualFile<scriptbundle>("~/Scripts/Views");</scriptbundle>

With this in place Cassette will render out a bundle for each script in the Views subdirectory. Let's see if it works. Add the following reference to our new JavaScript file in ~/Views/Home/Index.cshtml:

Bundles.Reference("~/Scripts/Views/Home/Index.js");

If you browse to the home page of the application this is what you should now see:

What this means is, Index.js was served up by Cassette. And more importantly before Index.js was served the referenced ~/bundles/core was served too.

Avoiding the Gotcha#

There is a gotcha which I've discovered whilst using Cassette's Asset References. Strictly speaking it's a Visual Studio gotcha rather than a Cassette gotcha. It concerns Cassette's support for Visual Studio XML style reference comments. In the example above I could have written this:

/// &lt;reference path="~/bundles/core" /&gt;

Instead of this:

// @reference ~/bundles/core

It would fulfil exactly the same purpose and would work identically. But there's a problem. Using Visual Studio XML style reference comments to refer to Cassette bundles appears to trash the Visual Studios JavaScript Intellisense. You'll lose the Intellisense that's driven by ~/Scripts/_references.js in VS 2012. So if you value your Intellisense (and I do) my advice is to stick to using the standard Cassette references style instead.

Go Forth and Reference#

There is also support in Cassette for CSS referencing (as well as other types of referencing relating to LESS and even CoffeeScript). I haven't made use of CSS referencing myself as, in stark contrast to my JS, my CSS is generally one bundle of styles which I'm happy to be rendered on each page. But it's nice to know the option is there if I wanted it.

Finally, as last time you can see what I've done in this post by just looking at the repository on GitHub. The changes I made are on the References branch of that particular repository.