How to reduce the impact of JavaScript on your page load time

Everything we know about reducing the weight of JavaScript from billions of pageviews

dark blue background with light blue balloon weighed down like your site would be weighed down with code

When we are onboarding new customers one question we often get, especially if that customer is an engineer, is about how the GoSquared JavaScript snippet will impact the performance of their site.

The GoSquared JavaScript snippet is a small piece of JavaScript you put on your site to give you analytics, lead capture, live chat and more. Having all this functionality in a single snippet makes life much easier for our customers, but adding extra weight on your site can make it slower, so we’ve put a lot of effort into making our scripts as speedy and lightweight as possible.

Here’s how we reduce the impact of JavaScript on page load times, the lessons we’ve learned along the way, and why you should care enough to learn these things and apply them elsewhere.

Why should I care about the impact of JavaScript?

dark blue background with pale blue blurred arrow and JS in large letters

The web is getting more complex. Website and web app experiences are getting ever-richer, and with the rise of more and more JavaScript frameworks, it’s easier than ever before to build these rich experiences.

But this richness and complexity come at a cost. Providing these experiences requires adding additional assets (scripts, stylesheets, images, videos), and every one of these assets consumes a certain amount of resources on your users’ devices (CPU, GPU, memory, network etc).

All this adds up to the difference between a fast, snappy experience for your user, and something slower, laggier, and altogether more frustrating. With page loading speed being one of the top reasons for visitors abandoning your website, it’s really important to get this right.

Exactly how much difference various resources make to the end-users’ overall experience varies by a number of factors – different types of resources consume different hardware in different ways; different users have different devices, all with different levels of capability.

JavaScript is expensive

Despite this, one thing is clear: byte-for-byte, JavaScript is by far the most expensive resource, especially for low-end and underpowered devices (linked is a fantastic article from Addy Osmani which explains this and is a highly recommended read). Not only does it consume network for downloading the files, but parsing, compiling, and executing those scripts all consume memory and CPU, which on a mobile device may be in particularly short supply.

When you install our code on your site or web app, that’s extra JavaScript you’re adding in addition to whatever else you already have there, so it’s incredibly important that we keep the overall cost to your users’ experience to an absolute minimum. In short, the way we do this is by making sure our scripts are as small and lean as we can make them.

This isn’t as simple as saying “ship the absolute smallest script possible!!” and shaving bytes wherever we can, regardless of any other cost. True, there are some cases where such micro-optimisations pay off, like when you’re serving billions of requests a day and can put a dollar value on every single byte-per-request of optimisation. But that sort of optimisation is more about cost-saving on our part, rather than making a tangible difference to the end-user.

This is about some of the things we do on an ongoing basis to keep the size of our JavaScript bundle small, with minimal impact to the overall performance of any page where it’s included, but also with minimal impact to our development process.

Quick wins: minification, compression, distribution

dark blue background with COMPRESS in pale blue in the middle

Bandwidth on the internet isn’t infinite, and the speed of light means latency is inevitable whenever data is sent over large geographical distances. Combining this with the rise of mobile browsing via cellular connections, the average website visitor of today may, in fact, have a connection roughly the same speed as a visitor 10 or more years ago.

There are some quick wins when it comes to reducing the impact of network effects without having to change your code at all:

  • Minify your code with a tool such as Uglify or Closure Compiler. This reduces the size of your code while maintaining equivalent functionality when it’s actually run.
  • Compress your assets in transmission with HTTP transfer encoding like gzip or brotli. Most servers or CDNs (see below) support this as a one-click setting to enable.
  • Distribute your static assets with a Content Delivery Network (CDN) that can serve them from a physical location very close to your visitors. This reduces the latency incurred when downloading your assets.

These have all been written about extensively and are pretty much industry-standard practices, but it’s important to remember them since they can make a huge impact to your users’ experience. They’re also easy to forget during development because network effects aren’t noticeable if you’re developing locally or on a high-speed wired connection in your office.

But that’s just the first part of the story. Once a browser has downloaded a script, there are several more steps, which all still contribute to the overall resource usage and speed of a page.

Parse, Compile, Execute

The way most modern browsers’ JavaScript engines actually run a script can be (roughly) reduced to three main phases: parsing, compilation, and execution.

Parsing is the process of reading the bytes of a script received by the browser and generating an internal representation of a script’s structure.

Compilation is taking that internal representation and using it to generate machine code that can be directly run by the processor, and execution is the actual running of that code.

