Why & How to Make the Heaviest Resource in the Web Lighter?

JavaScript is one of the heaviest resources on the web. The reason is that your code is not only downloaded by the browser, but it must also be parsed and executed. This is why a 100kBB image isn't equal to a 100kB JavaScript file.

Have you ever visited a website where you were unable to interact for a few seconds even when it was fully displayed? Parsing and executing JavaScript has a cost that can make you lost customers, money, and traffic.

53% of users will leave a mobile site if it takes more than 3 secs to load. [Source]

In this article, you learn how to make your website faster by simply analyzing the JavaScript you sent and reducing the size of it.

We are going to focus ourself on how to keep your "bundle" size reasonable. I'm not going to dive deep into how the JavaScript engine parses and executes your code. If you want to learn more about that, there's a course on FrontendMaster called Web Performance by Steve Kinney and the article The Cost Of JavaScript In 2019 by Addy Osmani.

Bundling Your Code

It can seem obvious nowadays to "bundle" your JavaScript with a tool like Webpack, Rollup or even Parcel, but a lot of developers don't know about this practice.

When you bundle your JavaScript code, you are taking all your code and all its dependencies and write them in one file.

This could easily create more issue than it solves if you are not using it carefully. Have you ever figured out that you were bundling a whole library of ~1mB for one icon?

This is why you need to analyze your bundle. When you do it, you can easily verify that all third-party packages included are really needed for your application.

An analyzed bundle

After analyzing your bundle you have multiple possibilities to reduce to the size of it without modifying your code.

Tree-Shaking

One of the easiest ways to do so is by using the technique called "Tree-Shaking".

It consists of eliminating dead-code. In other words, unused modules will not be included in your final bundle.

We can use the library lodash as an example. Quite often, you require this library to use only one, two or maybe three functions of it. But when bundling the code, you are packing the whole package (24.3 kB minified + gziped) while you only use ~1kB of it.

Selecting Tree-Shakable library and activating the Tree-Shaking settings on your bundler can easily make you won hundreds of kB of dead-code.

I actively use BundlePhobia to verify each dependency I install. I'm also using the plugin Import Cost for VSCode that gives me the size of my imports.

Example of Import Cost

Code Splitting

The second action you can do with a bundler to make your bundle lighter is called "Code Splitting".

This works by splitting your actual bundle into multiple bundles that will be loaded on demand by your application. This is useful to control the resource load prioritization.

You may have a WYSIWYG editor on your article creation page. This editor is only useful on that page but people reading your article will still retrieve the code, parse it and execute it if you don't use code splitting.

While on the other side, if you activate code splitting, you will only fetch the WYSIWYG editor code when you visit your article creation page.

Using this technique can drastically improve the first loading time of your application by only requesting critical assets and lazy-loading the other one.

This most common place to do code splitting is on your router. Doing so will let the client only download the code he needs for its current page.

// Example using the VueRouter
[
  {
    path: '/',
    name: 'Dashboard',
    component: () =>
      import(/* webpackChunkName: "dashboard" */ '@/views/DashboardView.vue'),
    meta: {
      middleware: ['auth'],
    },
  },
  {
    path: '/login',
    name: 'Login',
    component: () =>
      import(/* webpackChunkName: "login" */ '@/views/LoginView.vue'),
    meta: {
      middleware: ['guest'],
    },
  },
  ...
]

Search for Lighter Alternative

When reaching for external packages, we often stop our research when we found something that fulfills our needs. But keep in mind that there's probably a dozen of packages that satisfy your needs, and some of them are lighter than the others.

We finally have Optional Chaining in the spec but how did you manage to do something similar before?

I personally reached for the Lodash#get function (1.8kB minified + gziped) back then. Later I discovered the package dlv (191B minified + gziped).

In this example I have a small win, but if you multiply those you can easily remove dozen or even hundreds of kB in your bundle.

Have you ever heard of laue (10.7kB minified + gziped)? It's a great Vue library to make SVG charts. When you compare it to Chart.js (112.1kB minified + gziped) or ApexCharts (108.3kB minified + gziped) you see a real difference.

Ensure the Library X is needed

Quite often I found myself reaching for a third-party library while I did not need for one.

How many times you wanted to have something which isn't "common" and instead of building yours you decided to search for a library that does it for you?

Take notes that this is perfectly fine for some use-cases but you need to be careful about the impact that this third-party library will have on your code and your bundle size.

Lately, on one of my application, I wanted to have a fuzzy search system. The first thing I did was looking for a package that can do it for me. This is where I encounter fuse.js (3.9 kb minified + gziped) and installed it without thinking about the size of it.

In fact, fuse.js is great, but it also handles many use-cases that I'll never need. I ended up using a custom code (stolen on StackOverflow) that can handle my use case and is way lighter.

function fuzzy (text, search) {
  let hay = text.toLowerCase(),
    i = 0,
    n = -1,
    l;

  const s = search.toLowerCase();
  while ((l = s[i++])) if (!~(n = hay.indexOf(l, n + 1))) return false;
  return true;
}

Another example is the library you use for AJAX request. I regularly see people using axios (4.3 kB minified + gziped). The thing is, they could simply use the built-in Fetch API because they don't need the extra features that axios brings.

Conclusion

It can be very easy to send megabytes of JavaScript to your clients if you're not careful. Remember that you are testing your website on your developer machine with a good Internet connection while your client may have a poor smartphone with a 2G connection.

71% of mobile connections occur via 2G and 3G in 2018. [Source]

Ensure that your website works on those devices by testing it with services like web.dev or WebPageTest.

Those sites can give you a pretty good recommendation to increase the speed of your application and make the overall user experience better.

web.dev Report

Further reading

Credits

  • Header Photo by Allef Vinicius on Unsplash