Chop-Chop Preload, then load CSS & JavaScript asynchronously.

NPM Package GitHub Repo

To learn more about the benefits of using the Chop-Chop™ JS/CSS loader, which harnesses <link rel="preload"> in modern browsers like Chrome, see: Smashing Magazine | Preload: What’s it good for?


Install via Unpkg CDN

Chop-Chop is a tiny package with no dependencies browser script size
And of course, it’s even smaller when GZIP’d by the Unpkg CDN as seen here.

<script src="https://unpkg.com/chop-chop@0.0.8" integrity="sha384-nihFIupLpAen2KgO9BtVN6LcFYEJ0FwRmh9waYwopvof5L0CX6rzF3ikQZSbdcp9" crossorigin="anonymous"></script>

Browsers That Support Preload

Can I Use link-rel-preload? Data on support for the link-rel-preload feature across the major browsers from caniuse.com.

Note: Chop-Chop works in all modern browsers that support Promises. Chop-Chop automatically detects support for <link rel="preload"> and will fall back on methods that work for the current browser; i.e., injecting styles/scripts into the <head> as usual.

Browsers That Support Promises

Can I Use promises? Data on support for the promises feature across the major browsers from caniuse.com.

If you want to support much older browsers that lack support for Promises, you can add this script tag before ALL others. The polyfill.io service equips older browsers w/ modern ES6 features. Only loading polyfills for browsers that require them; i.e., not forcing everyone to download unnecessarily.

<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6&amp;flags=gated"></script>

Preload, then Load Styles

Preload Asynchronously, Load Synchronously

<script>
  chopChop.preloadThenLoad([
    'https://example.com/styles-a.css',
    'https://example.com/styles-b.css',
    'https://example.com/styles-c.css',
  ])
    .then(function(urls) {
      console.log('Styles loaded in the order given: %o', urls);
    });
</script>

In this example, styles are given as an array of URLs (or paths), in the order you’d like them loaded. If the browser supports <link rel="preload">, each of the URLs are preloaded asynchronously by the browser, without blocking the window.onload event.

And, this technique allows for multiple resources to be downloaded by a browser simultaneously. So instead of a browser being forced to download one after the other, just to keep them in the right order, <link rel="preload"> allows for an asynchronous approach. Yes, you read that right. This allows you to load CSS asynchronously.

Once preloading of all styles is complete (typically in just seconds or less), then each of the styles are added to the DOM (i.e., officially loaded into the <head>) from the browser’s cache. They were already preloaded intelligently, so the so-called ‘loading’ phase is merely about adding the styles to the DOM in the required order, awaiting the immediate onload event, then adding the next one, and so on — guaranteeing the correct load order.

If a browser doesn’t support <link rel="preload">, styles are simply loaded in the order given, by adding them to the <head> sequentially and awaiting the onload event for each style. Again, guaranteeing styles are loaded in order across all browsers.

Preload Asynchronously, Load Asynchronously Too

If you’re not concerned about the order in which styles are applied, you can pass true as the second argument to preloadThenLoad(). In this approach, each style is added to the DOM immediately; i.e., as soon as preloading of each individual resource is complete.

The downside here is that cascading stylesheets (those that depend on each other) may not always be applied in the proper order. So only use this approach when the styles you’re loading are entirely independent from each other; i.e., when the loading order doesn’t matter.

<script>
  chopChop.preloadThenLoad([
    'https://example.com/styles-a.css',
    'https://example.com/styles-b.css',
    'https://example.com/styles-c.css',
  ], true)
    .then(function(urls) {
      console.log('Styles loaded in the order they were downloaded by the browser: %o', urls);
    });
</script>

Preload, then Load Scripts

Script loading works the same way as it does for styles.

Preload Asynchronously, Load Synchronously

<script>
  chopChop.preloadThenLoad([
    'https://example.com/scripts-a.js',
    'https://example.com/scripts-b.js',
    'https://example.com/scripts-c.js',
  ])
    .then(function(urls) {
      console.log('Scripts loaded in the order given: %o', urls);
    });
</script>

In this example, scripts are given as an array of URLs (or paths), in the order you’d like them loaded. If the browser supports <link rel="preload">, each of the URLs are preloaded asynchronously by the browser, without blocking the window.onload event.

And, this technique allows for multiple resources to be downloaded by a browser simultaneously. So instead of a browser being forced to download one after the other, after the other, just to keep them in the right order, <link rel="preload"> allows for an asynchronous approach, while still executing scripts in the proper order.

