Lazy Load JavaScript[s]
my idea of lazy loading javascript files - like you already do with images and iframes…
In pursuit of the best user experience possible we try everything. Packing a small amount of CSS into the <head>
, using squoosh.app and webp to send less bytes by same quality and load those images lazy.
With CSS and images all optimised, the browser rapidly paints your markup into something beautiful.
Still - every first visit will take a while until the page is ready for interactions. Often this results by a ton of JavaScript that is instantly loaded, parsed and executed before a browser fires the DOMContentLoaded
event.
Some of this ton of JavaScript might not be needed above the fold, so why don't we load this lazy???
http/1.1 (anti-)performance-pattern
Back in http/1.x
times, every GET-Request decreased site performance and a best practice was to bundle all JavaScript code into one page-bundle.js file. On the upside you only have one GET-Request for JavaScript (think of a js-sprite) which is loaded once, cached and the next Page-Impression is faster.
On the downside this page-bundle.js usually is really big in file size and besides the long download, your browser needs to parse and execute all of this JavaScript - so downsizing GET-Requests results into longer JavaScript execution time and a later Time To Interactive…
Than, there was an http/1.1 anti performance pattern
where you build a js-bundle for every page-type and deliver this. On the upside every page only delivers what it needs. But on the downside you eliminate browser caching and stretch out - again - Time To Interactive this way.
http/2
With http/2 multiplexing all of this have changed. You can now send every single JavaScript file to your visitor, his browser caches what he gets and the next page will be quicker because the browser only download, parse and execute new JavaScript files, the known ones are stored in an optimised format by the browser.
Problem solved?
Yes… However, please read on.
Back to topic: lazy load scripts
Split your scripts into single JavaScript files, minify and deliver them as gzip/brotli/zopfli resources and than do the same as you do with images: only load, parse and execute them, when their markup-part appears in viewport. This is lazy-scripts.
LazyScripts is a small JavaScript library I wrote:
Version `0.2.3` can handle single and multiple JavaScript files, loaded, parsed and executed in order, but this files need to be IIFEs (Immediately Invoked Function Expression) - non-IIFEs are planned for a future version with full backwards compatibility.
Markup adjustments
Add a data attribute to your markup where your JavaScript is needed, as soon as this markup appears or becomes visible, a script-tag with your scrip(s) will be added to your page.
Lazy load single JavaScript files
You have a section, which needs a single JavaScript file to enhance its UX, add this to your markup:
<div
class="my-accordion"
data-lazy-script="/path/to/a/js/file.js">
…
</div>
Lazy load multiple JavaScript files
You have a section with a 3rd party slider library and your code to instantiate the slider:
<section
class="my-slider"
data-lazy-scripts='["/path/to/3rd/party/slider.js","/path/to/your/slider-config.js"]'>
…
</section>
This way, LazyScripts will download the JavaScript files after each other, so your `slider-config.js` won't run into a missing dependency.
LazyScripts keeps track of every loaded script (also the scripts already put in your markup) and will download every script resource only once.
<section
class="slider slider--one"
data-lazy-scripts='["/path/to/3rd/party/slider.min.js","/path/to/stage/slider.js"]'>
…
</section>
…
…
…
<section
class="slider slider--two"
data-lazy-scripts='["/path/to/3rd/party/slider.min.js","/path/to/special-offer/slider.js"]'>
…
</section>
This example will download the 3rd party slider script only once, but will load and execute your slider code, when it appears in viewport.
Add LazyScripts js to your page
npm install lazy-scripts --save
Load it with a script-tag `<script src="/path/to/lazy-scripts.min.js"></script>` and call it's function when your browser is ready: `new LazyScripts();`.
<html>
<head>
…
</head>
<body>
…
<script>…inline lazy-scripts.min.js here…</script>
<script>
new LazyScripts();
</script>
</body>
</html>
You can provide an option object as parameter, to tailor LazyScripts to your needs:
<script>
new LazyScripts({
lazyScriptsInitialized: 'lsi',
lazyScriptSelector: '[data-lazy-script]',
lazyScriptsSelector: '[data-lazy-scripts]',
});
</script>
Above you see the default values - you can set none, all or single options.
lazyScriptsInitialized
A `data`-attribute which is added to all lazy-script-loaded script-tags. Useful for `querySelector[All]()` calls or just to see which script is loaded.
lazyScriptSelector
A `data`-attribute you write in your markup to specify single-load JavaScript files. This value is used internal by a `document.querySelectorAll()`call - so be sure your value is compatible to that.
lazyScriptsSelector
A `data`-attribute you use to specify multiple dependency-chained JavaScript files. This value is used internal by a `document.querySelectorAll()` call - so be sure your value is compatible to that.
Browser support
LazyScripts is written in ES-6 and transpiled into a UMD file to run in a ES-5 environment. It uses IntersectionObserver to check if an element appears in viewport. If no IntersectionObserver is supported, it loads all `data-lazy-script[s]` after another. No IntersectionObserver-polyfill is included.
For code convenience I included the mdn-polyfill/NodeList.forEach.
If you run in any trouble, find a bug or have any ideas, let me know on github.
Thank You
I need to thank Apoorv Saxena and his lozad.js-repository on github, because I adopted his rollup bundling workflow to this package.
Article Image from Cris Saur via unsplash and ghost.