@style.tools/async-js v2.1.3
Async Javascript Loader
A lightweight, IE8+ asynchronous Javascript loader designed for the ultimate performance result for complex websites.
The script loader is based on little-loader by Walmart Labs, arguably the best script loader ever made.
Note This repository is a copy of async-css for easy stand-alone installation and usage as a script loader. The script-loader module is also available in the master async-css
repository.
dist/
contains the same modules as async-css. The script loader module is js-loader
.
Install via npm
npm install @style.tools/async-js --save
defer
withDomReady
fallback for old browsers- Timed and responsive download and/or script execution.
- Dependency based script execution.
- in-view (element in view) based download and/or script execution.
requestAnimationFrame
andrequestIdleCallback
- Chainable and events.
localStorage
or Cache API based<script>
loading (much faster, see css-art.com)- Async injected script capture via
MutationObserver
or DOM insert method rewriting (rewrite, delete, responsive/conditional loading) - An innovative script and config compression solution to achieve the smallest possible size in the HTML document header.
- Performance API timings for debugging and optimization.
Despite the many features, the basic script size is 1.83kb
compressed. It is designed for above-the-fold
optimization and to be included inline in the <head>
section of a HTML document.
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).
Documentation
Documentation is available on https://docs.style.tools/async-css/.
Usage
The Javascript loader is easy to use and provides onload events. The asyncJS
load method returns a then
method that resolves like a Promise when all scripts are executed.
// simple async loading
asyncJS('script.js').then(function() { /* onload */ });
// dependencies, timing and global options
asyncJS(
[ // load 3 scripts
'script.js',
{
src:'other-script.js',
dependencies: ['script.js'], // wait for script.js via dependencies
load_timing: {
type: 'inview',
selector: '#footer', // download script when footer becomes visible within 250 pixels
offset: -250
}
},
{
href:'mobile-script.js',
load_timing: {
type: 'media', // download script based on a media query (also works with viewport changes)
media: 'screen and (max-width: 600px)'
}
}
],
{ // global options applied to all scripts
base: '/long/path/to/js/', // base directory for relative script URLs
cache: {
type: "localStorage",
max_size: 10000, // cache only <10kb
fallback: 'cache-api', // fallback to Cache-API for bigger scripts
update: {
head: true, // use HTTP HEAD request to check for 304 - Not Modified
interval: 86400 // update once per day
},
source: ['xhr','cors'],
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-script": "1" // HTML attribute to add to stylesheet element
},
exec_timing: 'requestIdleCallback' // exec smoothly via requestIdleCallback
}
).then(function() { /* ready */ });
// chainable
asyncJS
.on('exec',function(script, scriptEl){
// script.js or other-script.js loaded
})
.on('script-ref',function() { }) // script with ref-name loaded
.on('script.js', function() {}); // script with src loaded
.load({
src: 'script.js',
ref: 'script-ref'
})
.then(function() { }) // script.js loaded
.load('other-script.js');
// capture and remove async script-injected script
asyncJS.capture(
{
match: "bloated-script.js",
action: {
type: "remove"
}
}, {
insert: true, // use DOM insert method rewriting
observer: false // alternative: MutationObserver
});
Inline scripts
The following example executes an inline script after dependency script2.js
is executed and using timing mechanism requestIdleCallback
.
The script text is retrieved from a DOM noscript[id=inline-script]
element. The inline script text can also be provided via JSON config or via an input.value
DOM element.
script3.js
waits for the inline script to be executed.
// exec inline script with dependencies
asyncJS([{
src: 'script.js',
inline: {
script: '#inline-script',
type: 'container', // or: 'input', 'text'
ref: 'inline-x',
dependencies: 'script2.js',
exec_timing: 'requestIdleCallback'
// exec_after: 'all' // 'any' to execute the inline script after each individual script is loaded when using global options
}
}, {
src: 'script2.js',
load_timing: {
type: 'setTimeout',
timeout: 3000
}
}, {
src: 'script3.js',
dependencies: 'inline-x'
}]);
Configuration
The full configuration of the script 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
asyncJS
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.
- Online IIFE generator
- Node.js IIFE generator and CLI program async-css-iife
- IIFE method provided by the 📐 Style.Tools PHP library
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 script 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 Script Loader using PHP.
Example in PHP
<?php
// include Async Script Loader inline
echo "<script>(function(){"; // wrap in IIFE
readfile('vendor/styletools/async-css/dist/async-core.js');
readfile('vendor/styletools/async-css/dist/js-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
// Script Loader config
$asyncJS = array(
'load' => array( 'script.js' ),
'load_options' => array( 'load_timing' => 'requestIdleCallback' )
);
// discover modules via config
$modules = StyleTools\CMSConnector::discover_modules($asyncJS);
// 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\CMSConnector::load_modules(
$modules,
true, // return script text
false // load dist/debug/ sources
);
// compress JSON config
$compressed_config = StyleTools\CMSConnector::compress_config($asyncJS);
// escape JSON for HTML attribute
$escaped_config = str_replace('\'', ''', htmlentities(json_encode($compressed_config), ENT_NOQUOTES, 'utf-8'));
// inline Async Script Loader
echo '<script data-c=\''.$escaped_config.'\'>!function(){'.implode($modules, '').'}();</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 script loader can be configured using the HTML attribute data-c
on the script element that contains the configuration to be passed to asyncJS
.
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>asyncJS("js/script.js", {"src": "js/extra.js", "exec_timing": "requestIdleCallback"})</script>
Compressed + using attribute config.
<script async src="dist/iife/all.js" data-c='[0,0,"js/script.js",{"4":"js/extra.js","21":27}]'></script>
The first two array elements in the data-c
attribute are for the async script loader configuration. The script loader can be disabled by entering 0
. The script loader uses position 3 and 4.
Responsive script loading
A unique innovation provided by the script loader is responsive script loading. It enables to download or execute scripts conditionally based on a Media Query.
An example use case could be a complex desktop design that scales back to a simplistic design for mobile phones. It may be that more than 1mb worth of scripts would not be required for the mobile responsive design. The 📐 Style.Tools script loader makes it possible (for the first time) to download or execute scripts only for specific media queries, thus potentially gaining a lot of speed for a responsive mobile design.
Javascript data is not the same as for example CSS or image data. It has a much bigger impact. Even the ability to save just 100kb of scripts on a mobile device could have a profound effect on performance.
Timed script loading
The script loader provides several timing methods including element in view (in-view), setTimeout
, requestIdleCallback
, requestAnimationFrame
and domReady
.
Both the download and script execution can be timed individually. This enables to start the download of a heavy script in the header but only execute the script when an element scrolls into view (including the option to set an offset
and threshold
).
An efficient timing method for most websites is requestIdleCallback with optional timeout
for a time-window to wait for CPU idle time before a script is executed. On a fast desktop PC a script could execute instantly while on a slow mobile phone a script could be allowed to wait for 10 seconds. It allows for fine grained script execution prioritization to achieve the fastest first page load performance on any device for javascript heavy websites.
The script loader enables to achieve perfect Google Lighthouse performance scores for javascript heavy websites.
Browser tests
Under construction...
Browser tests provided by BrowserStack.