Once preloading of all scripts is complete (typically in just seconds or less), then each of the scripts are added to the DOM (i.e., officially loaded into the <head>) from the browser’s cache. They were already preloaded intelligently, so the so-called ‘loading’ phase is merely about adding the scripts to the DOM in the required order, awaiting the immediate onload event, then adding the next one, and so on — guaranteeing the correct load order.

If a browser doesn’t support <link rel="preload">, scripts are simply loaded in the order given, by adding them to the <head> sequentially and awaiting the onload event for each script. Again, guaranteeing scripts are loaded in order across all browsers.

Preload Asynchronously, Load Asynchronously Too

If you’re not concerned about the order in which scripts are executed, you can pass true as the second argument to preloadThenLoad(). In this approach, each script is added to the DOM and executed immediately; i.e., as preloading of each resource is complete.

The downside here is that script dependencies may not always be applied in the proper order. So only use this approach when the scripts you’re loading are entirely independent from each other; i.e., when the loading order doesn’t matter.

<script>
  chopChop.preloadThenLoad([
    'https://example.com/scripts-a.js',
    'https://example.com/scripts-b.js',
    'https://example.com/scripts-c.js',
  ], true)
    .then(function(urls) {
      console.log('Scripts loaded/executed in the order they were downloaded by the browser: %o', urls);
    });
</script>

Preload, then Load Scripts & Styles

The examples above are broken down into styles & scripts separately; assuming you want to load styles, then load scripts separately. But that’s not a requirement. You can mix styles/scripts together so they’re all preloaded asynchronously, and potentially at the same time, but then applied and/or executed in the order given.

In this example, all resources are preloaded asynchronously. When preloading of all resources is complete, they are added to the DOM in the order you listed them in.

The only downside is that you’re forcing all resources to preload; i.e., styles and scripts too, before anything is officially loaded into the DOM. In many cases, it’s better to load styles first, then load scripts — as shown in the previous examples.

<script>
  chopChop.preloadThenLoad([
    'https://example.com/styles-a.css',
    'https://example.com/scripts-a.js',

    'https://example.com/styles-b.css',
    'https://example.com/scripts-b.js',

    'https://example.com/styles-c.css',
    'https://example.com/scripts-c.js',
  ])
    .then(function(urls) {
      console.log('Resources loaded in the order given: %o', urls);
    });
</script>

Straight-Up Preloading (Preload Only)

If all you want to do is preload, there’s a utility for that.

<script>
  if (chopChop.canPreload) { // If browser supports preloading.

    // Preload only.
    var promise = chopChop.preload([
      'https://example.com/scripts-a.js',
      'https://example.com/scripts-b.js',
      'https://example.com/scripts-c.js',
    ]);

    // Load them later, in the order given.
    promise.then(chopChop.load);

    // Or, load them later, in the order downloaded.
    promise.then(chopChop.loadAsync);

    // Or iterate the URLs.
    promise.then(function(urls) {
      urls.forEach(function(url) {
        console.log(url.href); // https://example.com/scripts-a.js
      });
    });
  }
</script>

Straight-Up Loading (Bypass Preloading)

If you don’t want to take advantage of any preloading, Chop-Chop has utilities for that. In this example, scripts are simply loaded in the order given, by adding them to the <head> sequentially and awaiting the onload event for each script. Again, guaranteeing that scripts are loaded in the required order across all browsers.

Note: Keep in mind that by removing the preload phase, you’re not necessarily gaining any speed. In fact, you’re probably losing some speed in browsers that support <link rel="preload">.

<script>
  chopChop.load([
    'https://example.com/scripts-a.js',
    'https://example.com/scripts-b.js',
    'https://example.com/scripts-c.js',
  ])
    .then(function(urls) {
      console.log('Resources loaded in the order given: %o', urls);
    });
</script>

Asynchronous Loading (Bypass Preloading)

Simply pass the second argument as true to enable async in the loading phase.

<script>
  chopChop.load([
    'https://example.com/scripts-a.js',
    'https://example.com/scripts-b.js',
    'https://example.com/scripts-c.js',
  ], true)
    .then(function(urls) {
      console.log('Resources loaded in the order they were downloaded by the browser: %o', urls);
    });

  // Or, there's an alias for this named `loadAsync()`.
  // So in this example you don't need the second argument as `true`.
  // i.e., this does the exact same thing as `chopChop.load([], true)`.
  chopChop.loadAsync([
    'https://example.com/scripts-a.js',
    'https://example.com/scripts-b.js',
    'https://example.com/scripts-c.js',
  ])
    .then(function(urls) {
      console.log('Resources loaded in the order they were downloaded by the browser: %o', urls);
    });