(Note: this is a vast oversimplification of what actually happens – in reality each browser’s JavaScript engine has its own multi-step pipeline – Chrome’s V8 has Turbofan and Ignition, Firefox has IonMonkey – and there’s a lot of incredibly clever computer science going on. The overall gist is roughly the same, though)

Each of these phases consumes processor and memory resources in slightly different ways, but the most sure-fire way of simultaneously optimising for all three is simple: ship less code.

dark blue background with pale blue boat and ship less code written across

This isn’t the same thing as simply sending fewer bytes over the wire to the browser. Transmission compression makes no difference (the browser has already received all the bytes), and even code minification is negligible – once the browser has parsed the code, all minification operations such as removing comments and renaming variables make practically no difference.

At this point it’s more about the overall complexity and structure of the code that makes the difference – broadly speaking, the number and arrangement of different functions, statements, and other declarations in the code.

This is the part where the impact to the user varies most depending on the user’s device. Just as with bandwidth it’s important to remember that not everybody is always on a super-fast office internet connection, so it’s also important to realise that not everybody is using a high-end MacBook Pro as their desktop device and the latest iPhone when on-the-go. The variability in device power is incredibly wide, especially on mobile. What might take just a second to parse, compile, and evaluate on the top-end iPhone might take 4-5 times longer on an “average” mobile device, and 10-20 times longer on a low-power device.

Forcing your users to wait several seconds while their browser churns through megabytes of JavaScript is a terrible experience. Currently, all of the above phases run on the browser’s main CPU thread, which means that while they’re happening, the web page itself is effectively non-responsive, so it’s incredibly important for a third-party widget like GoSquared Assistant to keep such interruption to an absolute minimum.

So with all that in mind, how can you actually go about reducing code size? And how do we do it here at GoSquared?

The tradeoffs of JavaScript libraries

dark blue background with a pale blue shelf of books with different engineering phrases on the spines

The JavaScript ecosystem has exploded in recent years. With the advent of proper bundlers like Webpack and Rollup, and with npm becoming the de facto repository for any module you could wish for, it’s now easier than ever before to pull in a third-party author’s module and include it in your JavaScript bundle.

Some of these third-party modules are frameworks; some are interface components; some are helpful utilities to save you writing complex logic yourself. All of these carry different motivations for why you might include them, but they all should be evaluated against the same criteria before you reach into the toolkit for them. Whenever we’re thinking of adding a new dependency we try to ask ourselves:

  • Do I need to add a new library at all?
  • Do I need to add this library?

The first of these questions is about deciding whether the benefit of relying on third-party code outweighs that of simply writing the equivalent logic yourself, which you could potentially do more efficiently, tailored directly to your own use-case. This is a highly subjective and controversial matter, with blog posts aplenty telling you what you should and shouldn’t do.

The true answer, as with so many things, is “it depends”. In every case, you should weigh up the benefits and costs involved:

  • How much development time is it going to save versus writing (and testing!) the equivalent code yourself?
  • How much extra weight will it add to the overall bundle versus the version you could write?
  • Does the third-party version include support for a wide set of inputs and use-cases that you simply won’t be using?
  • How much do the answers to these questions matter in relation to each other?

Only you can answer these questions based on what you’re developing and how it will eventually be used.

At GoSquared, we tend to err slightly more towards writing our own code than using third-party code, if it will save size in the final bundle without sacrificing our own productivity. But that’s because our code gets embedded on thousands of our customers’ sites – if it’s bloated or slow, then that makes our customers look bad, rather than us. We want to make sure that any site which chooses to add GoSquared always feels it’s a help, never a hindrance.

Picking the best option

The second of the initial questions above is about evaluating whether a particular piece of third-party code is the right choice to use in a situation. This is an interesting question because the JavaScript ecosystem has now reached a point where for any given problem there are often several modules available to solve that problem. And that means you can weigh up the different possible solutions against each other.

The criteria for choosing between multiple different library options are much the same as for evaluating whether to use third-party code in the first place.

  • Which (if any) is easier to use from a development perspective?
  • Which has better performance in the browser?
  • Which has the smallest overall size?

Again, the answers to these questions, and how you weigh them against each other, is highly subjective. But again, at GoSquared, we lean towards better size and performance for the code that goes on our customers’ sites.

For example, we’re big fans of React for building UI. We use it across the board in our main web app and in our iOS and Android apps via React Native. So for the Assistant, we wanted to work in a similar way. However, bundle size is much more important, so we instead use Preact, which is a (just about) API-compatible drop-in for React, but with a comparably tiny footprint. With a few tweaks to our build process, we’re able to write the exact same code we do for React, but with Preact in the final bundle.

