styletools/async-css

A lightweight, IE8+ asynchronous CSS loader that favors performance in modern browsers. For use with 📐 Style.Tools CSS optimization browser widget.

Installs: 53

Dependents: 2

Suggesters: 0

Stars: 0

Watchers: 1

Forks: 0

Open Issues: 0

Language:JavaScript

Type:project

2.1.12 2019-02-23 12:01 UTC

README

Build Status Version npm version Latest Stable Version BrowserStack Status

Async CSS Loader

A lightweight, IE8+ asynchronous CSS loader that favors performance in modern browsers.

Install via npm

npm install @style.tools/async-css --save

Install via PHP Composer

composer require styletools/async-css

The CSS loader is similar to loadCSS but enhanced with features for cutting edge website performance optimization, including:

  1. Timed and responsive download and/or render.
  2. Dependency based CSS rendering.
  3. in-view (element in view) based download and/or render.
  4. requestAnimationFrame and requestIdleCallback (smooth rendering)
  5. Chainable and events.
  6. localStorage or Cache API based <style> loading (much faster, see css-art.com)
  7. Async injected stylesheet capture via MutationObserver or DOM insert method rewriting (rewrite, delete, responsive/conditional loading)
  8. Fallback for browsers that do not support the loader via try {} catch and <noscript rel="css">.
  9. An innovative script and config compression solution to achieve the smallest possible size in the HTML document header.
  10. Performance API timings for debugging and optimization.

Despite the many features, the basic script size is 1.55kb compressed. It is designed for above-the-fold optimization and to be included inline in the <head> section of a HTML document.

Show script sizes of CSS loader modules
async-core.js Size: 1.95 kb (1999 bytes) Gzip: 0.96 kb (980 bytes).
css-loader.js Size: 1.02 kb (1043 bytes) Gzip: 0.61 kb (626 bytes).
js-loader.js Size: 1.60 kb (1637 bytes) Gzip: 0.89 kb (910 bytes).
attr-config.js Size: 0.25 kb (257 bytes) Gzip: 0.21 kb (216 bytes).
rebase.js Size: 0.18 kb (186 bytes) Gzip: 0.17 kb (169 bytes).
event-emitter.js Size: 0.46 kb (475 bytes) Gzip: 0.24 kb (246 bytes).
debug.js Size: 0.12 kb (120 bytes) Gzip: 0.12 kb (127 bytes).
regex.js Size: 0.14 kb (142 bytes) Gzip: 0.14 kb (140 bytes).
vendor.js Size: 0.18 kb (187 bytes) Gzip: 0.16 kb (165 bytes).
api.js Size: 0.27 kb (278 bytes) Gzip: 0.19 kb (198 bytes).
dependency.js Size: 0.68 kb (698 bytes) Gzip: 0.39 kb (396 bytes).
timing.js Size: 0.65 kb (668 bytes) Gzip: 0.38 kb (387 bytes).
inview.js Size: 0.89 kb (915 bytes) Gzip: 0.55 kb (561 bytes).
responsive.js Size: 0.26 kb (267 bytes) Gzip: 0.20 kb (201 bytes).
cache.js Size: 1.24 kb (1271 bytes) Gzip: 0.69 kb (709 bytes).
cache-css.js Size: 0.32 kb (323 bytes) Gzip: 0.24 kb (245 bytes).
cache-js.js Size: 0.11 kb (109 bytes) Gzip: 0.12 kb (119 bytes).
localstorage.js Size: 0.39 kb (399 bytes) Gzip: 0.26 kb (267 bytes).
cache-api.js Size: 0.62 kb (638 bytes) Gzip: 0.35 kb (359 bytes).
xhr.js Size: 0.68 kb (694 bytes) Gzip: 0.43 kb (444 bytes).
cache-update.js Size: 0.15 kb (152 bytes) Gzip: 0.13 kb (138 bytes).
capture.js Size: 1.14 kb (1166 bytes) Gzip: 0.66 kb (673 bytes).
capture-observer.js Size: 0.24 kb (249 bytes) Gzip: 0.19 kb (198 bytes).
capture-insert.js Size: 0.33 kb (333 bytes) Gzip: 0.22 kb (225 bytes).
capture-css.js Size: 0.14 kb (141 bytes) Gzip: 0.13 kb (130 bytes).
capture-js.js Size: 0.07 kb (69 bytes) Gzip: 0.08 kb (87 bytes).