</script>

Taking Advantage of Promises

You may have noticed that all chopChop.* utilities return a Promise. You can take advantage of this in your application by loading ‘sets’ of resources together in the most efficient way possible; i.e., based on the specific needs of your application.

For example, maybe I need to load jQuery and two jQuery plugins, and I consider those to be critical. However, maybe I also need Lodash, and I want to use Highlight.js too. But Lodash and Highlight.js are used in a less-critical portion of my application.

What I can do is preloadThenLoad() jQuery and plugins first. Once those are done, I can carry out the most critical tasks, then proceed with the loading of Lodash & Highlight.js.

<script>
  chopChop.preloadThenLoad([
    'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js',
    'https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js',
    'https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js',
  ])
    .then(function() {
      $(document).ready(function() {
        // jQuery ready, we can use jQuery.
      });

      // OK, let's move on to Lodash and Highlight.js.
      chopChop.preloadThenLoad([
        'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js',
        'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/ir-black.min.css',
        'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js',
      ])
        .then(function() {
          $(document).ready(function() {
            // jQuery ready, we can use jQuery.
            // Lodash and Highlight.js are ready too.
          });
        });
    });
</script>

Taking Promises a Step Further

A nice side-benefit of using Chop-Chop is that you can also fork into multiple concurrent, asynchronous preload Promise chains. In the previous example I showed how to take advantage of Promises, but that example used a simple strategy whereby one set of resources was loaded, and only then is the other set with Lodash/Highlight.js loaded.

Taking it a step further, I could just as easily ‘fork’ into multiple directions simulateneously; i.e., not wait for each ‘set’ to load before loading the next. Instead, I’ll start preloading both sets at the same time, and just let each set finish independently from the other.

In this additional example, two ‘sets’ begin preloading at the same time, and as each set becomes ready, we can proceed to carry out any operations we need to. Just remember that in using this technique, there is no guarantee that one set will complete before the other; i.e., when Lodash is ready, jQuery may or may not also be ready. It just depends on how long it takes a browser to load each set of resources.

<script>
  chopChop.preloadThenLoad([
    'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js',
    'https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js',
    'https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js',
  ])
    .then(function() {
      $(document).ready(function() {
        // jQuery ready, we can use jQuery.
        // Note: Lodash and Highlight.js may or may not be ready.
      });
    });

  chopChop.preloadThenLoad([
    'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js',
    'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/ir-black.min.css',
    'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js',
  ])
    .then(function() {
      $(document).ready(function() {
        // Lodash and Highlight.js are ready.
        // Note: jQuery may or may not be ready.
      });
    });
</script>

The benefit here is that we allow a browser to load as many resources, simulateneously, as it feels capable of. Of course, there’s no guarantee that using this technique will result in speed gains, but it certainly doesn’t hurt. A user with a very fast connection may benefit from you pushing all of the resources into a preload state at the same time, instead of loading the first three, then the next three.

Note: One minor downside to this approach is that you’re asking the browser to load everything at the same time, and in some circumstances you may prefer it if the browser is given less to do; e.g., if you really want it load and have jQuery + plugins ready for you ASAP. In that case, go with the example above, and handle each set sequentially; i.e., only ask the browser to download Lodash/Highlight.js once jQuery + plugins are ready and have been utilized by your application.

Promises + jQuery Tip: $(document).ready()

This will fire even if the DOM is already ‘ready’ — a good thing.

$(document).ready(function() {
  // jQuery is smart enough to trigger this immediately.
  // For example, if the `DOMContentLoaded` event has already been fired, that's fine.
  // So it's OK to use this in concert with chopChop Promise chains as often as you like.
});

Shorter Alias of preloadThenLoad

The most commonly-used method in the utilities provided by chopChop.*, is chopChop.preloadThenLoad. For this reason, it’s aliased as just chopChop.promise, as it returns a Promise. You can also alias chopChop as just cc.

Tip: If loading just one URL, you can pass a string instead of an array.

var cc = chopChop; // Shorter.
cc.promise(['http://example.com/script.js']).then(function() {
  // Script is ready.
});

If you want something even shorter and you only use preloadThenLoad.

