Skip to content

Instantly share code, notes, and snippets.

@bgerrissen
Created November 25, 2010 09:52
Show Gist options
  • Save bgerrissen/715141 to your computer and use it in GitHub Desktop.
Save bgerrissen/715141 to your computer and use it in GitHub Desktop.
RequireJS cacheKey

reason

Offers a simple mechanism to allow extremely long expire-headers on scripts whilst still being able to switch to newer versions on the fly.

implementation

A js file defined with data-main attribute on the script tag of require.js, will always be loaded with current timestamp to always force a fresh load.

// main.js?cacheKey=TIMESTAMP-123412312321
<script data-main="main" src="require.js"></script>

Inside main.js you can define a cacheKey that will be appended to all loaded scripts from that point on.

// results in [root]/module.js?cacheKey=v1.0.0
require.cacheKey( "v1.0.0" );
require( "module" , function( module ){ 
    /* code*/} 
);

or using RequireJS config mechanism:

// results in [root]/module.js?cacheKey=v1.0.0
require( {
    cacheKey: "v1.0.0"
} , "module" , function( module ){ 
    /* code*/} 
);

Set extreme long expire headers on .js files and you get optimal browser caching whilst able to update an entire dependency tree by setting one value.

As an alternative to setting the cacheKey variable in javascript, it could also be passed by another data- attribute. (from Smith, see comments)

<script data-main="main" data-cacheKey="v1.0.0" src="require.js"></script>

This allows both development teams (backend/frontend) to be able to control the cacheKey. However a question of what overrides what remains.

As a bonus, you could opt to override cacheKey usage with a path-expression plugin (see path-expression gist).

// not a pretty path-expression I admit.
require( "module !nocachekey" , function( module ){ 
    /* code*/} 
);
@smith
Copy link

smith commented Nov 25, 2010

This looks really good.

One caveat though: I'm often using PHP or Rails or something where the server-side code is the one who knows the cache key. In this case it shouldn't go inside of a module, and what would be ideal would be something like:

<script data-main="main" data-cacheKey="<?= $cb ?>" src="require.js"></script>

@bgerrissen
Copy link
Author

Good addition, will update the gist, thanks Smith ;)

However, you only need to update the cacheKey whenever a javascript dependency changes and it's maintenance is more appropriate to fall under your javascript/frontend devs, I mean they have direct access to main.js. Then there's the question of double definition, perhaps an error or warning should be thrown when the cacheKey is defined both ways?

@jrburke
Copy link

jrburke commented Nov 26, 2010

RequireJS supports an urlArgs configuration option. Is that good enough? There is no data- attribute for it, but I figure this is an advanced feature, so I think it is OK for now to ask people to use a require({ urlArgs: "something"}, ["main"]): script call instead of using data-main in their page.

@jrburke
Copy link

jrburke commented Nov 26, 2010

Thinking about this more, you can do it with urlArgs now, do something like:

<script data-main="main.js?<?= $cb ?>" src="require.js"></script>

then inside main.js:

require({ urlArgs: "v=1.0.0"}, [...], function () {
});

The trick is that data-main is not a module name but a file path to the main.js file, relative to the document's URL.

@bgerrissen
Copy link
Author

urlArgs fills the bill 90%, but is rather obscure and low level (you have to explain in detail what you can do with it).
So thought defining a standard and clear way to utilise long expire-headers and make it an obvious part of the API would be a good idea.

Utilising long expire-headers+urlArgs for cache controle adds enormous value, you can tell people to handcode it like this or simply make it part of your API/documentation. Afterall, adding convenience (however minute) methods to reduce handcoding is exactly what made jQuery so succesful as well.

ps. perhaps I think too much about API clarity and design, it's also the reason I never released any of my module loaders, I kept tinkering with the API and mechanics =P

Most people will use main.js to kickstart their other js implementations, so generally this file should not cache (which has a low impact) since it will see the most changes. "main.js" is also the most logical place to bust caching throughout the dependency trees. It's a small tweak, but oh so convenient and clear.

@jrburke
Copy link

jrburke commented Nov 29, 2010

For best caching, I have always been under the impression that paths with version numbers in them are better over querystrings because there are some proxies that will not cache anything with a querystring. So my recommended approach for creating versioned files is to change the path, and this could be done via the baseUrl if all resources change (and in fact how it is normally handled in Dojo). So inside main.js:

require({
    baseUrl: './v1/'
});

I appreciate that not all systems can use versioned paths, so urlArgs is available for those cases, but I would hope that it is more of a minority use case. I also am not keen to expand the API since it is just a permutation on urlArgs and hopefully a more minority use case, and it adds more to the download size.

@mariusGundersen
Copy link

It would be really nice to have a cacheKey property which can be read from the data attribute of the main js file, and which applies to every js file loaded with require. But using urlArgs, as presented by jrburke, does work. The way I do it now is to add a data-cacheKey property to the script parameter, like so:

<script data-main="main.js?" data-cacheKey="<?= $cb ?>" src="require.js"></script>

And then use the following config in the main file:

require.config({
    urlArgs: "cacheKey="+$('[data-cacheKey]').attr("data-cacheKey")
});

This lets me set the cacheKey on the backend, and then use it for every file loaded in the frontend. A good idea for the cacheKey would be the build number or build time of the web project, which would require all the frontend files to be reloaded every time the build on the server changes

@jrburke
Copy link

jrburke commented May 26, 2012

@mariusGundersen: you can do it in the HTML today by defining a require object before the script tag for requirejs -- requirejs will use that as the config object when it initializes:

<script>var require = { urlArgs: '<?= $cb ?>' };</script>
<script data-main="main.js" src="require.js"></script>

@mariusGundersen
Copy link

can you combine require configurations? What if the main.js file needs to configure require, won't that overwrite what was done inline?

@jrburke
Copy link

jrburke commented May 30, 2012

requirejs will merge config calls but it does not do a real deep merge. The first level or two is merged. Depends on the config setting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment