Skip to content

Commit

Permalink
added a maximum time that eCSSential will allow page rendering to be …
Browse files Browse the repository at this point in the history
…blocked while a stylesheet loads. Default timeout is 8 seconds, at which point the page will be shown and the stylesheet will be refetched in a non-blocking manner. This option is configurable via the "patience" option.
  • Loading branch information
Scott Jehl committed Jun 8, 2012
1 parent e036cbe commit 8b7bbbc
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 13 deletions.
43 changes: 38 additions & 5 deletions README.md
Expand Up @@ -8,12 +8,17 @@

Loading CSS in an optimized or prioritized fashion is very difficult. In order for a website to load cleanly, all CSS needed for rendering that page layout must be referenced in the `head` of a document. This is because stylesheets loaded in this way will block page rendering until they are loaded and ready to apply. If a stylesheet is referenced later in a document, or loaded dynamically via JS, users will often see a <abbr title="flash of unstyled content">FOUC</abbr> while that stylesheet loads concurrently with page rendering.

Unfortunately, this limitation can make for a lot of overhead in responsive designs, particularly if a stylesheet contains a large amount of CSS for breakpoints that don't currently apply at a particular viewport size, or worse, CSS that won't ever apply on a particular device. More unfortunate, using separate `link` elements with `media` attributes to reference stylesheets with their intended breakpoints [doesn't prevent those stylesheets from downloading and blocking page rendering](http://scottjehl.github.com/CSS-Download-Tests/), even in environments where they don't currently or will never apply.
Unfortunately, this limitation can make for a lot of overhead in responsive designs, particularly if a stylesheet contains a large amount of CSS for breakpoints that don't currently apply at a particular viewport size, or worse, CSS that won't ever apply on a particular device. More unfortunate, using separate `link` elements with `media` attributes to reference stylesheets with their intended breakpoints [doesn't prevent those stylesheets from downloading and blocking page rendering](http://scottjehl.github.com/CSS-Download-Tests/), even in environments where they don't currently or will never apply.

Lastly, if for some reason a stylesheet takes a long time to load, most browsers will let it continue to block page rendering for 30 seconds or more!


### How eCSSential Helps

eCSSential is a JavaScript utility that is designed to make browsers download files in a faster, more responsible manner than they do by default. Technically speaking, it is a teeny bit of inline JavaScript that determines which of your stylesheets should be loaded immediately and block page rendering (any stylesheets intended for mobile-first breakpoints that currently apply), which stylesheets should be deferred to load asynchronously (any stylesheets intended for breakpoints that don't currently apply to the current viewport size, but could apply later, given the device's screen size), and which stylesheets should never be loaded at all (any stylesheets intended for viewport dimensions that are larger than the device's screen). Once sorted, the essential (or eCSSential if you will) files are loaded in a way that ensures page rendering will be blocked until they're ready. The other less-essential files are loaded in a non-blocking way, letting the page render while they are fetched.
eCSSential is a JavaScript utility that is designed to make browsers download files in a faster, more responsible manner than they do by default. Technically speaking, it is a tiny bit of JavaScript that when placed in the `head` of a page, determines which of your stylesheets should be loaded immediately and block page rendering (any stylesheets intended for mobile-first breakpoints that currently apply), which stylesheets should be deferred to load asynchronously (any stylesheets intended for breakpoints that don't currently apply to the current viewport size, but could apply later, given the device's screen size), and which stylesheets should never be loaded at all (any stylesheets intended for viewport dimensions that are larger than the device's screen). Once sorted, the essential (or eCSSential if you will) files are loaded in a way that ensures page rendering will be blocked until they're ready. The other less-essential files are loaded in a non-blocking way, letting the page render while they are fetched.

In one further improvement to browsers' default loading behavior, stylesheets that are loaded in a blocking manner are given 8 seconds (by default) to load before they are refetched asynchonously, allowing the page to appear and be used.


## Check out the demos

Expand Down Expand Up @@ -70,15 +75,28 @@ Because eCSSential requires JavaScript support to perform its optimizations, you
That's it! With eCSSential in place, your pages will now render much faster on many devices (particularly small screens).


## Setting configuration options

eCSSential comes with a number of defaults that can be overridden on a per-call basis. These options are configured by passing a second argument to the `eCSSential` function in the form of an object with one or more key/value pairs.

eCSSential( {
"all": "css/all.css",
"(min-width: 20em)": "css/min-20em.css",
[_...more files..._]
},
// SET CONFIGURATION OPTIONS HERE
{ optionA: true, optionB: 500 } );

The following sections will reference configuration options that are defined in this manner.


## Optimizing Further with File Concatenation

By default, eCSSential creates individual `link` elements for each stylesheet it requests, which, depending on the number of CSS files you have, can make for a lot of HTTP requests. Reducing HTTP requests is one of the best ways to improve the performance of a site, so eCSSential is designed to work with concatenated files if you instruct it to do so.

Optionally, eCSSential can be configured to fetch all of your CSS via only 2 HTTP requests, of which the first request is immediate and blocking (synchronous), and the second is deferred and non-blocking (asynchronous). To use this feature, you'll need the help of a server-side concatenation tool, such as [QuickConcat](https://github.com/filamentgroup/quickconcat), or a build system that generates all static versions of your potential CSS file combinations. The `examples` directory contains a demo of this feature. You can also find it [here](http://scottjehl.github.com/eCSSential/examples/concat). The demo uses static generated combinations of the CSS files.

To configure eCSSential to fetch concatenated files via a single request, you'll need to define a second argument when you call `eCSSential()`.

That argument should be a JavaScript object, and it should include a `concat` method. The only rules for that method is that it should accept an array and return a string. For example, if your concatenated CSS files have filenames that are a long joined name of the files they contain, separated by periods with their directories and extensions removed, you might define a `concat` option like this:
To configure eCSSential to fetch concatenated files via a single request, you'll need to define a configuration property of `concat` as a function. The only rules for that `concat` function is that it should accept an array and return a string. For example, if your concatenated CSS files have filenames that are a long joined name of the files they contain, separated by periods with their directories and extensions removed, you might define a `concat` option like this:

eCSSential({
"all": "css/all.css",
Expand Down Expand Up @@ -147,6 +165,21 @@ Alternatively, if you would like to load all of your stylesheets in IE 6-8 polyf

When doing this, just be sure to add [Respond.js](https://github.com/scottjehl/Respond) or an equivalent workaround after the references to these CSS files.


## Changing the maximum time rendering will block

By default, eCSSential will allow a blocking stylesheet to load for 8 seconds before refetching it and showing the page in whatever state it may be. If you'd like to change this timeout, just pass a different millisecond-based `patience` value in the configuration object, like so:

eCSSential({
"all": "all.css",
"(min-width: 20em)": "css/min-20em.css",
"(min-width: 37.5em)": "css/min-37.5em.css",
"(min-width: 50em)": "css/min-50em.css",
"(min-width: 62.5em)": "css/min-62.5em.css"
//set the max rendering timeout to 6 seconds instead of 8
}, { patience: 6000 } );


## Disabling the default deferred stylesheet qualifier

By default, eCSSential will not load stylesheets that are targeted at dimensions not possible on a particular device (based on its screen size). This ammounts to better performance on small screens by reducing HTTP requests, but it does have the potential drawback that if the browser window is moved to a different sized screen, it may not have all of the styles it needs optimize for that new screen. This is somewhat of an edge case, but if you'd like to make sure every non-applicable CSS file is loaded asynchonously, you can pass the `deferAll` option as true.
Expand Down
29 changes: 25 additions & 4 deletions dist/eCSSential.js
@@ -1,4 +1,4 @@
/*! eCSSential - v0.1.0 - 2012-06-07
/*! eCSSential - v0.1.0 - 2012-06-08
* https://github.com/scottjehl/eCSSential
* Copyright (c) 2012 Scott Jehl, @scottjehl, Filament Group, Inc.; Licensed GPL, MIT; Includes matchMedia.js: http://j.mp/jay3wJ (MIT) */

Expand Down Expand Up @@ -30,11 +30,15 @@ window.matchMedia = window.matchMedia || (function(doc, undefined){
};

}(document));
/*! eCSSential.js: An experiment in optimized loading of mobile-first responsive CSS. [c]2012 @scottjehl, MIT/GPLv2 */
/*! eCSSential
* https://github.com/scottjehl/eCSSential
* Copyright (c) 2012 Scott Jehl, @scottjehl, Filament Group, Inc.; Licensed GPL, MIT; Includes matchMedia.js: http://j.mp/jay3wJ (MIT) */

window.eCSSential = function( css, config ){
"use strict";
var load = [],
defer = [],
timedout = [],
// All options false or null by default; no need for a mixin
o = config || {},
w = window,
Expand Down Expand Up @@ -95,10 +99,27 @@ window.eCSSential = function( css, config ){
}
}

// document.write the stylesheet that should block
// document.write the stylesheets that should block
if( load.length ){
d.write( makeLinks( load ) );
insLoc = d.getElementById( "eCSS" );

// set up timeout to stop a stylesheet from blocking after 8 seconds
// or by however many ms are passed via o.patience
var links = insLoc.parentNode.getElementsByTagName( "link" );
for(var i = 0, il = links.length; i< il; i++ ){
(function( c ){
var t = w.setTimeout(function(){
var next = c.nextSibling;
c.parentNode.removeChild( c );
next.parentNode.insertBefore( c, next );
timedout.push( c );
}, o.patience || 8000 );
c.onload = function(){
clearTimeout( t );
};
}( links[ i ] ));
}
}

// defer the load of the stylesheet that could later apply
Expand All @@ -108,5 +129,5 @@ window.eCSSential = function( css, config ){
insLoc.parentNode.insertBefore( div, insLoc );
}
// return data for testing
return { css: css, config: config, block: load, defer: defer };
return { css: css, config: config, block: load, defer: defer, timedout: timedout };
};
4 changes: 2 additions & 2 deletions dist/eCSSential.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/logdata.js
Expand Up @@ -78,6 +78,7 @@
info.push("<dt>Total number of CSS requests</dt><dd>"+ numlinks +"</dd>");
info.push("<dt>Total number of blocking CSS requests</dt><dd>"+ (loadCSS.config && loadCSS.config.concat && loadCSS.block.length ? 1 : loadCSS.block.length ) +"</dd>");
info.push("<dt>Total number of non-blocking CSS requests</dt><dd>"+ (loadCSS.config && loadCSS.config.concat && loadCSS.defer.length ? 1 : loadCSS.defer.length) +"</dd>");
info.push("<dt>CSS files that timed out and were refetched</dt><dd>"+ (loadCSS.length ? loadCSS.timedout.join(", ") : "0" ) +"</dd>");
info.push("<dt>Initial viewport dimensions</dt><dd>Width: "+ window.innerWidth +", Height: "+ window.innerHeight +"</dd>");
info.push("<dt>Initial screen dimensions</dt><dd>Width: "+ screen.width +", Height: "+ screen.height +"</dd>");
info.push("</dl>");
Expand Down
22 changes: 20 additions & 2 deletions src/eCSSential.js
Expand Up @@ -6,6 +6,7 @@ window.eCSSential = function( css, config ){
"use strict";
var load = [],
defer = [],
timedout = [],
// All options false or null by default; no need for a mixin
o = config || {},
w = window,
Expand Down Expand Up @@ -66,10 +67,27 @@ window.eCSSential = function( css, config ){
}
}

// document.write the stylesheet that should block
// document.write the stylesheets that should block
if( load.length ){
d.write( makeLinks( load ) );
insLoc = d.getElementById( "eCSS" );

// set up timeout to stop a stylesheet from blocking after 8 seconds
// or by however many ms are passed via o.patience
var links = insLoc.parentNode.getElementsByTagName( "link" );
for(var i = 0, il = links.length; i< il; i++ ){
(function( c ){
var t = w.setTimeout(function(){
var next = c.nextSibling;
c.parentNode.removeChild( c );
next.parentNode.insertBefore( c, next );
timedout.push( c );
}, o.patience || 8000 );
c.onload = function(){
clearTimeout( t );
};
}( links[ i ] ));
}
}

// defer the load of the stylesheet that could later apply
Expand All @@ -79,5 +97,5 @@ window.eCSSential = function( css, config ){
insLoc.parentNode.insertBefore( div, insLoc );
}
// return data for testing
return { css: css, config: config, block: load, defer: defer };
return { css: css, config: config, block: load, defer: defer, timedout: timedout };
};

0 comments on commit 8b7bbbc

Please sign in to comment.