Sometimes it’s not as simple as swapping one library out for another; often there’ll be some degree of development work involved. Whether that development effort is worth the benefit to you is something you’ll need to evaluate based on your own situation.

A great tool for very quickly evaluating the potential weight of a library is Bundlephobia, which calculates the rough effect of including a module by analysing its source code. The only caveat being that it can’t take into account whether you’re including the whole library or just some parts – more on that shortly. It’s great to use as a general guide, though.

Include only what you need, if you can

Another important point to bear in mind when evaluating third-party libraries is the matter of decomposability.

That is, if a library supports a whole variety of use-cases, most of which you won’t be using, has it been written in such a way that allows you to pull in only those parts you actually need? Bundlers like Webpack or Rollup feature the idea of “tree-shaking” or “dead code elimination” which are different ways of ensuring the output only includes the code you actually use.

If a library is structured in the right way, you should be able to include only the parts you need, even if the library as a whole includes a much wider set of functionality.

A prime example of this is in the field of manipulating Dates in JavaScript. Over the years, Moment.js emerged as the library of choice for its versatility and near-complete feature set. Pretty much everything you could possibly want to do with a date (parsing, manipulating, formatting), you can do with Moment.

However, it was written largely before the age of modern bundlers, so it wasn’t written with decomposability in mind – you included the script file where you needed it and got all the functionality. Nowadays, however, there are plenty of alternative solutions, either using JavaScript’s built-in functionality, or alternative libraries such as date-fns, which were written with tree-shaking in mind. The date-fns library isn’t a simple drop-in replacement for Moment – switching from one to the other requires a fair amount of refactoring work – but we made a decision early on in the development of the GoSquared Assistant to avoid large, non-decomposable libraries such as Moment.

So that’s how we reduce the effect of third-party code included in a bundle. Besides simply writing less of our own code, there’s one last major thing we do to keep our production builds lean:

Strip out absolutely everything not needed in production

dark blue background with pale blue scissors in the centre cutting through a dotted line

During development, you’ll probably have functionality in your code for validating, debugging or logging various features, to ensure your code does what you expect it to (and if you don’t then you probably should). Even if not, the third-party libraries you include probably do. This includes everything like console.log statements, propTypes validation if you use React, and so on.

None of this functionality is of any use, however, – or at best is of very little use – in the production build you ship to customers. So it should be removed – preferably automatically by your build system.

Most libraries like React will “gate” their development-mode logic with environment variables, something like this:

if (process.env.NODE_ENV === "production") {
  // fast version of code, no debugging
} else {
  // slower version with better debugging
}

You may want to do something similar in your own code. A good bundler or minifier will be able to detect and inline the value of process.env.NODE_ENV here (with plugins like rollup-plugin-replace or Webpack’s EnvironmentPlugin), and fully strip out the parts of the code that you don’t want to include.

There are many other things that probably shouldn’t be included in the final production bundle, many of which can be completely automatically removed as part of the build process. For example, for React propTypes, we use the transform-react-remove-prop-types Babel plugin to remove all development-mode validation and warning from the production build.

So what’s the end result?

seesaw on a dark blue background showing that gosquared assistant is lighter

To the best of our knowledge, GoSquared Assistant is the leanest and fastest chat widget available right now, by a factor of about 10×.

Putting all these techniques together for the GoSquared Assistant codebase, we have:

  • Minify & compress all deployed scripts
  • Choose small and performant libraries such as Preact instead of React, where it makes sense
  • Optimise our build settings in production mode to remove all code that’s only relevant to development

Getting down to 39KB

All this results in a bundle that encapsulates all necessary JavaScript, third-party libraries, CSS and localisation strings for 18 languages that (at time of writing) measures in at 112KB (39KB after gzip), and which parses and runs faster than any other on-site chat provider out there. Even on a relatively low-end mobile device, the impact it has on any page it’s on is barely noticeable.

We’re very proud of what we’ve managed to achieve in the performance of GoSquared Assistant. To the best of our knowledge, it’s the leanest and fastest chat widget available right now, by a factor of about 10×, which makes a huge difference on mobile devices.

Obviously, there’s still more we can do, and more functionality we’ll be adding over time, but you can trust that we’ll always be keeping the performance impact to our customers’ sites front-of-mind whenever we add anything new.

Further Reading

A lot has been written recently about realising the cost of JavaScript, the developer experience, and choosing libraries. Here’s a few of our favourites:

Do you have any questions about optimising your code for speed, or want to know more about how GoSquared will work on your website? Reach out to us via Twitter, or via GoSquared Assistant!

Never miss a post