Lazy Resources#

javascript icon 🌐

I’ve been rebuilding this website with Jekyll and found myself using a few different resources as part of the process:

As these resources aren’t absolutely crucial to rendering the page or interacting with it, I chose to load them after the rest of the site. At first I was using async or defer on the script tags, then I decided to add a self-invoking function to the end of the body that added the relevent elements to the DOM. This improved load times and Lighthouse scores but I realised I could do more. There are some pages where the scripts or styles simply aren’t needed at all. I knew there would be conditions for loading the scripts, but these aren’t always possible to check for in the Jekyll build process. By adding a prerequisite function to the window load event, I can use the compiled DOM to check if each page needs a resource.

The script below is 965 bytes minified, but it’ll stop ≤23.6kB from being loaded unnecessarily across up to 5 requests. For larger or more complicated sites the gains could be even greater so I’ll definitely be applying this pattern to websites I make in the future.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/*
 * Author: Jack Carey (jackcarey.co.uk)
 * License: GPL-3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
 * Version: 2
 * Description: Load resources at some later stage so they are not render blocking.
 * Each object should contain the attributes that will be applied to the link or script elements.
 * 'prereq' is optional and must be a function.
 *  - The resource will only be loaded if it returns a truthy value.
 *  - It is checked on window load.
 * Performance is the goal here so you should minify this file and your resources as part of your build process.
 */
let links = [
  {
    href: "/css/pygments.min.css",
    type: "text/css",
    rel: "stylesheet",
    prereq: () => {
      return document.querySelector("code") != null;
    },
  },
  {
    href: "/css/lightbox.min.css",
    type: "text/css",
    rel: "stylesheet",
    prereq: () => {
      return document.querySelector("a img") != null;
    },
  },
];
let scripts = [
  {
    src: "/js/lightbox.min.js",
    type: "text/javascript",
    prereq: () => {
      return document.querySelector("a img") != null;
    },
  },
  {
    src: "/js/label-code.min.js",
    type: "text/javascript",
    prereq: () => {
      return document.querySelector("code") != null;
    },
  },
  {
    src: "/js/links-external.min.js",
    type: "text/javascript",
  },
  {
    src: "/js/art.min.js",
    type: "text/javascript",
    prereq: () => {
      return document.querySelector(".splash-art") != null;
    },
  },
];
function addResource(appendTo, tagName, obj) {
  let elem = document.createElement(tagName);
  let loadResource = () => {
    Object.keys(obj).forEach((key) => {
      if (typeof obj[key] !== "function") {
        console.log(key);
        elem[key] = obj[key];
      }
    });
    appendTo.appendChild(elem);
  };
  let prereq = obj.prereq ? obj.prereq : null;
  if (prereq) {
    //load the resource on load if the prerequisite is true
    window.addEventListener("load", (ev) => {
      if (prereq()) {
        loadResource();
      }
    });
  } else {
    //load the resource now
    loadResource();
  }
}
links.forEach((obj) => {
  addResource(document.head, "link", obj);
});
scripts.forEach((obj) => {
  addResource(document.body, "script", obj);
});
« Fuzzy Sorting a Sitemap Books, Podcasts, Movies, and more! »
Thanks for reading! 😎