Technical

Now serving over http/2

http/2 is well and truly here, and is even partially supported by Internet Explorer 11. Check out the can I use page for http/2. It’s time to stop bundling your JavaScript and Stylesheets into single files without consideration.

Web server implementation for http/2 is also good, with NGINX having supported it from it’s core install for a few years now.

A big http/2 advantage is that you will not run into http 1.1’s limits on the maximum number of parallel connections to a single domain. These vary between browser with Chrome by default supporting 6 connections (it’s worth noting that each browser install can be manually configured to change this number, although I doubt many people do).

The http 1.1 way

Let’s have a think about what happens when we request an imaginary webpage – edspencer.me.uk/test-page.html – over http 1.1.

Looking at a browser that supports a maximum of 6 connections at a time, imagine our test-page.html contains references to 9 external assets, referenced in the header, all hosted on the same domain.

  • edspencer.me.uk/stylesheet1.css
  • edspencer.me.uk/stylesheet2.css
  • edspencer.me.uk/stylesheet3.css
  • edspencer.me.uk/scripts1.js
  • edspencer.me.uk/scripts2.js
  • edspencer.me.uk/scripts3.js
  • edspencer.me.uk/image1.png
  • edspencer.me.uk/image2.png
  • edspencer.me.uk/font1.woff2

What is going to happen here? Well, assuming that our cache is empty, the first 6 referenced files will be downloaded first. Anything after the first 6 will be queued until one of the 6 download slots frees up. This will happen when a download completes.

A simplified analogy would be you’re queuing at a checkout, and there are 6 tills staffed by 6 operators. A maximum of 6 people can be served at the same time.

This also happens for Ajax requests to the same domain, which must also form an orderly queue, with a maximum of 6 going over the wire at the same time.

There were a few workarounds for this in the http 1.1 world. One was to combine your assets into bundles. So in our above example, our 3 stylesheets become one single stylesheet, and our 3 JavaScript files would become a single .js file. This reduces the number of request slots needed by 4.

Another way would be to serve your assets from different domains in order to bypass the 6 connections per domain limit. For example, having your images served from images.edspencer.me.uk and your stylesheets from styles.edspencer.me.uk.

Both of these techniques worked well under http 1.1, but had their downsides.

Downside 1 – Serving styles and JavaScript for entire areas of a website that a client may never access

Imagine I have a whole section in my web application that is only for users with admin access. If I’m bundling all application styles and scripts into two respective files, I’m burdening the clients that will never access the admin tools of my application with the code for my admin tool. Their experience of my website would improve if I served them a smaller set of assets that did not include the code needed to run the admin tool that they will never access.

Downside 2 – Maintaining web infrastructure for serving from multiple subdomains

Setting up subdomains requires webserver and DNS configuration. I also then need to work out how I’m going to get the web application’s static assets onto their relevant subdomains. It’s a lot of effort.

The http/2 way

With http/2, you don’t need to bundle anymore, and you can instead split and serve your web app using multiple files without worrying about blocking one of those limited download slots. This is largely because of the improvements in the protocol transport, which have resulted in the recommended minimum limit for browsers implementing http/2 being much greater, at 100 concurrent connections.

Splitting your bundles more logically, instead of bundling into one will result in more, smaller bundles being sent to the client. For example you could have a bundle that contains the JavaScript for the admin pages of a web app, and it’ll only get served to the client should they land on an admin page.

If someone visits your web app and only lands on the homepage, you don’t need to serve them with the code needed to run the admin pages of your web app.

Enabling http/2 on NGINX

Setting up http/2 on NGINX is trivial, with the only change needed being the inclusion of the characters “h2” into the listen definition for your site:

server {
  listen 443 ssl http2;
  ...
}

All you then need to do is restart NGINX, and you’re good to go. You can test this out by taking a peek at the network tab in the developer tools of your preferred browser. Note the “h2” in the Protocol column:

The gains

This is a WordPress blog, and whilst I’ve taken steps to improve it’s performance, I have struggled to get the raw number of assets needed by the site down. After enabling http/2, I got an immediate and significant performance score improvement from Google Lighthouse:

I’ll be migrating this blog to a headless CMS soon which will give me much more control and will hopefully give me a nice score of 100! Watch this space.

Notes

  • http/2 is only supported over SSL
  • AWS s3 does not support http/2. You will need to put Cloudfront in front of it in order to get http/2 support
  • Google Cloud CDN supports http/2 out of the box, without any additional services
Babel

What Babel 7 does and doesn’t do

Babel 7 will not transpile any of the following out of the box:

If you want to use any of these, you should either bring in your own polyfills for what you need, or you should include corejs with Babel.

Babel 7 will transpile the following out of the box:

There are plugins for each of these but they do not appear to be necessary. It looks like Babel is trying to steer towards transpiling syntax and keyword features, and not functions or objects, which need to be polyfilled.

Babel, JavaScript

Running Babel should be the one of the last stages of your front end build

After recently upgrading from Babel 6 to 7.4 for a project, I decided to check over the outputted file to make sure all was well.

I started by looking for the basics – was const getting changed to var? Yes it was. Were ES6 classes being converted to functions? Yes they were. And were object spreads being transpiled down? Yes they were, but this was now being done in a different way to Babel 6.

In Babel 7.4, using an object spread operator will result in Babel injecting a polyfill function called _objectSpread into the top of the outputted JavaScript file. This function will then be called wherever you are using an object spread. E.g:

Input:

function someOtherTest () {
  const p1 = {
    name: 'p1'
  };
  
  const combinedP1 = {
    height: 100,
    ...p1
  }
}

Results in the following, after being run through Babel:

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function someOtherTest() {
  var p1 = {
    name: 'p1'
  };

  var combinedP1 = _objectSpread({
    height: 100
  }, p1);
}

Here’s a link to see this in action on the Babel repl.

As you can see, it’s a lot more code, but is a tax that some of us have to pay in order to support some older browsers.

And this is where we need to be careful. Most front end build processes typically follow this flow:

  • Gather the source js files
  • Combine them into one file
  • Minify the single combined file
  • Write the output to disk

What you do not want to do, is to run Babel before the output has been combined into one file. If you follow this pattern:

  • Gather the source js files
  • Transpile them using Babel
  • Combine them into one file
  • Minify the single combined file
  • Write the output to disk

You will end up with some serious bloat! In every file that you have used the object spread operator in, Babel will inject a locally scoped _objectSpread function. So you could easily end up with multiple copies of the exact same polyfill function.

This is the web, and we don’t like unnecessary bloat – so the correct workflow for your front end build is:

  • Gather the source js files
  • Combine them into one file
  • Transpile them using babel
  • Minify the single combined file
  • Write the output to disk

This way, you’ll only get polyfill functions injected once into your outputted JS, even if the polyfill is used multiple times.