README
shortbread
is a Node module that helps you implement an asynchronous, non-blocking loading strategy for your CSS and JavaScript resources, thus improving the start render time of your websites. It's intendend to be part of your toolchain and plays well with Grunt, Gulp and alike.
Installation
To add shortbread as a development dependency to your Node project, run
npm install shortbread --save-dev
Asynchronous resource loading
Most often, not all the CSS and JavaScript resources referenced by your website are really necessary for initially rendering the page to your visitor. It makes sense to distinguish between critical and non-critical resources and defer the loading of non-critical ones until after the page got rendered to the screen. To further speed up things, it might also make sense to inline the most critical stuff directly into your HTML source when the page is loaded for the first time (and leverage browser caching on all subsequent visits).
Let shortbread simplify these things for you and provide it with
- the JavaScript resources you want to load,
- your CSS resources,
- possibly some critical CSS / JavaScript resource (subject to inlining),
- an optional cookie slot (see below) and
- an optional JavaScript callback you want to call after all resources have been loaded for the first time.
Based on these values, shortbread creates two HTML fragments to be included into the <head>
of your documents depending on whether it's the first or a subsequent page load. You will need some server side code to accomplish that.
First page load
The first page load HTML fragment will do the following:
- It inlines a small JavaScript (including prelink) which is needed for creating a client-side shortbread instance and performing the following steps.
- It inlines your critical CSS and / or JavaScript resources (if any).
- It loads your non-critical JavaScript resources with
async
anddefer
and registers them with shortbread once they finished loading. If a JavaScript resource is listed as both critical (inlined) and non-critical (external), the non-critical instance is loaded withrel=prefetch
(polyfilled if necessary) to prevent double evaluation. - It loads your CSS resources with
rel=preload
(polyfilled if necessary) and registers them with shortbread once they finished loading. - It wraps up with a
<noscript>
fallback for (synchronously) loading at least the CSS resources in case there's no JavaScript available. - As soon as all CSS and JavaScript resources finished loading*, shortbread
- sets a cookie to let your server distinguish between initial and subsequent page loads and
- runs the final JavaScript callback you provided (if any).
* Please be aware that, depending on the browser in use, the non-critical version of hybrid JavaScript resources (see 3) may or may not have finished loading when the callback gets triggered, as e.g. Internet Explorer 11 (which has rel=prefetch
support) doesn't fire an onload
in this case. In the worst case, the resource has to be loaded in a synchronous manner during the second page load.
Subsequent page loads
Based on the cookie set during intial page load, your server should be able to detect subsequent visits and include the alternative HTML fragment. This one leverages the browser cache and synchronously loads both
- your JavaScript and
- CSS resources.
Shortbread cookie
The shortbread cookie serves two purposes:
- If it is set, the server recognizes that the resources have already been loaded and switches into subsequent page load mode (i.e. it doesn't make the client load the resources asynchronously as they should have already been cached).
- The expected cookie value represents a unique set of resources and changes whenever one of the resources changes in content. This way the cookie serves as a cache busting measure and ensures that the resources get updated as soon as they're modified on the server. Please be aware that you also need to adapt the server-side code whenever the resources change.
API
To use shortbread from JavaScript, you'd do the following (example for NodeJS):
const shortbread = require('shortbread');
const fragments = shortbread(js, css, critical, 'main', 'allLoaded', {prefix: '/'});
The return value will be an object with the following properties:
{
initial: '<script>...</noscript>', // Initial page load fragment
subsequent: '<script>...</link>', // Subsequent page load fragment
resources: { // Single resource and corresponding hashes
'422a6fc6': 'path/to/resource/1',
'60062743': 'path/to/resource/2'
},
hash: 'df5bf8f7', // Cookie value when all resources are loaded
cookie: 'sb_main' // Cookie name (here: including "main" slot)
}
You can use this object as a source of templating values when rendering the the server-side code to handle client requests.
The signature of shortbread()
looks like this:
/**
* Create HTML fragments for assynchronously loading JavaScript and CSS resources
*
* @param {File|Array.<String,File>|Object.<String,File>} js [OPTIONAL] JavaScript resource(s)
* @param {File|Array.<String,File>|Object.<String,File>} css [OPTIONAL] CSS resource(s)
* @param {File|Array.<File>|Object.<File>} critical [OPTIONAL] Critical CSS / JS resource(s)
* @param {String} slot [OPTIONAL] Cookie slot
* @param {String} callback [OPTIONAL] Callback
* @param {Object} config [OPTIONAL] Extended configuration
*/
function shortbread(js, css, critical, slot, callback, config) {
// ...
}
JavaScript and CSS resources
shortbread expects you to use Vinyl objects to pass in your JavaScript and CSS resources (js
, css
and critical
arguments). When creating <script src="...">
and <link href="...">
elements, it uses the Vinyl objects' relative
property to determine the request paths for your resources, so you can easily use virtual paths for your HTML output like in this example:
const vinyl = require('vinyl-file');
const script = vinyl.readSync(path.join(__dirname, 'path/to/script.js'));
script.path = `${script.base}/js/mysite.js`;
// Will create `<script src="js/mysite.js" async defer></script>`
As you see, I recommend vinyl-file for creating Vinyl objects of your files.
For external resources you can also pass in absolute URLs for js
and css
instead of Vinyl objects.
shortbread uses regular expressions to filter and separate the critical CSS & JavaScript resources. Resources not matching any expression will be ignored. You may configure custom file name patterns via the extended configuration options css
and js
.
Cookie slot
By default, the name of the shortbread cookie is sb
. If you're using multiple resource sets, however, you'll have to keep track of them separately by "slotting" the cookie. When you pass a slot
argument to the shortbread()
function, say "set1"
, the cookie will be named sb_set1
. The actual cookie name is returned in the cookie
property of shortbread()
's result object.
Extended configuration
The sixth argument to shortbread()
may be an object with following properties:
Property | Type | Description |
---|---|---|
prefix | String | Prefix for all resource URLs (JavaScript and CSS). Set it to "/" for instance in order to use root-relative paths like <script src="/path/to/script.js"> . |
css | Array | List of regular expressions to match critical CSS resource file names (defaults to ['\\.css
|