var cc = chopChop.preloadThenLoad;
cc(['http://example.com/script.js']).then(function() {
  // Script is ready.
});

Understanding Phases

Preload Phase

When a browser supports <link rel="preload">, Chop-Chop will take advantage of that by asking the browser to load styles/scripts in a truly asynchronous fashion, and in many cases, simultaneously, using a priority that is worked out by the browser following a number of factors — all of which generally improve the speed of your application.

To learn more about <link rel="preload">, see this article.

It’s important to realize that in the preload phase, styles & scripts are simply being downloaded, not applied or executed by the browser. For example, when preloading a .css file, the stylesheet is downloaded, but does not affect any styles whatsoever. If a .js file is being preloaded, it is being downloaded, but not executed whatsoever.

So, in the preload phase, the browser is simply downloading. And, hopefully (probably, very likely) caching static JS/CSS files too — assuming the styles/scripts you’re preloading are delivered with a cache-control header as they should be.

Allowing a browser to cache static assets like styles/scripts is highly recommended, whether you’re using Chop-Chop or not, and generally speaking, most web servers and nearly all CDNs allow static assets to be cached. To disallow caching is not nice.

Load Phase

The load phase is about adding the styles/scripts to the DOM, by appending <link rel="stylesheet"> and/or <script></script> tags to the <head> of your document. But keep in mind, whenever a browser supports <link rel="preload">, the so-called ‘load’ phase occurs after preloading, so there is nothing left to download. Downloading has already occurred in the preload phase.

So when a browser supports preloading, you can think of the ‘load’ phase as being the ‘injection’ phase. Because no loading occurs, not really. Instead, the so-called ‘load’ phase is merely about adding the resources to the DOM in the requested order. Simply injecting files that have already been downloaded into the <head> of your document.

If a browser doesn’t support <link rel="preload">, the ‘load’ phase requires downloading to occur also, because preloading was not possible. This is not unlike many other JavaScript/CSS loaders that have been around for some time. So it’s a logical fallback technique whenever a browser does not yet support <link rel="preload">.

Async i.e., preloadThenLoad() w/ true flag.

Following the examples above, the second argument to preloadThenLoad() can be set to true. This enables an asynchronous ‘load’ phase, on top of the already-asynchronous ‘preload’ phase. The preload phase is always asynchronous, whereas the ‘load’ phase is not asynchronous by default, but can be if you wish.

When the ‘load’ phase occurs asynchronously (true flag) it means DOM injections occur immediately; i.e., as each individual file finishes preloading, instead of waiting for all preloading to complete, and only then performing DOM injections in the specific order given by your call to preloadThenLoad(). So the true flag offers you the best possible speed, but there’s a significant downside that’s important to be aware of.

The downside is that you lose control over the exact order in which styles/scripts are applied. So only use this approach when the styles you’re loading are entirely independent from each other, or when the scripts you’re loading are stand-alone; i.e., do not depend on other scripts being loaded before them.

For example, you wouldn’t want to load jQuery + jQuery plugins this way. Exclude the true flag when loading jQuery + plugins. But if you’re loading stand-alone JavaScript applications; e.g., analytics, share button functionality, or anything else that doesn’t have dependencies whatsoever, then loading them with the true flag is faster.

Alternative to preloadThenLoad() w/ true flag.

As noted in the previous section, there is a downside to forcing an asynchronous ‘load’ phase with the true flag. You lose control over the order in which styles/scripts are actually applied to the document. Even still, Chop-Chop is a very well-optimized approach to loading JS/CSS, because of it’s ability to harness <link rel="preload">.

But you might be wondering how you can avoid preloading a very long list of external resource dependencies, waiting for all of those to finish preloading, and only then adding them to the DOM in the requested order. The solution lies in your implentation.

In short, don’t do it that way. If you have a long list of style/script dependencies, load them in logical ‘sets’. That way they finish in small spurts, and can be applied quickly. Then you can move along and load something else.

For example, if a part of your application needs jQuery + two plugins to carry out some initial tasks, load jQuery and those two plugins first, carry out those operations, then continue by loading the rest of your dependencies, or maybe another ‘set’.

Generally speaking, this is advised in all areas of a web-based application. It’s a way for you to optimize the progressive loading and application of the JavaScript/CSS that a document depends on. And remember, we’re usually talking about seconds or less. But every little bit helps, and loading in logical ‘sets’ is a way to squeeze out a little extra speed.

For further details and examples of this, see: Taking Advantage of Promises