Performance first: no concessions for old browsers

The CSS loader favors performance in modern browsers. For example, loadCSS waits for document.body to be available before downloading of CSS will start to prevent render blocking issues in IE11. This requires a costly setTimeout solution.

asyncCSS does not favor IE over modern browsers and starts downloads immediately, saving costly milliseconds and reducing javascript burden.

Documentation

Documentation is available on https://docs.style.tools/async-css/.

Usage

The CSS loader is easy to use and provides onload events. The asyncCSS load method returns a then method that resolves like a Promise when all stylesheets are rendered.

// simple async loading
asyncCSS('sheet.css').then(function() { /* onload */ });

// dependencies, timing and global options
asyncCSS(
   [  // load 3 stylesheets
      'sheet.css', 
      {
         href:'other-sheet.css',
         dependencies: ['sheet.css'], // wait for sheet.css via dependencies and insert after DOM element
         load_timing: {
            type: 'inview',
            selector: '#footer', // download stylesheet when footer becomes visible within 250 pixels
            offset: -250
         }
      }, 
      {
         href:'mobile-sheet.css',
         load_timing: {
            type: 'media', // download stylesheet based on a media query (also works with viewport changes)
            media: 'screen and (max-width: 600px)'
         }
      }
   ],
   {  // global options applied to all stylesheets
      base: '/long/path/to/css/', // base directory for relative sheet URLs
      cache: {
         type: "localstorage",
         max_size: 10000, // cache only <10kb
         fallback: 'cache-api', // fallback to Cache-API for bigger sheets
         update: {
            head: true, // use HTTP HEAD request to check for 304 - Not Modified
            interval: 86400 // update once per day
         },
         source: ['cssText','xhr','cors'], // default
         cors: {
            proxy: 'https://cors-anywhere.herokuapp.com/', // more proxies on https://gist.github.com/jimmywarting/ac1be6ea0297c16c477e17f8fbe51347
         },
         xhr: {
            headers: {
               "x-special-header": "secret-key" // request header to include in XHR requests
            }
         }
      },
      attributes: { 
         "data-app-sheet": "1" // HTML attribute to add to stylesheet element
      },
      render_timing: 'requestAnimationFrame' // render via requestAnimationFrame
   } 
).then(function() { /* ready */ });

// chainable
asyncCSS
   .on('load',function(sheet, sheetEl){
      //  sheet.css or other-sheet.css loaded
   }) 
   .on('sheet-ref',function() { }) // sheet with ref-name loaded
   .on('sheet.css', function() {}); // sheet with href loaded
   .load({
      href: 'sheet.css', 
      ref: 'sheet-ref'
   })
   .then(function() { }) // sheet.css loaded
   .load('other-sheet.css');

// capture and remove async script-injected sheet
asyncCSS.capture(
{
   match: "bloated-sheet.css",
   action: {
      "type": "remove"
   }
}, {
   insert: true // use DOM insert method rewriting
});

Configuration

The full configuration of the CSS loader is available in JSON schemas.

https://github.com/style-tools/async-css/tree/master/json-schemas

For an overview of options, see https://docs.style.tools/async-css/usage.

Installation

asyncCSS is designed for above-the-fold optimization and to achieve the smallest size possible. The script is compiled into individual modules via Google Closure Compiler that does not just compress javascript but also optimizes it for speed. The individual modules can be concatenated without the use of a module loader. You can use your CMS (PHP, Node.js etc.) to inline the scripts.

IIFE

For easy usage it is possible to create an IIFE. IIFE or Immediately-invoked Function Expressions is a coding pattern for loading a script. An IIFE can be used in the browser safely.

Example

<script async src="dist/iife.js"></script>

There are several solutions available to create an IIFE.

Manual concatenation

The Google Closure Compiler module architecture is originally designed to make it easy to selectively load modules for individual pages on the basis of the applied CSS loading configuration. The above-the-fold budget is limited so every byte that can be saved counts.

By manually concatenating the modules via a CMS it is possible to achieve the smallest script size possible for any given configuration. The 📐 Style.Tools PHP library provides a module auto-discovery method that enables to load just the modules that are needed.

