Jeremy Wagner

A Less Risky Differential Serving Pattern

18 July, 2019

2019 has been the year of differential serving for me. I've written an article about it. I've given a talk about it. I've even been helping one of my clients to roll it out broadly across their entire site. It's a great way to cut down on the amount of JavaScript you serve to your site's visitors without sacrificing features.

The pattern we use to ensure both modern and legacy browsers get the scripts that are appropriately transpiled according to their capabilities looks like this:

<script type="module" src="/js/modern.mjs"></script>
<script nomodule defer src="/js/legacy.js"></script>

This is the platform-provided pattern for achieving differential serving. It works a treat in those modern and evergreen browsers, but unfortunately, it backfires in some cases as this Github gist attests. Some legacy (and not-so-legacy) browsers will download both bundles. In some particularly egregious cases, some browsers will download and execute both bundles. If your project requirements dictate that this is unacceptable, you'll want to avoid this sort of double trouble. "But how?" you beseech me, "How do I avoid this horrible outcome?"

Well, thank you for asking.

There is another way, but be warned that it's a hack. That said, it's a hack that works. It ensures that your site's visitors won't download or execute both bundles. By checking to see if the browser supports the nomodule attribute, you can infer whether or not that browser supports the type="module" attribute:

<script>
  // Create a new script element to slot into the DOM.
  var scriptEl = document.createElement("script");

  // Check whether the script element supports the `nomodule` attribute.
  if ("noModule" in scriptEl) {
    scriptEl.src = "/js/modern.mjs";
    scriptEl.type = "module";
  } else {
    scriptEl.src = "/js/legacy.js";
    scriptEl.defer = true;
  }

  document.body.appendChild(scriptEl);
</script>

Again, it's a bit of a hack, but in cases where both support for older browsers and performance for them is non-negotiable, this just works. It won't let you down, but I hope for a time where eventually I won't need to recommend this pattern. In a world where IE 11 so tenaciously hangs on, though, this is what I'm rolling with to make sure scripts are differentially served in a way that won't penalize users on legacy browsers.

Feel like reading more? Head on back to the article list!