Our current startup pipeline currently resembles a model that, while independently evolved, is now known in the industry as "Cutting the mustard" (coined by BBC News developer, Tom Maslen). Ref: http://responsivenews.co.uk/post/18948466399/cutting-the-mustard
- Page HTML sets <html class="client-nojs">.
- Page HTML optimistically sets class to client-js from a small, synchronous, inline script, which runs from the <head> before stylesheets and main content, thus before rendering.
- Page HTML loads startup.js using <script async src="..">. This is asynchronous.
- The startup.js scripts performs a feature test to decide whether the current browser supports our idea of a modern browsers, and based on that either continues to initialise the rest of the JavaScript-based enhancements (Grade A), or decides to gracefully fallback to a more basic experience in which we don't even attempt to load any further JavaScript (Grade C).
We use the client-nojs and client-js classes to dynamically vary our content and interfaces with the foresight of knowing whether or not the JS-enabled enhancements will be loaded. For example, by having the server render some of the will-be interactive interfaces so that users see them immediately without delay.
Our current approach applies Grade C both to modern browsers in which javascript is disabled, as well as older browsers that understand basic JavaScript but do not support the capabilities our current features require.
Our current approach also optimises for what we believe to be the most common case: a modern browser that has js enabled (by setting the class name to client-js optimistically without first showing the fallback experience, and without waiting for the async payload).
This comes at the cost of older browsers with JS-enabled sometimes seeing js-related interfaces until the async startup.js payload arrives, which toggles off those styles. This was a trade-off we deemed worth it.
<!DOCTYPE html> <html class="client-nojs" lang="en" dir="ltr"> <head> <meta charset="UTF-8"/> <title>Wikipedia, the free encyclopedia</title> <script>document.documentElement.className = document.documentElement.className.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );</script>
if ( !isCompatible() ) { // Undo class swapping in case of an unsupported browser. [..] document.documentElement.className = document.documentElement.className .replace( /(^|\s)client-js(\s|$)/, '$1client-nojs$2' ); // [..] return; } // [..] // Load remainder of code
This seems good, but after reading Enhancing Optimistically on the Filament Group blog, I realised that if the async script fails to load, the interface will be stuck on Grade A, despite those interfaces never coming to life.
This can happen due to intermittent network issues, or just because it is taking a very long (potentially infinite) amount of time.
Example code from the blog post:
if ( .. ) { // Let's enhance optimistically... addClass(); // load enhanced JS file var script = loadJS( enhancedScriptPath ); // if script hasn't loaded after 8 seconds, remove the enhanced class var fallback = setTimeout( removeClass, 8000 ); // when the script loads, clear the timer out and add the class again just in case script.onload = function(){ // clear the fallback timer clearTimeout( fallback ); // add this class, just in case it was removed already (we can't cancel this request so it might arrive any time) addClass(); }; }
We should do this :)