The following example shows how to inline the Async CSS Loader using PHP.

Example in PHP

<?php

// include Async CSS Loader inline
echo "<script>(function(){"; // wrap in IIFE
readfile('vendor/styletools/async-css/dist/async-core.js');
readfile('vendor/styletools/async-css/dist/css-loader.js'); 
readfile('vendor/styletools/async-css/dist/cache.js'); // cache module
readfile('vendor/styletools/async-css/dist/localstorage.js'); // localStorage module
echo "})();</script>";
?>

Example using 📐 Style.Tools PHP library

<?php

// CSS Loader config 
$asyncCSS_config = array(
   'load' => array( 'sheet.css' ),
   'load_options' => array( 'load_timing' => 'requestAnimationFrame' )
); 

// alternative, when using a CMS connector plugin
// $asyncCSS_config = StyleTools\AsyncCSS::load_config(true);

// you can add config via database or a PHP method:
// StyleTools\AsyncCSS::add_config($config, $load_options, $capture, $capture_options);

// auto-discover modules via config
$modules = StyleTools\AsyncCSSModules::discover($asyncCSS_config);

// manually add some modules
$modules[] = 'api'; // public API and events
$modules[] = 'attr-config'; // data-c HTML attribute config

// load modules and dependencies in correct order
$modules = StyleTools\AsyncCSS::load_modules(
   $modules,
   true, // return script text
   false // load dist/debug/ sources
);
$module_iife = '!function(){'.implode($modules, '').'}()';

// alternative, create a GCC compressed IIFE
/*
// IIFE options
$iife_options = array(
   'cache' => true,
   'compress' => true,
   'mode' => 'unary'
);

// debug sources
if ($async_debug) {
   $iife_options['debug'] = true;
}

// generate
$module_iife = StyleTools\AsyncCSS::iife($modules, $iife_options);
*/

// compress JSON config
$compressed_config = StyleTools\AsyncCSS::compress_config(
   $asyncCSS_config, 
   false, // optional: asyncJS_config (script loader)
   true // return escaped JSON (for HTML attribute usage)
);

// inline Async CSS Loader
echo '<script data-c=\''.$compressed_config.'\'>'.$module_iife.'</script>';
?>

For more examples, see https://docs.style.tools/async-css/installation

JSON config compression

To reduce the size in the HTML document header to the absolute minimum, the CSS loader can be configured using the HTML attribute data-c on the script element that contains the configuration to be passed to asyncCSS.

The configuration can be compressed into a numeric index based JSON object to achieve the smallest size possible for complex JSON configuration.

Example

Non-compressed.

<script src="dist/iife/all.js"></script>
<script>asyncCSS("css/style.css", {"href": "css/extra.css", "load_timing": "domReady"})</script>

Compressed + using attribute config.

<script async src="dist/iife/all.js" data-c='["css/style.css",{"4":"css/extra.css","21":27}]'></script>

CSS Loading Optimization

Modern website performance optimization requires the optimization of CSS loading. It is essential for performance scores such as the Google Lighthouse score, an increasingly important aspect for modern SEO (search engine optimization).

Before the browser can render content it must process all the style and layout information for the current page. As a result, the browser will block rendering until external stylesheets are downloaded and processed, which may require multiple roundtrips and delay the time to first render.

The critical styles needed to style the above-the-fold content are inlined and applied to the document immediately. The larger stylesheets are loaded asynchronously and are applied to the document once it finishes loading, or using an optional conditional timing method, without blocking the initial render of the critical content.

Google PageSpeed Insights

Browser tests 68747470733a2f2f7374796c652e746f6f6c732f62726f77736572737461636b2e706e67

68747470733a2f2f7777772e62726f77736572737461636b2e636f6d2f6175746f6d6174652f62616467652e7376673f62616467655f6b65793d62325a765430524d516e42596146464456553147644468785a554a716155686d4e4646364d445172515864784e474a565454466e576d7070637a30744c54644b52486c715245463362464e6d64334934515574324b334e426556453950513d3d2d2d39303735666135663863396539356437636330326236353964303165643331663538386665623132

Under construction...

Browser tests provided by BrowserStack.